Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
angular
.module( "core.common.internationalization" )
.constant( "naturalSort", naturalSort )
.constant( "naturalSortComparator", naturalSortComparator );
// Comparator to be used with the orderBy filter
function naturalSortComparator( a, b ) {
return naturalSort( a.value, b.value );
}
/*
* Natural Sort algorithm for Javascript - Version 0.8.1 - Released under MIT license
* Author: Jim Palmer (based on chunking idea from Dave Koelle)
*/
function naturalSort( a, b ) {
// convert all to strings strip whitespace
// Match whitespace at start and end of input.
const rePadding = /^\s+|\s+$/g;
const toTrimmed = input => ( naturalSort.insensitive ? `${input}`.toLowerCase() : `${input}` ).replace( rePadding, "" );
// chunk/tokenize
const trimmedA = toTrimmed( a );
const trimmedB = toTrimmed( b );
// Numeric, hex or date detection.
// Splits the input string into segments that can interpreted numerically.
const reNumeric = /(^([+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?(?=\D|\s|$))|^0x[\da-fA-F]+$|\d+)/g;
const numericA = trimmedA
.replace( reNumeric, "\0$1\0" )
.replace( /\0$/, "" )
.replace( /^\0/, "" )
.split( "\0" );
const numericB = trimmedB
.replace( reNumeric, "\0$1\0" )
.replace( /\0$/, "" )
.replace( /^\0/, "" )
.split( "\0" );
const reDate = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[/-]\d{1,4}\/-]\d{1,4}|^\w+, \w+ \d+, \d{4})/;
const reHexadecimal = /^0x[0-9a-f]+$/i;
const numericValueA = parseInt( trimmedA.match( reHexadecimal ), 16 ) || ( numericA.length !== 1 && Date.parse( trimmedA ) );
const numericValueB = parseInt( trimmedB.match( reHexadecimal ), 16 ) || numericValueA && trimmedB.match( reDate ) && Date.parse( trimmedB ) || null;
// Match 0 at start of input.
const reZero = /^0/;
// Match consecutive whitespace.
const reWhitespace = /\s+/g;
// normalize spaces; find floats not starting with '0', string or 0 if not defined (Clint Priest)
const normChunk = ( input, length ) => ( !input.match( reZero ) || length === 1 ) && parseFloat( input ) || input.replace( reWhitespace, " " ).replace( rePadding, "" ) || 0;
let segmentNormalizedA = "";
let segmentNormalizedB = "";
// first try and sort Hex codes or Dates
if( numericValueB ) {
if( numericValueA < numericValueB ) {
return -1;
} else if( numericValueA > numericValueB ) {
return 1;
}
}
// natural sorting through split numeric strings and default strings
const segmentCountA = numericA.length;
const segmentCountB = numericB.length;
const segmentCount = Math.max( segmentCountA, segmentCountB );
for( let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++ ) {
segmentNormalizedA = normChunk( numericA[ segmentIndex ] || "", segmentCountA );
segmentNormalizedB = normChunk( numericB[ segmentIndex ] || "", segmentCountB );
// handle numeric vs string comparison - number < string - (Kyle Adams)
if( isNaN( segmentNormalizedA ) !== isNaN( segmentNormalizedB ) ) {
return isNaN( segmentNormalizedA ) ? 1 : -1;
}
// use locale comparison if available
if( segmentNormalizedA.localeCompare ) {
const comp = segmentNormalizedA.localeCompare( segmentNormalizedB );
if( comp === 0 ) {
continue;
}
return comp / Math.abs( comp );
}
if( segmentNormalizedA < segmentNormalizedB ) {
return -1;
} else if( segmentNormalizedA > segmentNormalizedB ) {
return 1;
}
}
return 0;
}
// Insensitive by default. Was inherently case-insensitive previously, due to
// certain inputs enforcing certain comparison paths.
naturalSort.insensitive = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment