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