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