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 v2.14 57 * by Vaxquis: fixed and streamlined date sorts; it now uses the JS Date() Object to do the sorting. 58 */ 59 60// 61 62var stIsIE = /*@cc_on!@*/false; 63//var tableid = 0; 64 65var sorttable = { 66 reinit: function () { 67 arguments.callee.done = true; 68 // kill the timer 69 //if (_timer) {clearInterval(_timer);} 70 71 if ( !document.createElement || !document.getElementsByTagName ) { 72 return; 73 } 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 ? ' <font face="webdings">6</font>' : ' ▾'; 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 ? ' <font face="webdings">5</font>' : ' ▴'; 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 ? ' <font face="webdings">5</font>' : ' ▴'; 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 ? ' <font face="webdings">6</font>' : ' ▾'; 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 ? ' <font face="webdings">6</font>' : ' ▾'; 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 dateCnt = 0; 448 var ipCnt = 0; 449 450 for( var i = 0; i < table.tBodies[0].rows.length; i++ ) { 451 var text = sorttable.getInnerText( table.tBodies[0].rows[i].cells[column] ); 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])$/ ) ) { 454 ipCnt++; 455 } else if ( text.match( /^[\-\+].?[\d,.]+.?$/ ) ) { 456 numCnt++; 457 } else if ( isNaN( new Date( text ).getTime() ) ) { 458 dateCnt++; 459 } else { // not a date (nor IP nor number) 460 textCnt++; 461 } 462 } 463 } 464 if ( textCnt > numCnt && textCnt > ipCnt && textCnt > dateCnt ) 465 return sorttable.sort_alpha; 466 if ( numCnt > ipCnt && numCnt > dateCnt ) 467 return sorttable.sort_numeric; 468 if ( ipCnt > dateCnt ) 469 return sorttable.sort_ipaddr; 470 return sorttable.sort_date; 471 }, 472 getInnerText: function ( node ) { 473 // gets the text we want to use for sorting for a cell. 474 // strips leading and trailing whitespace. 475 // this is *not* a generic getInnerText function; it's special to sorttable. 476 // for example, you can override the cell text with a customkey attribute. 477 // it also gets .value for <input> fields. 478 if ( !node ) { 479 return ''; 480 } 481 var hasInputs = ( typeof node.getElementsByTagName === "function" ) && 482 node.getElementsByTagName( "input" ).length; 483 if ( node.getAttribute( "sorttable_customkey" ) !== null ) { 484 return node.getAttribute( "sorttable_customkey" ); 485 } else if ( typeof node.textContent !== "undefined" && !hasInputs ) { 486 return node.textContent.replace( /^\s+|\s+$/g, '' ); 487 } else if ( typeof node.innerText !== "undefined" && !hasInputs ) { 488 return node.innerText.replace( /^\s+|\s+$/g, '' ); 489 } else if ( typeof node.text !== "undefined" && !hasInputs ) { 490 return node.text.replace( /^\s+|\s+$/g, '' ); 491 } else { 492 switch ( node.nodeType ) { 493 case 3: 494 return ( node.nodeName.toLowerCase() === "input" ) ? node.value.replace( /^\s+|\s+$/g, '' ) : ''; 495 case 4: 496 return node.nodeValue.replace( /^\s+|\s+$/g, '' ); 497 case 1: 498 case 11: 499 var innerText = ''; 500 for( var i = 0; i < node.childNodes.length; i++ ) { 501 innerText += sorttable.getInnerText( node.childNodes[i] ); 502 } 503 return innerText.replace( /^\s+|\s+$/g, '' ); 504 default: 505 return ''; 506 } 507 } 508 }, 509 reverse: function ( tbody, sindex ) { 510 // reverse the rows in a tbody 511 var newrows = []; 512 for( var i = sindex; i < tbody.rows.length; i++ ) { 513 newrows[newrows.length] = tbody.rows[i]; 514 } 515 for( var i = newrows.length - 1; i >= 0; i-- ) { 516 tbody.appendChild( newrows[i] ); 517 } 518 //delete newrows; 519 }, 520 original_order: function ( tbody, isindex ) { 521 // build an array to sort. This is a Schwartzian transform thing, 522 // i.e., we "decorate" each row with the actual sort key, 523 // sort based on the sort keys, and then put the rows back in order 524 // which is a lot faster because you only do getInnerText once per row 525 var row_array = []; 526 var rows = tbody.rows; 527 var sindex = isindex; 528 for( var j = sindex; j < rows.length; j++ ) { 529 row_array[row_array.length] = [rows[j].className, rows[j]]; 530 } 531 /* If you want a stable sort, uncomment the following line */ 532 //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 533 /* and comment out this one */ 534 row_array.sort( sorttable.sort_alpha ); 535 536 var tb = tbody; 537 for( var j3 = 0; j3 < row_array.length; j3++ ) { 538 tb.appendChild( row_array[j3][1] ); 539 } 540 541 //delete row_array; 542 }, 543 /* sort functions 544 each sort function takes two parameters, a and b 545 you are comparing a[0] and b[0] */ 546 sort_ipaddr: function ( a, b ) { 547 var aa = a[0].split( ".", 4 ); 548 var bb = b[0].split( ".", 4 ); 549 var resulta = aa[0] * 0x1000000 + aa[1] * 0x10000 + aa[2] * 0x100 + aa[3] * 1; 550 var resultb = bb[0] * 0x1000000 + bb[1] * 0x10000 + bb[2] * 0x100 + bb[3] * 1; 551 return resulta - resultb; 552 }, 553 sort_numeric: function ( a, b ) { 554 if ( a[0] === "" ) { 555 return -1; 556 } 557 if ( b[0] === "" ) { 558 return 1; 559 } 560 var aa = parseFloat( a[0].replace( /[^0-9.\-]/g, '' ) ); 561 if ( isNaN( aa ) ) { 562 aa = Number.NEGATIVE_INFINITY; 563 } 564 var bb = parseFloat( b[0].replace( /[^0-9.\-]/g, '' ) ); 565 if ( isNaN( bb ) ) { 566 bb = Number.NEGATIVE_INFINITY; 567 } 568 return aa - bb; 569 }, 570 sort_alpha: function ( a, b ) { 571 return a[0].localeCompare( b[0] ); 572 }, 573 sort_date: function ( a, b ) { 574 var aa = new Date( a[0] ), bb = new Date( b[0] ); 575 return ( aa > bb ) - ( aa < bb ); 576 }, 577 shaker_sort: function ( list, comp_func ) { 578 // A stable sort function to allow multi-level sorting of data 579 // see: http://en.wikipedia.org/wiki/Cocktail_sort 580 // thanks to Joseph Nahmias 581 var b = 0; 582 var t = list.length - 1; 583 var swap = true; 584 var q; 585 586 while( swap ) { 587 swap = false; 588 for( var i = b; i < t; ++i ) { 589 if ( comp_func( list[i], list[i + 1] ) > 0 ) { 590 q = list[i]; 591 list[i] = list[i + 1]; 592 list[i + 1] = q; 593 swap = true; 594 } 595 } // for 596 t--; 597 598 if ( !swap ) { 599 break; 600 } 601 602 for( var i = t; i > b; --i ) { 603 if ( comp_func( list[i], list[i - 1] ) < 0 ) { 604 q = list[i]; 605 list[i] = list[i - 1]; 606 list[i - 1] = q; 607 swap = true; 608 } 609 } // for 610 b++; 611 612 } // while(swap) 613 } 614 615}; 616 617if ( typeof ( window.addEvent ) !== "undefined" ) { 618 window.addEvent( window, "load", sorttable.init ); 619} else { 620 jQuery( function () { 621 sorttable.init(); 622 } ); 623} 624