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 var elems = document.getElementsByTagName( "table" ); 73 var elem; 74 for( var i = 0; i < elems.length; i++ ) { 75 elem = elems[i]; 76 if ( elem.className.search( /\bsortable\b/ ) !== -1 ) { 77 sorttable.makeSortable( elem ); 78 } 79 } 80 elems = document.getElementsByTagName( "div" ); 81 for( var i = 0; i < elems.length; i++ ) { 82 elem = elems[i]; 83 if ( elem.className.search( /\bsortable\b/ ) !== -1 ) { 84 sorttable.makeSortableDiv( elem ); 85 } 86 } 87 }, 88 init: function () { 89 if ( arguments.callee.done ) { 90 return; 91 } 92 sorttable.reinit(); 93 }, 94 makeSortableDiv: function ( div ) { 95 var childTables = div.getElementsByTagName( "table" ); 96 var elem; 97 for( var i = 0; i < childTables.length; i++ ) { 98 elem = childTables[i]; 99 var colid = div.className; 100 var patt1 = /\bcol_\d_[a-z]+/gi; 101 var overs = []; 102 if ( colid.search( patt1 ) !== -1 ) { 103 var overrides = colid.match( patt1 ); 104 for( var i = 0; i < overrides.length; i++ ) { 105 var entry = overrides[i]; 106 if ( entry !== "" ) { 107 try { 108 var tmp = entry.split( "_" ); 109 var ind = tmp[1]; 110 var val = tmp[2]; 111 overs[ind] = val; 112 } catch( e ) { 113 } 114 } 115 } 116 colid = colid.replace( patt1, '' ); 117 } 118 var patt2 = /\bsortbottom_?\d?/gi; 119 var bottoms = 0; 120 if ( colid.search( patt2 ) !== -1 ) { 121 var bs = colid.match( patt2 ); 122 try { 123 var tmp = bs[0].split( "_" ); 124 //var ind = tmp[1]; 125 var val = 1; 126 if ( tmp.length > 1 ) { 127 val = tmp[1]; 128 } 129 bottoms = val; 130 } catch( e ) { 131 } 132 } 133 var patt2ph = /\bthreephase/gi; 134 var ph2 = true; 135 if ( colid.search( patt2ph ) !== -1 ) { 136 ph2 = false; 137 } 138 139 sorttable.makeSortable( elem, overs, bottoms, ph2 ); 140 var pattdefault = /\bsortr?\d\d?/gi; 141 if ( colid.search( pattdefault ) !== -1 ) { 142 var mi = colid.match( pattdefault ); 143 colid = mi[0].replace( 'sort', '' ); 144 if ( colid !== '' ) { 145 colid = colid.trim(); 146 } 147 var revs = false; 148 if ( colid.search( /\br/ ) !== -1 ) { 149 revs = true; 150 colid = colid.replace( 'r', '' ); 151 } 152 sorttable.defaultSort( elem, colid, revs ); 153 } 154 } 155 }, 156 defaultSort: function ( table, colid, revs ) { 157// theadrow = table.tHead.rows[0].cells; 158 var havetHead = table.tHead; 159 var sindex = 1; 160 if ( havetHead ) { 161 sindex = 0; 162 } 163 var theadrow = table.rows[0].cells; 164 colid--; 165 var colname = "col" + colid; 166 // remove sorttable_sorted classes 167 var thiscell = false; 168 for( var i = 0; i < theadrow.length; i++ ) { 169 var cell = theadrow[i]; 170 var colclass = cell.className; 171 var classname = colclass.split( " " ); 172// if (cell.className==colname) 173 if ( classname[0] === colname ) { 174 thiscell = cell; 175 } 176// if (cell.nodeType == 1) { // an element 177// cell.className = cell.className.replace('sorttable_sorted_reverse',''); 178// cell.className = cell.className.replace('sorttable_sorted',''); 179// } 180 } 181 if ( thiscell === false ) { 182 return; 183 } 184 var sortfwdind = document.getElementById( 'sorttable_sortfwdind' ); 185 if ( sortfwdind ) { 186 sortfwdind.parentNode.removeChild( sortfwdind ); 187 } 188 var sortrevind = document.getElementById( 'sorttable_sortrevind' ); 189 if ( sortrevind ) { 190 sortrevind.parentNode.removeChild( sortrevind ); 191 } 192 193 thiscell.className += ' sorttable_sorted'; 194 sortfwdind = document.createElement( 'span' ); 195 sortfwdind.id = "sorttable_sortfwdind"; 196 sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾'; 197 thiscell.appendChild( sortfwdind ); 198 199 // build an array to sort. This is a Schwartzian transform thing, 200 // i.e., we "decorate" each row with the actual sort key, 201 // sort based on the sort keys, and then put the rows back in order 202 // which is a lot faster because you only do getInnerText once per row 203 var row_array = []; 204 var col = thiscell.sorttable_columnindex; 205 var rows = thiscell.sorttable_tbody.rows; 206 for( var j = sindex; j < rows.length; j++ ) { 207 row_array[row_array.length] = [sorttable.getInnerText( rows[j].cells[col] ), rows[j]]; 208 } 209 /* If you want a stable sort, uncomment the following line */ 210 //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 211 /* and comment out this one */ 212 row_array.sort( thiscell.sorttable_sortfunction ); 213 214 var tb = thiscell.sorttable_tbody; 215 for( var jj = 0; jj < row_array.length; jj++ ) { 216 tb.appendChild( row_array[jj][1] ); 217 } 218 219 //delete row_array; 220 // If reverse sort wanted, then doit 221 if ( revs ) { 222 // reverse the table, which is quicker 223 sorttable.reverse( thiscell.sorttable_tbody, sindex ); 224 thiscell.className = thiscell.className.replace( 'sorttable_sorted', 225 'sorttable_sorted_reverse' ); 226 thiscell.removeChild( document.getElementById( 'sorttable_sortfwdind' ) ); 227 sortrevind = document.createElement( 'span' ); 228 sortrevind.id = "sorttable_sortrevind"; 229 sortrevind.innerHTML = stIsIE ? ' <font face="webdings">5</font>' : ' ▴'; 230 thiscell.appendChild( sortrevind ); 231 } 232 }, 233 makeSortable: function ( table, overrides, bottoms, ph2 ) { 234// tableid++; 235 /* 236 if (table.getElementsByTagName('thead').length === 0) { 237 // table doesn't have a tHead. Since it should have, create one and 238 // put the first table row in it. 239 the = document.createElement('thead'); 240 the.appendChild(table.rows[0]); 241 table.insertBefore(the,table.firstChild); 242 } 243 */ 244 // Safari doesn't support table.tHead, sigh 245 /* 246 if (table.tHead === null) {table.tHead = table.getElementsByTagName('thead')[0];} 247 248 if (table.tHead.rows.length != 1) {return;} // can't cope with two header rows 249 */ 250// table.tHead.className += ' tableid'+tableid; 251 252 // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as 253 // "total" rows, for example). This is B&R, since what you're supposed 254 // to do is put them in a tfoot. So, if there are sortbottom rows, 255 // for backwards compatibility, move them to tfoot (creating it if needed). 256 257 var sortbottomrows = []; 258 if ( bottoms > 0 ) { 259 var frombottom = table.rows.length - bottoms; 260 for( var i = table.rows.length - 1; i >= frombottom; i-- ) { 261// if (bottoms<frombottom) { 262 sortbottomrows[sortbottomrows.length] = table.rows[i]; 263// } 264// frombottom++; 265 } 266 if ( sortbottomrows ) { 267 var tfo; 268 if ( table.tFoot === null ) { 269 // table doesn't have a tfoot. Create one. 270 tfo = document.createElement( 'tfoot' ); 271 table.appendChild( tfo ); 272 } 273 for( var ii = sortbottomrows.length - 1; ii >= 0; ii-- ) { 274 tfo.appendChild( sortbottomrows[ii] ); 275 } 276 //delete sortbottomrows; 277 } 278 } 279 // work through each column and calculate its type 280 var havetHead = table.tHead; 281 var sindex = 1; 282 if ( havetHead ) { 283 sindex = 0; 284 } 285 var headrow = table.rows[0].cells; 286// for (var i=0; i<headrow.length; i++) { 287 for( var i = 0; i < headrow.length; i++ ) { 288 // manually override the type with a sorttable_type attribute 289 var colOptions = ""; 290 if ( overrides[i + 1] ) 291 { 292 colOptions = overrides[i + 1]; 293 } 294 if ( !colOptions.match( /\bnosort\b/ ) ) { // skip this col 295 var mtch = colOptions.match( /\b[a-z0-9]+\b/ ); 296 var override; 297 if ( mtch ) { 298 override = mtch[0]; 299 } 300 if ( mtch && typeof sorttable["sort_" + override] === 'function' ) { 301 headrow[i].sorttable_sortfunction = sorttable["sort_" + override]; 302 } else { 303 headrow[i].sorttable_sortfunction = sorttable.guessType( table, i ); 304 } 305 /* 306 if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col 307 mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/); 308 if (mtch) { override = mtch[1]; } 309 if (mtch && typeof sorttable["sort_"+override] == 'function') { 310 headrow[i].sorttable_sortfunction = sorttable["sort_"+override]; 311 } else { 312 headrow[i].sorttable_sortfunction = sorttable.guessType(table,i); 313 } 314 */ 315 // make it clickable to sort 316 headrow[i].sorttable_columnindex = i; 317 headrow[i].sorttable_tbody = table.tBodies[0]; 318 headrow[i].sindex = sindex; 319// dean_addEvent(headrow[i],"click", function(e) { 320// addEvent(headrow[i],"click", function(e) { 321 jQuery( headrow[i] ).click( function () { 322 323 var theadrow = this.parentNode; 324 var sortrevind, sortfwdind; 325 if ( this.className.search( /\bsorttable_sorted\b/ ) !== -1 ) { 326 // if we're already sorted by this column, just 327 // reverse the table, which is quicker 328 sorttable.reverse( this.sorttable_tbody, this.sindex ); 329 this.className = this.className.replace( 'sorttable_sorted', 330 'sorttable_sorted_reverse' ); 331 sortfwdind = document.getElementById( 'sorttable_sortfwdind' ); 332 if ( sortfwdind ) { 333 sortfwdind.parentNode.removeChild( sortfwdind ); 334 } 335// this.removeChild(document.getElementById('sorttable_sortfwdind')); 336 sortrevind = document.getElementById( 'sorttable_sortrevind' ); 337 if ( sortrevind ) { 338 sortrevind.parentNode.removeChild( sortrevind ); 339 } 340 sortrevind = document.createElement( 'span' ); 341 sortrevind.id = "sorttable_sortrevind"; 342 sortrevind.innerHTML = stIsIE ? ' <font face="webdings">5</font>' : ' ▴'; 343 this.appendChild( sortrevind ); 344 return; 345 } 346 if ( this.className.search( /\bsorttable_sorted_reverse\b/ ) !== -1 ) { 347 if ( !ph2 ) { 348 sorttable.original_order( this.sorttable_tbody, this.sindex ); 349 var list = theadrow.childNodes; 350 for( var i = 0; i < list.length; i++ ) { 351 var cell = list[i]; 352 if ( cell.nodeType === 1 ) { // an element 353 cell.className = cell.className.replace( 'sorttable_sorted_reverse', '' ); 354 cell.className = cell.className.replace( 'sorttable_sorted', '' ); 355 } 356 } 357 sortfwdind = document.getElementById( 'sorttable_sortfwdind' ); 358 if ( sortfwdind ) { 359 sortfwdind.parentNode.removeChild( sortfwdind ); 360 } 361 sortrevind = document.getElementById( 'sorttable_sortrevind' ); 362 if ( sortrevind ) { 363 sortrevind.parentNode.removeChild( sortrevind ); 364 } 365 return; 366 } else { 367 // if we're already sorted by this column in reverse, just 368 // re-reverse the table, which is quicker 369 sorttable.reverse( this.sorttable_tbody, this.sindex ); 370 this.className = this.className.replace( 'sorttable_sorted_reverse', 371 'sorttable_sorted' ); 372 sortrevind = document.getElementById( 'sorttable_sortrevind' ); 373 if ( sortrevind ) { 374 sortrevind.parentNode.removeChild( sortrevind ); 375 } 376 // this.removeChild(document.getElementById('sorttable_sortrevind')); 377 sortfwdind = document.getElementById( 'sorttable_sortfwdind' ); 378 if ( sortfwdind ) { 379 sortfwdind.parentNode.removeChild( sortfwdind ); 380 } 381 sortfwdind = document.createElement( 'span' ); 382 sortfwdind.id = "sorttable_sortfwdind"; 383 sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾'; 384 this.appendChild( sortfwdind ); 385 return; 386 } 387 } 388 389 // remove sorttable_sorted classes 390// theadrow = this.parentNode; 391 var list = theadrow.childNodes; 392 for( var i = 0; i < list.length; i++ ) { 393 var cell = list[i]; 394 if ( cell.nodeType === 1 ) { // an element 395 cell.className = cell.className.replace( 'sorttable_sorted_reverse', '' ); 396 cell.className = cell.className.replace( 'sorttable_sorted', '' ); 397 } 398 } 399 sortfwdind = document.getElementById( 'sorttable_sortfwdind' ); 400 if ( sortfwdind ) { 401 sortfwdind.parentNode.removeChild( sortfwdind ); 402 } 403 sortrevind = document.getElementById( 'sorttable_sortrevind' ); 404 if ( sortrevind ) { 405 sortrevind.parentNode.removeChild( sortrevind ); 406 } 407 408 this.className += ' sorttable_sorted'; 409 sortfwdind = document.createElement( 'span' ); 410 sortfwdind.id = "sorttable_sortfwdind"; 411 sortfwdind.innerHTML = stIsIE ? ' <font face="webdings">6</font>' : ' ▾'; 412 this.appendChild( sortfwdind ); 413 414 // build an array to sort. This is a Schwartzian transform thing, 415 // i.e., we "decorate" each row with the actual sort key, 416 // sort based on the sort keys, and then put the rows back in order 417 // which is a lot faster because you only do getInnerText once per row 418 var row_array = []; 419 var col = this.sorttable_columnindex; 420 var rows = this.sorttable_tbody.rows; 421 sindex = this.sindex; 422 for( var j = sindex; j < rows.length; j++ ) { 423 row_array[row_array.length] = [sorttable.getInnerText( rows[j].cells[col] ), rows[j]]; 424 } 425 /* If you want a stable sort, uncomment the following line */ 426 //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 427 /* and comment out this one */ 428 row_array.sort( this.sorttable_sortfunction ); 429 430 var tb = this.sorttable_tbody; 431 for( var j3 = 0; j3 < row_array.length; j3++ ) { 432 tb.appendChild( row_array[j3][1] ); 433 } 434 435 //delete row_array; 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 textCnt = 0; 443 var numCnt = 0; 444 var dateCnt = 0; 445 var ipCnt = 0; 446 447 for( var i = 0; i < table.tBodies[0].rows.length; i++ ) { 448 var text = sorttable.getInnerText( table.tBodies[0].rows[i].cells[column] ); 449 if ( text !== "" ) { 450 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])$/ ) ) { 451 ipCnt++; 452 } else if ( text.match( /^[\-\+]?.?\d*[\d,.]?\d+.?$/ ) ) { 453 numCnt++; 454 } else if ( !isNaN( new Date( text ).getTime() ) ) { 455 dateCnt++; 456 } else { // not a date (nor IP nor number) 457 textCnt++; 458 } 459 } 460 } 461 if ( textCnt > numCnt && textCnt > ipCnt && textCnt > dateCnt ) 462 return sorttable.sort_alpha; 463 if ( numCnt > ipCnt && numCnt > dateCnt ) 464 return sorttable.sort_numeric; 465 if ( ipCnt > dateCnt ) 466 return sorttable.sort_ipaddr; 467 return sorttable.sort_date; 468 }, 469 getInnerText: function ( node ) { 470 // gets the text we want to use for sorting for a cell. 471 // strips leading and trailing whitespace. 472 // this is *not* a generic getInnerText function; it's special to sorttable. 473 // for example, you can override the cell text with a customkey attribute. 474 // it also gets .value for <input> fields. 475 if ( !node ) { 476 return ''; 477 } 478 var hasInputs = ( typeof node.getElementsByTagName === "function" ) && 479 node.getElementsByTagName( "input" ).length; 480 if ( node.getAttribute( "sorttable_customkey" ) !== null ) { 481 return node.getAttribute( "sorttable_customkey" ); 482 } else if ( typeof node.textContent !== "undefined" && !hasInputs ) { 483 return node.textContent.replace( /^\s+|\s+$/g, '' ); 484 } else if ( typeof node.innerText !== "undefined" && !hasInputs ) { 485 return node.innerText.replace( /^\s+|\s+$/g, '' ); 486 } else if ( typeof node.text !== "undefined" && !hasInputs ) { 487 return node.text.replace( /^\s+|\s+$/g, '' ); 488 } else { 489 switch ( node.nodeType ) { 490 case 3: 491 return ( node.nodeName.toLowerCase() === "input" ) ? node.value.replace( /^\s+|\s+$/g, '' ) : ''; 492 case 4: 493 return node.nodeValue.replace( /^\s+|\s+$/g, '' ); 494 case 1: 495 case 11: 496 var innerText = ''; 497 for( var i = 0; i < node.childNodes.length; i++ ) { 498 innerText += sorttable.getInnerText( node.childNodes[i] ); 499 } 500 return innerText.replace( /^\s+|\s+$/g, '' ); 501 default: 502 return ''; 503 } 504 } 505 }, 506 reverse: function ( tbody, sindex ) { 507 // reverse the rows in a tbody 508 var newrows = []; 509 for( var i = sindex; i < tbody.rows.length; i++ ) { 510 newrows[newrows.length] = tbody.rows[i]; 511 } 512 for( var i = newrows.length - 1; i >= 0; i-- ) { 513 tbody.appendChild( newrows[i] ); 514 } 515 //delete newrows; 516 }, 517 original_order: function ( tbody, isindex ) { 518 // build an array to sort. This is a Schwartzian transform thing, 519 // i.e., we "decorate" each row with the actual sort key, 520 // sort based on the sort keys, and then put the rows back in order 521 // which is a lot faster because you only do getInnerText once per row 522 var row_array = []; 523 var rows = tbody.rows; 524 var sindex = isindex; 525 for( var j = sindex; j < rows.length; j++ ) { 526 row_array[row_array.length] = [rows[j].className, rows[j]]; 527 } 528 /* If you want a stable sort, uncomment the following line */ 529 //sorttable.shaker_sort(row_array, this.sorttable_sortfunction); 530 /* and comment out this one */ 531 row_array.sort( sorttable.sort_alpha ); 532 533 var tb = tbody; 534 for( var j3 = 0; j3 < row_array.length; j3++ ) { 535 tb.appendChild( row_array[j3][1] ); 536 } 537 538 //delete row_array; 539 }, 540 /* sort functions 541 each sort function takes two parameters, a and b 542 you are comparing a[0] and b[0] */ 543 sort_ipaddr: function ( a, b ) { 544 var aa = a[0].split( ".", 4 ); 545 var bb = b[0].split( ".", 4 ); 546 var resulta = aa[0] * 0x1000000 + aa[1] * 0x10000 + aa[2] * 0x100 + aa[3] * 1; 547 var resultb = bb[0] * 0x1000000 + bb[1] * 0x10000 + bb[2] * 0x100 + bb[3] * 1; 548 return resulta - resultb; 549 }, 550 sort_numeric: function ( a, b ) { 551 if ( a[0] === "" ) { 552 return -1; 553 } 554 if ( b[0] === "" ) { 555 return 1; 556 } 557 var aa = parseFloat( a[0].replace( ",", "." ).replace( /[^0-9.\-]/g, "" ) ); 558 if ( isNaN( aa ) ) { 559 aa = Number.NEGATIVE_INFINITY; 560 } 561 var bb = parseFloat( b[0].replace( ",", "." ).replace( /[^0-9.\-]/g, "" ) ); 562 if ( isNaN( bb ) ) { 563 bb = Number.NEGATIVE_INFINITY; 564 } 565 return aa - bb; 566 }, 567 sort_alpha: function ( a, b ) { 568 return a[0].localeCompare( b[0] ); 569 }, 570 sort_date: function ( a, b ) { 571 var aa = new Date( a[0] ), bb = new Date( b[0] ); 572 return ( aa > bb ) - ( aa < bb ); 573 }, 574 shaker_sort: function ( list, comp_func ) { 575 // A stable sort function to allow multi-level sorting of data 576 // see: http://en.wikipedia.org/wiki/Cocktail_sort 577 // thanks to Joseph Nahmias 578 var b = 0; 579 var t = list.length - 1; 580 var swap = true; 581 var q; 582 583 while( swap ) { 584 swap = false; 585 for( var i = b; i < t; ++i ) { 586 if ( comp_func( list[i], list[i + 1] ) > 0 ) { 587 q = list[i]; 588 list[i] = list[i + 1]; 589 list[i + 1] = q; 590 swap = true; 591 } 592 } // for 593 t--; 594 595 if ( !swap ) { 596 break; 597 } 598 599 for( var i = t; i > b; --i ) { 600 if ( comp_func( list[i], list[i - 1] ) < 0 ) { 601 q = list[i]; 602 list[i] = list[i - 1]; 603 list[i - 1] = q; 604 swap = true; 605 } 606 } // for 607 b++; 608 609 } // while(swap) 610 } 611 612}; 613 614if ( typeof ( window.addEvent ) !== "undefined" ) { 615 window.addEvent( window, "load", sorttable.init ); 616} else { 617 jQuery( function () { 618 sorttable.init(); 619 } ); 620} 621