/**
* @class Utility class that provides methods to sort data from Arrays.
* These methods are intended to be used as compareFunctions by the standard
* JavaScript {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort sort} function.
* @hideconstructor
*
* @copyright (c) 2021 TLF Research Ltd.
*/
function Sort() { }
// Parses a string like 'foo[34]' into 'foo' and 34
const PARSE_PROPERTY = /([^\[]+)(?:\[(\d+)\])?/;
/**
* Specifies to sort using a numeric comparison.
* @const
*/
Sort.NUMERIC = true;
/**
* Returns a sort function that will sort an object array in-place into <em>ascending</em> order using the given properties.
*
* @param {string|array} properties Object properties to use for the sort - to select a specific property from an array
* specify the index in the string e.g. 'property[0]' to use the first element.
* @param {boolean} [numeric=false] If true use a numeric (not string) comparison
* @param {object} [substitution] Allows values to be substituted with new ones before doing the compare.
* The substituted value is *only* used for the comparison; the original value remains in the sorted array.
*
* @example
* // a becomes [{"prop": "1"}, {"prop": "2"}, {"prop": "10"}, {"prop": null}]
* const a = [{"prop": "1"}, {"prop": null}, {"prop": "10"}, {"prop": "2"}];
* a.sort(Sort.Asc("prop", Sort.NUMERIC, {"null": "9999"}));
*
* @return {Function}
*/
Sort.Asc = function (properties, numeric, substitution) {
return Sort.sort(properties, numeric, substitution, -1, 1);
};
/**
* Returns a sort function that will sort an object array in-place into <em>descending</em> order using the given properties.
*
* @param {string|array} properties Object properties to use for the sort - to select a specific property from an array
* specify the index in the string e.g. 'property[0]' to use the first element.
* @param {boolean} [numeric=false] If true use a numeric (not string) comparison
* @param {object} [substitution] Allows values to be substituted with new ones before doing the compare.
* The substituted value is *only* used for the comparison; the original value remains in the sorted array.
*
* @example
* // a becomes [{"prop": null}, {"prop": "10"}, {"prop": "2"}, {"prop": "1"}]
* const a = [{"prop": "1"}, {"prop": null}, {"prop": "10"}, {"prop": "2"}];
* a.sort(Sort.Desc("prop", Sort.NUMERIC, {"null": "9999"}));
*
* @return {Function}
*/
Sort.Desc = function (properties, numeric, substitution) {
return Sort.sort(properties, numeric, substitution, 1, -1);
};
/**
* Sort the given object array so that its values are in the same order as the desired order.
*
* @param {array} orig Original object array to be sorted - is not changed.
* @param {string|null} property Object property to order by. If set to null it will sort using the original's raw values. In this case original shoud be a simple array of scalar values.
* @param {array} desired Desired order of array elements.
* Any values in 'orig' that are not in 'desired' will be appended to the end of the result in their original order.
*
* @example
* // a becomes [{foo:"1"}, {foo:"2"}, {foo:"6"}, {foo:"5"}]
* const a = Sort.Specific([{foo:"6"}, {foo:"5"}, {foo:"2"}, {foo:"1"}], "foo", ["1", "2"]);
*
* @return {array} Sorted array
*/
Sort.Specific = function (orig, property, desired = []) {
const clone = JSON.parse(JSON.stringify(orig));
const result = [];
desired.forEach(function (want) {
const idx = clone.map(val => property === null ? val : val[property]).indexOf(want);
if (idx !== -1) {
result.push(...clone.slice(idx, idx + 1));
clone.splice(idx, 1);
}
})
return [...result, ...clone];
}
Sort.sort = function (properties, numeric, substitution, lessThan, greaterThan) {
if (!Array.isArray(properties)) {
properties = [properties];
}
return compare;
function compare(a, b, i) {
if (typeof i === 'undefined') {
i = 0;
}
const [_, property, idx] = properties[i].match(PARSE_PROPERTY);
let valA, valB;
if (typeof idx === 'undefined') {
valA = a[property];
valB = b[property];
} else {
valA = a[property][idx];
valB = b[property][idx];
}
if (substitution && typeof substitution[valA] !== 'undefined') {
valA = substitution[valA];
}
if (substitution && typeof substitution[valB] !== 'undefined') {
valB = substitution[valB];
}
let testA = numeric ? Number(valA) : String(valA);
let testB = numeric ? Number(valB) : String(valB);
if (testA === testB) {
return ++i >= properties.length ? 0 : compare(a, b, i);
} else {
return testA < testB ? lessThan : greaterThan;
}
}
};