Friday 15 November 2019

Sorting array word-wise in Javascript

Sometimes it is nice to sort arrays word-wise in Javascript. This means sorting not the entire column value,

/**
 * Summary: Sorts alphabetaically word-wise to be used in e.g. a Select2Js control. Specify the n-word to start sorting with. 
 *
 * Alphabetically sorts by each word
 * @param {Number}   startWordIndex The 'n-word' to begin sorting with  *
 * @param {boolean}  isAscending The direction of sorting - true means ascending, false means descending
 * @return {type} Return sort comparison value. If 0, the sorting is tie, if < 0 the element a preceeds b
 */
(function () {
    var sortFunction = function sortFunctionTemplate(isAscending, startWordIndex, valueSelector, a, b) {
        var aValue = valueSelector(a);
        var bValue = valueSelector(b);
        var astripped = aValue.split(' ');
        var bstripped = bValue.split(' ');
        var wordLength = Math.min(astripped.length, bstripped.length);

        var compareValue = 0;
        for (var i = startWordIndex; i < wordLength; i++) {
            compareValue = astripped[i].localeCompare(bstripped[i]);
            if (compareValue !== 0) {
                break;
            }
        }
        if (compareValue === 0) {
            if (astripped.length > bstripped.length) {
                compareValue = 1;
            } else if (astripped.length < bstripped.length) {
                compareValue = 1;
            }
        }
        return compareValue * (isAscending ? 1 : -1);
    };
    if (typeof (Array.prototype.sortWordwise) === 'undefined') {
        // ReSharper disable once NativeTypePrototypeExtending
        Array.prototype.sortWordwise = function sortWordwise(startWordIndex, isAscending, valueSelector) {
            if (valueSelector === null) {
                valueSelector = function (item) {
                    return item;
                };
            }
            //console.log('sorterer word-wise... ');

            return this.sort(sortFunction.bind(this, isAscending, startWordIndex, valueSelector));

        };

    }
})();

Example how to use this method: Here we use the Select2.js jQuery plugin to do our custom sorting to sort word-wise. I only consider the text after the ':' in my specific use case and I supply the starting index - sorting by the nth-word 1 (i.e. second word and so on) and supply a value selector function also. Select2.Js also retrieves a matcher function to specify the matching to be done case-insensitive (the find item in list function of the select 2 js control in this specific case).

$('.stentGraftfabrikatpicker').select2({
      width: "element",
            sortResults: function(data) {
                var velgVerdi = data[0];
                var ret = [velgVerdi];
                var dataToBeSorted = data.splice(1);
                return ret.concat(
                    dataToBeSorted.sortWordwise(1, true, function(item) {
                        var value = item.text;
                        return value.indexOf(':') >= 0 ? value.substring(value.indexOf(':') + 1) : value;
                    })
                );
            },
      matcher: function(term, text) {
       return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
      },
      allowClear: true
    });

AngularJS Directive for Focus trap for modal popups

If you use Bootstrap modal popup, chances are that hitting TAB enough types on the keyboard will navigate out of the modal popup and back to the background web page, the DOM elements "beneath" the modal popup. This AngularJS directive should fix up that, wrapping everything in an IFE (Immediately invoking Function Expression). Note that this only works if your module is called 'app' (the default name).

(function() {
angular.module('app')
 .directive('modal', trapFocus)
 
 function trapFocus() {
        return {
            restrict: 'C',
            priority: 1,
            link: function (scope, element, attr) {
                if (typeof (scope.registerFocusTrap) === 'undefined') {
                    scope.registerFocusTrap = registerFocusTrap;
                } else {
                    for (var i = 0; i < element.length; i++) {
                        registerFocusTrap(element.get(i));
 
                    }
                }
 
            }
        };
    }
 
    function registerFocusTrap(element) {
        var focusableEls = element.querySelectorAll(
            'a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])');
        var firstFocusableEl = focusableEls[0];
        var lastFocusableEl = focusableEls[focusableEls.length - 1];
        // ReSharper disable once InconsistentNaming
        var KEYCODE_TAB = 9;
 
        $(element).on('keydown',
            function (e) {
                console.log('inside registerFocusTrap keydown');
                var isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB);
 
                if (!isTabPressed) {
                    return;
                }
 
                if (e.shiftKey) /* shift + tab */ {
                    if (document.activeElement === firstFocusableEl) {
                        lastFocusableEl.focus();
                        e.preventDefault();
                    }
                } else /* tab */ {
                    if (document.activeElement === lastFocusableEl) {
                        firstFocusableEl.focus();
                        e.preventDefault();
                    }
                }
            }
        );
    }
  
}); 
 

