xref: /plugin/sortablejs/script.js (revision 274578ffeea535787fddcf6d57c322b9b3890c7d)
1/*
2 SortTable
3
4 Instructions:
5 Used from dokuwiki
6 Click on the headers to sort
7
8 Thanks to many, many people for contributions and suggestions.
9 Currently licensed as X11:
10 http://www.kryogenix.org/code/browser/licence.html
11
12 v2.1 (7th April 2007)
13 * originally by Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
14
15 v2.1b (19 Feb 2008)
16 * first Otto Vainio (otto@valjakko.net) revision
17 * Fixed some jslint errors to support DokuWiki (http://www.splitbrain.org) js compression
18 * function reinitsort()
19 * sorttable.reinit
20
21 v2.2 (27.11.2008)
22 * Changed line 77 document.getElementsByTagName('table') to div.getElementsByTagName('table')
23 to allow multiple sortable tables in same page (Thanks to Hans Sampiemon)
24
25 v2.3 (14.1.2009)
26 * Added option for default sorting.
27 * Use dokuwiki event registration.
28
29 v2.4 (27.1.2009)
30 * Cleaned some jlint errors to make this workable, when css+js compress is set in dokuwiki
31
32 v2.5 (10.5.2011)
33 * Fixed problems with secionediting, footnotes and edittable
34
35 v2.6 (18.7.2013)
36 * Added support for jQuery and dokuwiki Weatherwax ->
37
38 v2.7 (28.5.2014)
39 * Fixed problem with first row not getting sorted
40
41 v2.8 (30.5.2014)
42 * Fixed problem with first row not getting sorted in default sort. Added option "sumrow" to prevent sum line sort.
43
44 v2.9 (13.8.2014)
45 * Fixed problem with header row being sorted in earlier versions of dokuwiki. Added option for sorting back to default
46
47 v2.11 (6.7.2015)
48 * Added ip address sort. (Thanks Chefkeks)
49
50 v2.12 (14.12.2015)
51 * PHP 7 compatibility and issue #8. Default sort for columns > 9
52
53 v2.13
54 * revision by Vaxquis; fixes some (most) of the current issues
55 */
56
57//
58
59var stIsIE = /*@cc_on!@*/false;
60//var tableid = 0;
61
62var sorttable = {
63    reinit: function () {
64        arguments.callee.done = true;
65        // kill the timer
66        //if (_timer) {clearInterval(_timer);}
67
68        if ( !document.createElement || !document.getElementsByTagName ) {
69            return;
70        }
71
72//    sorttable.DATE_RE = /^(\d\d?)[\/\.\-](\d\d?)[\/\.\-]((\d\d)?\d\d)$/;
73        sorttable.DATE_RE = /^(\d\d?)[\/\.\-](\d\d?)[\/\.\-]((\d\d)?\d\d)((\d\d?)[:\.]?(\d\d?))?$/;
74
75        var elems = document.getElementsByTagName( "table" );
76        var elem;
77        for( var i = 0; i < elems.length; i++ ) {
78            elem = elems[i];
79            if ( elem.className.search( /\bsortable\b/ ) !== -1 ) {
80                sorttable.makeSortable( elem );
81            }
82        }
83        elems = document.getElementsByTagName( "div" );
84        for( var i = 0; i < elems.length; i++ ) {
85            elem = elems[i];
86            if ( elem.className.search( /\bsortable\b/ ) !== -1 ) {
87                sorttable.makeSortableDiv( elem );
88            }
89        }
90    },
91    init: function () {
92        if ( arguments.callee.done ) {
93            return;
94        }
95        sorttable.reinit();
96    },
97    makeSortableDiv: function ( div ) {
98        var childTables = div.getElementsByTagName( "table" );
99        var elem;
100        for( var i = 0; i < childTables.length; i++ ) {
101            elem = childTables[i];
102            var colid = div.className;
103            var patt1 = /\bcol_\d_[a-z]+/gi;
104            var overs = [];
105            if ( colid.search( patt1 ) !== -1 ) {
106                var overrides = colid.match( patt1 );
107                for( var i = 0; i < overrides.length; i++ ) {
108                    var entry = overrides[i];
109                    if ( entry !== "" ) {
110                        try {
111                            var tmp = entry.split( "_" );
112                            var ind = tmp[1];
113                            var val = tmp[2];
114                            overs[ind] = val;
115                        } catch( e ) {
116                        }
117                    }
118                }
119                colid = colid.replace( patt1, '' );
120            }
121            var patt2 = /\bsortbottom_?\d?/gi;
122            var bottoms = 0;
123            if ( colid.search( patt2 ) !== -1 ) {
124                var bs = colid.match( patt2 );
125                try {
126                    var tmp = bs[0].split( "_" );
127                    //var ind = tmp[1];
128                    var val = 1;
129                    if ( tmp.length > 1 ) {
130                        val = tmp[1];
131                    }
132                    bottoms = val;
133                } catch( e ) {
134                }
135            }
136            var patt2ph = /\bthreephase/gi;
137            var ph2 = true;
138            if ( colid.search( patt2ph ) !== -1 ) {
139                ph2 = false;
140            }
141
142            sorttable.makeSortable( elem, overs, bottoms, ph2 );
143            var pattdefault = /\bsortr?\d\d?/gi;
144            if ( colid.search( pattdefault ) !== -1 ) {
145                var mi = colid.match( pattdefault );
146                colid = mi[0].replace( 'sort', '' );
147                if ( colid !== '' ) {
148                    colid = colid.trim();
149                }
150                var revs = false;
151                if ( colid.search( /\br/ ) !== -1 ) {
152                    revs = true;
153                    colid = colid.replace( 'r', '' );
154                }
155                sorttable.defaultSort( elem, colid, revs );
156            }
157        }
158    },
159    defaultSort: function ( table, colid, revs ) {
160//    theadrow = table.tHead.rows[0].cells;
161        var havetHead = table.tHead;
162        var sindex = 1;
163        if ( havetHead ) {
164            sindex = 0;
165        }
166        var theadrow = table.rows[0].cells;
167        colid--;
168        var colname = "col" + colid;
169        // remove sorttable_sorted classes
170        var thiscell = false;
171        for( var i = 0; i < theadrow.length; i++ ) {
172            var cell = theadrow[i];
173            var colclass = cell.className;
174            var classname = colclass.split( " " );
175//       if (cell.className==colname)
176            if ( classname[0] === colname ) {
177                thiscell = cell;
178            }
179//       if (cell.nodeType == 1) { // an element
180//         cell.className = cell.className.replace('sorttable_sorted_reverse','');
181//         cell.className = cell.className.replace('sorttable_sorted','');
182//       }
183        }
184        if ( thiscell === false ) {
185            return;
186        }
187        var sortfwdind = document.getElementById( 'sorttable_sortfwdind' );
188        if ( sortfwdind ) {
189            sortfwdind.parentNode.removeChild( sortfwdind );
190        }
191        var sortrevind = document.getElementById( 'sorttable_sortrevind' );
192        if ( sortrevind ) {
193            sortrevind.parentNode.removeChild( sortrevind );
194        }
195
196        thiscell.className += ' sorttable_sorted';
197        sortfwdind = document.createElement( 'span' );
198        sortfwdind.id = "sorttable_sortfwdind";
199        sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
200        thiscell.appendChild( sortfwdind );
201
202        // build an array to sort. This is a Schwartzian transform thing,
203        // i.e., we "decorate" each row with the actual sort key,
204        // sort based on the sort keys, and then put the rows back in order
205        // which is a lot faster because you only do getInnerText once per row
206        var row_array = [];
207        var col = thiscell.sorttable_columnindex;
208        var rows = thiscell.sorttable_tbody.rows;
209        for( var j = sindex; j < rows.length; j++ ) {
210            row_array[row_array.length] = [sorttable.getInnerText( rows[j].cells[col] ), rows[j]];
211        }
212        /* If you want a stable sort, uncomment the following line */
213        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
214        /* and comment out this one */
215        row_array.sort( thiscell.sorttable_sortfunction );
216
217        var tb = thiscell.sorttable_tbody;
218        for( var jj = 0; jj < row_array.length; jj++ ) {
219            tb.appendChild( row_array[jj][1] );
220        }
221
222        //delete row_array;
223        // If reverse sort wanted, then doit
224        if ( revs ) {
225            // reverse the table, which is quicker
226            sorttable.reverse( thiscell.sorttable_tbody, sindex );
227            thiscell.className = thiscell.className.replace( 'sorttable_sorted',
228                    'sorttable_sorted_reverse' );
229            thiscell.removeChild( document.getElementById( 'sorttable_sortfwdind' ) );
230            sortrevind = document.createElement( 'span' );
231            sortrevind.id = "sorttable_sortrevind";
232            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
233            thiscell.appendChild( sortrevind );
234        }
235    },
236    makeSortable: function ( table, overrides, bottoms, ph2 ) {
237//    tableid++;
238        /*
239         if (table.getElementsByTagName('thead').length === 0) {
240         // table doesn't have a tHead. Since it should have, create one and
241         // put the first table row in it.
242         the = document.createElement('thead');
243         the.appendChild(table.rows[0]);
244         table.insertBefore(the,table.firstChild);
245         }
246         */
247        // Safari doesn't support table.tHead, sigh
248        /*
249         if (table.tHead === null) {table.tHead = table.getElementsByTagName('thead')[0];}
250
251         if (table.tHead.rows.length != 1) {return;} // can't cope with two header rows
252         */
253//    table.tHead.className += ' tableid'+tableid;
254
255        // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
256        // "total" rows, for example). This is B&R, since what you're supposed
257        // to do is put them in a tfoot. So, if there are sortbottom rows,
258        // for backwards compatibility, move them to tfoot (creating it if needed).
259
260        var sortbottomrows = [];
261        if ( bottoms > 0 ) {
262            var frombottom = table.rows.length - bottoms;
263            for( var i = table.rows.length - 1; i >= frombottom; i-- ) {
264//      if (bottoms<frombottom) {
265                sortbottomrows[sortbottomrows.length] = table.rows[i];
266//      }
267//      frombottom++;
268            }
269            if ( sortbottomrows ) {
270                var tfo;
271                if ( table.tFoot === null ) {
272                    // table doesn't have a tfoot. Create one.
273                    tfo = document.createElement( 'tfoot' );
274                    table.appendChild( tfo );
275                }
276                for( var ii = sortbottomrows.length - 1; ii >= 0; ii-- ) {
277                    tfo.appendChild( sortbottomrows[ii] );
278                }
279                //delete sortbottomrows;
280            }
281        }
282        // work through each column and calculate its type
283        var havetHead = table.tHead;
284        var sindex = 1;
285        if ( havetHead ) {
286            sindex = 0;
287        }
288        var headrow = table.rows[0].cells;
289//    for (var i=0; i<headrow.length; i++) {
290        for( var i = 0; i < headrow.length; i++ ) {
291            // manually override the type with a sorttable_type attribute
292            var colOptions = "";
293            if ( overrides[i + 1] )
294            {
295                colOptions = overrides[i + 1];
296            }
297            if ( !colOptions.match( /\bnosort\b/ ) ) { // skip this col
298                var mtch = colOptions.match( /\b[a-z0-9]+\b/ );
299                var override;
300                if ( mtch ) {
301                    override = mtch[0];
302                }
303                if ( mtch && typeof sorttable["sort_" + override] === 'function' ) {
304                    headrow[i].sorttable_sortfunction = sorttable["sort_" + override];
305                } else {
306                    headrow[i].sorttable_sortfunction = sorttable.guessType( table, i );
307                }
308                /*
309                 if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
310                 mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
311                 if (mtch) { override = mtch[1]; }
312                 if (mtch && typeof sorttable["sort_"+override] == 'function') {
313                 headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
314                 } else {
315                 headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
316                 }
317                 */
318                // make it clickable to sort
319                headrow[i].sorttable_columnindex = i;
320                headrow[i].sorttable_tbody = table.tBodies[0];
321                headrow[i].sindex = sindex;
322//        dean_addEvent(headrow[i],"click", function(e) {
323//        addEvent(headrow[i],"click", function(e) {
324                jQuery( headrow[i] ).click( function () {
325
326                    var theadrow = this.parentNode;
327                    var sortrevind, sortfwdind;
328                    if ( this.className.search( /\bsorttable_sorted\b/ ) !== -1 ) {
329                        // if we're already sorted by this column, just
330                        // reverse the table, which is quicker
331                        sorttable.reverse( this.sorttable_tbody, this.sindex );
332                        this.className = this.className.replace( 'sorttable_sorted',
333                                'sorttable_sorted_reverse' );
334                        sortfwdind = document.getElementById( 'sorttable_sortfwdind' );
335                        if ( sortfwdind ) {
336                            sortfwdind.parentNode.removeChild( sortfwdind );
337                        }
338//            this.removeChild(document.getElementById('sorttable_sortfwdind'));
339                        sortrevind = document.getElementById( 'sorttable_sortrevind' );
340                        if ( sortrevind ) {
341                            sortrevind.parentNode.removeChild( sortrevind );
342                        }
343                        sortrevind = document.createElement( 'span' );
344                        sortrevind.id = "sorttable_sortrevind";
345                        sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
346                        this.appendChild( sortrevind );
347                        return;
348                    }
349                    if ( this.className.search( /\bsorttable_sorted_reverse\b/ ) !== -1 ) {
350                        if ( !ph2 ) {
351                            sorttable.original_order( this.sorttable_tbody, this.sindex );
352                            var list = theadrow.childNodes;
353                            for( var i = 0; i < list.length; i++ ) {
354                                var cell = list[i];
355                                if ( cell.nodeType === 1 ) { // an element
356                                    cell.className = cell.className.replace( 'sorttable_sorted_reverse', '' );
357                                    cell.className = cell.className.replace( 'sorttable_sorted', '' );
358                                }
359                            }
360                            sortfwdind = document.getElementById( 'sorttable_sortfwdind' );
361                            if ( sortfwdind ) {
362                                sortfwdind.parentNode.removeChild( sortfwdind );
363                            }
364                            sortrevind = document.getElementById( 'sorttable_sortrevind' );
365                            if ( sortrevind ) {
366                                sortrevind.parentNode.removeChild( sortrevind );
367                            }
368                            return;
369                        } else {
370                            // if we're already sorted by this column in reverse, just
371                            // re-reverse the table, which is quicker
372                            sorttable.reverse( this.sorttable_tbody, this.sindex );
373                            this.className = this.className.replace( 'sorttable_sorted_reverse',
374                                    'sorttable_sorted' );
375                            sortrevind = document.getElementById( 'sorttable_sortrevind' );
376                            if ( sortrevind ) {
377                                sortrevind.parentNode.removeChild( sortrevind );
378                            }
379                            //            this.removeChild(document.getElementById('sorttable_sortrevind'));
380                            sortfwdind = document.getElementById( 'sorttable_sortfwdind' );
381                            if ( sortfwdind ) {
382                                sortfwdind.parentNode.removeChild( sortfwdind );
383                            }
384                            sortfwdind = document.createElement( 'span' );
385                            sortfwdind.id = "sorttable_sortfwdind";
386                            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
387                            this.appendChild( sortfwdind );
388                            return;
389                        }
390                    }
391
392                    // remove sorttable_sorted classes
393//          theadrow = this.parentNode;
394                    var list = theadrow.childNodes;
395                    for( var i = 0; i < list.length; i++ ) {
396                        var cell = list[i];
397                        if ( cell.nodeType === 1 ) { // an element
398                            cell.className = cell.className.replace( 'sorttable_sorted_reverse', '' );
399                            cell.className = cell.className.replace( 'sorttable_sorted', '' );
400                        }
401                    }
402                    sortfwdind = document.getElementById( 'sorttable_sortfwdind' );
403                    if ( sortfwdind ) {
404                        sortfwdind.parentNode.removeChild( sortfwdind );
405                    }
406                    sortrevind = document.getElementById( 'sorttable_sortrevind' );
407                    if ( sortrevind ) {
408                        sortrevind.parentNode.removeChild( sortrevind );
409                    }
410
411                    this.className += ' sorttable_sorted';
412                    sortfwdind = document.createElement( 'span' );
413                    sortfwdind.id = "sorttable_sortfwdind";
414                    sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
415                    this.appendChild( sortfwdind );
416
417                    // build an array to sort. This is a Schwartzian transform thing,
418                    // i.e., we "decorate" each row with the actual sort key,
419                    // sort based on the sort keys, and then put the rows back in order
420                    // which is a lot faster because you only do getInnerText once per row
421                    var row_array = [];
422                    var col = this.sorttable_columnindex;
423                    var rows = this.sorttable_tbody.rows;
424                    sindex = this.sindex;
425                    for( var j = sindex; j < rows.length; j++ ) {
426                        row_array[row_array.length] = [sorttable.getInnerText( rows[j].cells[col] ), rows[j]];
427                    }
428                    /* If you want a stable sort, uncomment the following line */
429                    //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
430                    /* and comment out this one */
431                    row_array.sort( this.sorttable_sortfunction );
432
433                    var tb = this.sorttable_tbody;
434                    for( var j3 = 0; j3 < row_array.length; j3++ ) {
435                        tb.appendChild( row_array[j3][1] );
436                    }
437
438                    //delete row_array;
439                } );
440            }
441        }
442    },
443    guessType: function ( table, column ) {
444        // guess the type of a column based on its first non-blank row
445        var textCnt = 0;
446        var numCnt = 0;
447        var ddmmCnt = 0;
448        var mmddCnt = 0;
449        var ipCnt = 0;
450
451        for( var i = 0; i < table.tBodies[0].rows.length; i++ ) {
452            var text = sorttable.getInnerText( table.tBodies[0].rows[i].cells[column] );
453            if ( text !== "" ) {
454                if ( text.match( /^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$/ ) ) {  // now for ip-addresses
455                    ipCnt++;
456                } else if ( text.match( /^[\-\+]?[£$¤]?[\d,.]+[%€]?$/ ) ) {
457                    numCnt++;
458                } else {
459                    // check for a date: dd/mm/yyyy or dd/mm/yy
460                    // can have / or . or - as separator
461                    // can be mm/dd as well
462                    var possdate = text.match( sorttable.DATE_RE );
463                    if ( possdate ) {
464                        // looks like a date
465                        var first = parseInt( possdate[1] );
466                        var second = parseInt( possdate[2] );
467                        if ( first > 12 ) {
468                            // definitely dd/mm
469                            ddmmCnt++;
470                        } else if ( second > 12 ) {
471                            mmddCnt++;
472                        } else {
473                            // looks like a date, but we can't tell which, so assume
474                            // that it's dd/mm (English imperialism!) and keep looking
475                            ddmmCnt++;
476                        }
477                    } else { // not a date (nor IP nor number)
478                        textCnt++;
479                    }
480                }
481            }
482        }
483        if ( textCnt > numCnt && textCnt > ipCnt && textCnt > ddmmCnt && textCnt > mmddCnt )
484            return sorttable.sort_alpha;
485        if ( numCnt > ipCnt && numCnt > ddmmCnt && numCnt > mmddCnt )
486            return sorttable.sort_numeric;
487        if ( ipCnt > ddmmCnt && ipCnt > mmddCnt )
488            return sorttable.sort_ipaddr;
489        return ( ddmmCnt > mmddCnt ) ? sorttable.sort_ddmm : sorttable.sort_mmdd;
490    },
491    getInnerText: function ( node ) {
492        // gets the text we want to use for sorting for a cell.
493        // strips leading and trailing whitespace.
494        // this is *not* a generic getInnerText function; it's special to sorttable.
495        // for example, you can override the cell text with a customkey attribute.
496        // it also gets .value for <input> fields.
497        if ( !node ) {
498            return '';
499        }
500        var hasInputs = ( typeof node.getElementsByTagName === "function" ) &&
501                node.getElementsByTagName( "input" ).length;
502        if ( node.getAttribute( "sorttable_customkey" ) !== null ) {
503            return node.getAttribute( "sorttable_customkey" );
504        } else if ( typeof node.textContent !== "undefined" && !hasInputs ) {
505            return node.textContent.replace( /^\s+|\s+$/g, '' );
506        } else if ( typeof node.innerText !== "undefined" && !hasInputs ) {
507            return node.innerText.replace( /^\s+|\s+$/g, '' );
508        } else if ( typeof node.text !== "undefined" && !hasInputs ) {
509            return node.text.replace( /^\s+|\s+$/g, '' );
510        } else {
511            switch ( node.nodeType ) {
512                case 3:
513                    return ( node.nodeName.toLowerCase() === "input" ) ? node.value.replace( /^\s+|\s+$/g, '' ) : '';
514                case 4:
515                    return node.nodeValue.replace( /^\s+|\s+$/g, '' );
516                case 1:
517                case 11:
518                    var innerText = '';
519                    for( var i = 0; i < node.childNodes.length; i++ ) {
520                        innerText += sorttable.getInnerText( node.childNodes[i] );
521                    }
522                    return innerText.replace( /^\s+|\s+$/g, '' );
523                default:
524                    return '';
525            }
526        }
527    },
528    reverse: function ( tbody, sindex ) {
529        // reverse the rows in a tbody
530        var newrows = [];
531        for( var i = sindex; i < tbody.rows.length; i++ ) {
532            newrows[newrows.length] = tbody.rows[i];
533        }
534        for( var i = newrows.length - 1; i >= 0; i-- ) {
535            tbody.appendChild( newrows[i] );
536        }
537        //delete newrows;
538    },
539    original_order: function ( tbody, isindex ) {
540        // build an array to sort. This is a Schwartzian transform thing,
541        // i.e., we "decorate" each row with the actual sort key,
542        // sort based on the sort keys, and then put the rows back in order
543        // which is a lot faster because you only do getInnerText once per row
544        var row_array = [];
545        var rows = tbody.rows;
546        var sindex = isindex;
547        for( var j = sindex; j < rows.length; j++ ) {
548            row_array[row_array.length] = [rows[j].className, rows[j]];
549        }
550        /* If you want a stable sort, uncomment the following line */
551        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
552        /* and comment out this one */
553        row_array.sort( sorttable.sort_alpha );
554
555        var tb = tbody;
556        for( var j3 = 0; j3 < row_array.length; j3++ ) {
557            tb.appendChild( row_array[j3][1] );
558        }
559
560        //delete row_array;
561    },
562    /* sort functions
563     each sort function takes two parameters, a and b
564     you are comparing a[0] and b[0] */
565    sort_ipaddr: function ( a, b ) {
566        var aa = a[0].split( ".", 4 );
567        var bb = b[0].split( ".", 4 );
568        var resulta = aa[0] * 0x1000000 + aa[1] * 0x10000 + aa[2] * 0x100 + aa[3] * 1;
569        var resultb = bb[0] * 0x1000000 + bb[1] * 0x10000 + bb[2] * 0x100 + bb[3] * 1;
570        return resulta - resultb;
571    },
572    sort_numeric: function ( a, b ) {
573        if ( a[0] === "" ) {
574            return -1;
575        }
576        if ( b[0] === "" ) {
577            return 1;
578        }
579        var aa = parseFloat( a[0].replace( /[^0-9.\-]/g, '' ) );
580        if ( isNaN( aa ) ) {
581            aa = Number.NEGATIVE_INFINITY;
582        }
583        var bb = parseFloat( b[0].replace( /[^0-9.\-]/g, '' ) );
584        if ( isNaN( bb ) ) {
585            bb = Number.NEGATIVE_INFINITY;
586        }
587        return aa - bb;
588    },
589    sort_alpha: function ( a, b ) {
590        return a[0].localeCompare( b[0] );
591    },
592    sort_ddmm: function ( a, b ) {
593        var mtch = a[0].match( sorttable.DATE_RE );
594        var y = mtch[3];
595        var m = mtch[2];
596        var d = mtch[1];
597        var t = mtch[5] + '';
598        if ( t.length < 1 ) {
599            t = '';
600        }
601        if ( m.length === 1 ) {
602            m = '0' + m;
603        }
604        if ( d.length === 1 ) {
605            d = '0' + d;
606        }
607        var dt1 = y + m + d + t;
608        mtch = b[0].match( sorttable.DATE_RE );
609        y = mtch[3];
610        m = mtch[2];
611        d = mtch[1];
612        t = mtch[5] + '';
613        if ( t.length < 1 ) {
614            t = '';
615        }
616        if ( m.length === 1 ) {
617            m = '0' + m;
618        }
619        if ( d.length === 1 ) {
620            d = '0' + d;
621        }
622        var dt2 = y + m + d + t;
623        if ( dt1 === dt2 ) {
624            return 0;
625        }
626        if ( dt1 < dt2 ) {
627            return -1;
628        }
629        return 1;
630    },
631    sort_mmdd: function ( a, b ) {
632        var mtch = a[0].match( sorttable.DATE_RE );
633        var y = mtch[3];
634        var d = mtch[2];
635        var m = mtch[1];
636        var t = mtch[5] + '';
637        if ( m.length === 1 ) {
638            m = '0' + m;
639        }
640        if ( d.length === 1 ) {
641            d = '0' + d;
642        }
643        var dt1 = y + m + d + t;
644        mtch = b[0].match( sorttable.DATE_RE );
645        y = mtch[3];
646        d = mtch[2];
647        m = mtch[1];
648        t = mtch[5] + '';
649        if ( t.length < 1 ) {
650            t = '';
651        }
652        if ( m.length === 1 ) {
653            m = '0' + m;
654        }
655        if ( d.length === 1 ) {
656            d = '0' + d;
657        }
658        var dt2 = y + m + d + t;
659        if ( dt1 === dt2 ) {
660            return 0;
661        }
662        if ( dt1 < dt2 ) {
663            return -1;
664        }
665        return 1;
666    },
667    shaker_sort: function ( list, comp_func ) {
668        // A stable sort function to allow multi-level sorting of data
669        // see: http://en.wikipedia.org/wiki/Cocktail_sort
670        // thanks to Joseph Nahmias
671        var b = 0;
672        var t = list.length - 1;
673        var swap = true;
674        var q;
675
676        while( swap ) {
677            swap = false;
678            for( var i = b; i < t; ++i ) {
679                if ( comp_func( list[i], list[i + 1] ) > 0 ) {
680                    q = list[i];
681                    list[i] = list[i + 1];
682                    list[i + 1] = q;
683                    swap = true;
684                }
685            } // for
686            t--;
687
688            if ( !swap ) {
689                break;
690            }
691
692            for( var i = t; i > b; --i ) {
693                if ( comp_func( list[i], list[i - 1] ) < 0 ) {
694                    q = list[i];
695                    list[i] = list[i - 1];
696                    list[i - 1] = q;
697                    swap = true;
698                }
699            } // for
700            b++;
701
702        } // while(swap)
703    }
704
705
706};
707
708if ( typeof ( window.addEvent ) !== "undefined" ) {
709    window.addEvent( window, "load", sorttable.init );
710} else {
711    jQuery( function () {
712        sorttable.init();
713    } );
714}
715