var aNames= [ "Bob", "Jim", "Sam", "Al" ];
alert( aNames.toString() ); // (unsorted) shows: Bob,Jim,Sam,Al
aNames.sort(); // sort the array
alert( aNames.toString() ); // (sorted) shows: Al,Bob,Jim,Sam
As shown in that example, the sort is a "lexical" sort -- it sorts alphabetically in ascending (A-Z) order. That's great for sorting names, but not so wonderful when sorting numbers or other data types. For instance:
var aNumbers= [ 900, 17, 3, 1002 ];
alert( aNumbers.toString() ); // (unsorted) shows: 900,17,3,1002
aNumbers.sort(); // sort the array
alert( aNumbers.toString() ); // (sorted???) shows: 1002,17,3,900
About the only thing that's good about that result is that it makes it easy for your JavaScript professor to give you a failing grade! It's even worse if you are sorting date fields. The default sort turns a Date value into words, like:
function CompareNumbers( v1, v2 ) {
if ( v1 < v2 ) return -1; // v1 goes nearer to the top
if ( v1 > v2 ) return 1; // v1 goes nearer to the bottom
return 0; // both are the same
}
...
var aNumbers= [ 900, 17, 3, 1002 ];
alert( aNumbers.toString() ); // (unsorted) shows: 900,17,3,1002
aNumbers.sort( CompareNumbers ); // sort the array
alert( aNumbers.toString() ); // (sorted!!!) shows: 3,17,900,1002
The comparison function gets called many times during the sorting operation. Each time, it is passed two values (v1 and v2). All you need to do is decide which value is higher and return an indicator:
aNumbers.sort(
function(v1,v2){
return v1-v2; // sort numbers ascending
}
);
function MyCompare( v1, v2 ) {
if ( gfSortDescending ) {
var t=v1; v1=v2; v2=t; // swap the parms
}
if ( v1 < v2 ) return -1; // v1 goes nearer to the top
if ( v1 > v2 ) return 1; // v1 goes nearer to the bottom
return 0; // both are the same
}
function CompareDates( d1, d2 ) {
if ( d1 < d2 ) return -1; // d1 is in the past of d2
if ( d1 > d2 ) return 1; // d1 is in the future of d2
return 0;
}
...
var dt1= new Date('10/31/2010');
var dt2= new Date('11/01/2010');
var dt3= new Date('1/1/2011');
var aDates= [dt1, dt2, dt3 ];
aDates.sort( CompareDates );
alert( aDates.toString() ); // sorted correctly in ascending order
Another commonly-used option: Avoid storing Date objects in the array, and instead, store text strings that are in an implicitly sortable format: YYYY-MM-DD
var aDateStrings= ["2010-10-31", "2010-11-01", "2011-01-01" ];
aDateStrings.sort(); // OK to use the default (string) sort
alert( aDateStrings.toString() ); // sorted in ascending order
The trick with this technique is to be sure that all of the dates are formatted correctly with four-digit years, and two digits (including leading zero if needed) for the month and the day.
function ArticleRec(i,a,t,aw)
{
this.nID= i;
this.sAuthor= a;
this.sTitle= t;
this.aAwards= aw; // EC, EEA, CP, or (empty)
this.nAwardLvl= 0; // (none)
if (this.aAwards.indexOf("CP") > -1) this.nAwardLvl = 1;
if (this.aAwards.indexOf("EEA") > -1) this.nAwardLvl = 2;
if (this.aAwards.indexOf("EC") > -1) this.nAwardLvl = 3;
}
Note the addition of some program logic (lines 8-10) that generates an integer value based on the award level. This constructor logic saves some steps later when we want to sort the record array.
var arArticles= [
new ArticleRec(1023,"matthews...","Switch in Access", "EEA,CP" ),
new ArticleRec(501, "DanRollins", "ABCs of JavaScript","" ),
new ArticleRec(2001,"matthews...","Pivot Tables", "EEA,CP" ),
new ArticleRec(1700,"DanRollins", "Writing Limericks", "CP" ),
new ArticleRec(2590,"DanRollins", "C++ Multithreading","EEA" ),
new ArticleRec(3111,"Harfang", "Printing Labels", "EC,EEA,CP" ),
new ArticleRec(2901,"demazter", "Offline Defrag", "EEA,CP" ),
new ArticleRec(17, "matthews...","PMT, FV, and PPMT", "EC,EEA,CP" ),
new ArticleRec(1517,"angelIII", "UPDATES with JOIN", "EC,EEA" )
];
I wrote a little "display the data" function. Here is the table before and after using the default sort:
var gsSortBy=""; // a field name -- global var for now
MyCompare= function(v1,v2) {
if ( v1[gsSortBy] < v2[gsSortBy] ) return -1;
if ( v1[gsSortBy] > v2[gsSortBy] ) return 1;
return 0;
}
...
gsSortBy="sAuthor"; // set the sort key
arArticles.sort( MyCompare );
And the result is somewhat better:
var gsSortBy=""; // a field name -- global var for now
MyCompareNoCase= function(v1,v2) {
if ( v1[gsSortBy].toLowerCase() < v2[gsSortBy].toLowerCase() ) return -1;
if ( v1[gsSortBy].toLowerCase() > v2[gsSortBy].toLowerCase() ) return 1;
return 0;
}
...
gsSortBy= "sAuthor";
arArticles.sort( MyCompareNoCase );
And the result is exactly what we want:
var gsSortBy=""; // a field name -- global var for now
MyCompareNoCaseAndNumbers= function( v1,v2 ) { // two objects
if ( typeof v1[gsSortBy]=="number" ) { // when sorting by a numeric field
if ( v1[gsSortBy] < v2[gsSortBy] ) return -1;
if ( v1[gsSortBy] > v2[gsSortBy] ) return 1;
return 0;
}
else { // assume sort field is a String
if ( v1[gsSortBy].toLowerCase() < v2[gsSortBy].toLowerCase() ) return -1;
if ( v1[gsSortBy].toLowerCase() > v2[gsSortBy].toLowerCase() ) return 1;
return 0;
}
}
...
gsSortBy= "nID"; // set the sort key
arArticles.sort( MyCompareNoCaseAndNumbers );
This also works for sorting on nAwardLvl since it, too, has a numeric value.
MyCompareNoCaseAndNumbers= function(v1,v2) {
if (gfSortDescending) {
var t=v1; v1=v2; v2=t; // swap the parms
}
// do the comparison here
...
}
...
gsSortBy= "nAwardLvl"; // global var for now
gfSortDescending= true; // global var for now
arArticles.sort( MyCompareNoCaseAndNumbers );
var sKey1= v1.sNameLast + v1.sNameFirst;
var sKey2= v2.sNameLast + v2.sNameFirst;
if (sKey1 > sKey2 ) return 1;
if (sKey1 < sKey2 ) return -1;
return 0;
Though simple, that's not very flexible; for one thing, it fails for numeric sorts. To create a generalized solution, just think about this: The secondary sort comes into play only when the two primary sort keys are equal. So all you need to do is add some logic to the comparison function where you would normally return 0. Here's a simplified example of what I mean:
// uses global variables gsSortyBy and gsSortBy2
MyCompareTwoKeys= function(v1,v2) {
if ( v1[gsSortBy] < v2[gsSortBy] ) return -1;
if ( v1[gsSortBy] > v2[gsSortBy] ) return 1;
// else, they are equal
if ( gsSortBy2 > "" ) { // a secondary sort field has been set
if ( v1[gsSortBy2] < v2[gsSortBy2] ) return -1;
if ( v1[gsSortBy2] > v2[gsSortBy2] ) return 1;
}
return 0;
}
So, here's our output when sorted by Author (one key) and then when sorted by Author and Title (two keys).
// Assign some default attributes to the array object
// This lets us keep the data together rather than use global variables
//
arArticles.sortBy= "nAwardLvl"; // a field name in an ArticleRec object
arArticles.sortDir= "AtoZ"; // "AtoZ" or "ZtoA"
arArticles.sortBy2= "sAuthor";
arArticles.sortDir2="AtoZ";
//-------------------------------------- the sort function for this array
arArticles.Compare= function(v1,v2) {
if ( arArticles.sortDir == "ZtoA" ) {
var t=v1; v1=v2; v2=t; // swap the parms
}
var sFld= arArticles.sortBy;
if ( typeof v1[sFld]=="number" ) {
if ( v1[sFld] < v2[sFld] ) return -1;
if ( v1[sFld] > v2[sFld] ) return 1;
}
else { // string
if ( v1[sFld].toLowerCase() < v2[sFld].toLowerCase() ) return -1;
if ( v1[sFld].toLowerCase() > v2[sFld].toLowerCase() ) return 1;
}
//------------------------------- check for and handle secondary sort key
if ( arArticles.sortBy2 > "" ) {
if ( arArticles.sortDir=="AtoZ" && arArticles.sortDir2=="ZtoA" ) {
var t=v1; v1=v2; v2=t; // swap the parms
}
sFld= arArticles.sortBy2;
if ( typeof v1[sFld]=="number" ) {
if ( v1[sFld] < v2[sFld] ) return -1;
if ( v1[sFld] > v2[sFld] ) return 1;
}
else { // string
if ( v1[sFld].toLowerCase() < v2[sFld].toLowerCase() ) return -1;
if ( v1[sFld].toLowerCase() > v2[sFld].toLowerCase() ) return 1;
}
}
return 0;
}
And here is the complete code of an HTA (Hypertext Application) that uses these JavaScript techniques.
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (0)