Note that the name of registered modules in AngularJs is not supported in AngularJs by itself. This polyfill should tough fix up this.

(function(orig) {
    angular.modules = [];
    angular.module = function() {
        if (arguments.length > 1) {
            angular.modules.push(arguments[0]);
        }
        return orig.apply(null, arguments);
    }
})(angular.module);

Sunday 10 November 2019

Implementing projection in Javascript

Github page for source code in this article:
https://github.com/toreaurstadboss/JsLinqSimpleProjection
https://www.npmjs.com/package/jslinqsimpleprojection
Npm package for source code in this article: Since I started working with Linq in C#, I missed a good way of doing much of the same functionality in Javascript. Today, there are several Linq libraries for Javascript and Typescript, and libraries such as Backbone.Js or Lodash also containing a lot of helpful operators or utility methods. As an educational exercise, I was looking into a simple way of doing a projection method in pure Javascript (no es6 syntax). Here is what I made. First off, we need to be able to project an array of objects by listing up properties. In ES6 Javascript we could use arrow functions. But I wanted to support pure Javascript. So I choose to use a comma separated list of property or field values to dive into the array object, written in Json notation of course. Consider first this array as an example:

        var someCountries = [
          { country: "Norway", population: 5.2, code: "NO" },
          { country: "Finland", population: 5.5, code: "SU" },
          { country: "Iceland", population: 0.4, code: "IC" },
          { country: "Sweden", population: 10.2, code: "SW" }
        ];

We want to project this array using a new method select on the array of which we use the Array.prototype to achieve. Note that this will immediately add methods to all array objects immediately in the global scope. Now consider a projection of just the 'country' and the 'population' fields of the Json structure. Given a method call of just these two properties, we want to create a select projection method. First consider this lightweight linqmodule implementation, using an IFE (Immediately invoked function expression) and using the revealing module pattern. We expose the method dump to this module.

var linqmodule = (function() {
  projection = function(members) {
    var membersArray = members.replace(/s/g, "").split(",");
    var projectedObj = {};

    for (var i = 0; i < this.length; i++) {
      for (var j = 0; j < membersArray.length; j++) {
        var key = membersArray[j];
        if (j === 0) {
          projectedObj[i] = {};
        }
        projectedObj[i][key] = this[i][key];
      }
    }

    return projectedObj;
  };
  Array.prototype.select = projection;

  dumpmethod = function(arrayobj) {
    var result = "";
    result += "[";

    for (var i = 0; i < Object.keys(arrayobj).length; i++) {
      var membersArray = Object.keys(arrayobj[i]);
      for (var j = 0; j < membersArray.length; j++) {
        if (j === 0) {
          result += "{";
        }
        var key = membersArray[j];
        result +=
          "key: " +
          key +
          " , value: " +
          arrayobj[i][key] +
          (j < membersArray.length - 1 ? " , " : "");
        if (j === membersArray.length - 1) {
          result +=
            "}" + (i < Object.keys(arrayobj).length - 1 ? "," : "") + "\n";
        }
      }
    }
    result += "]";

    return result;
  };

  return {
    dump: dumpmethod
  };
})();


Now it is easy to dump the contents of our projected array (which is copied into a new object) using the dump method:

 result = someNums.select("country,population");
         document.getElementById("result").innerText = linqmodule.dump(result);

        console.log(result);

Note that our new object contains only the country and population fields in the Json structure, not the code. We have created a simple projection mechanism in Javascript in a self contained module!