1/** 2 * Copyright (c)2005-2009 Matt Kruse (javascripttoolbox.com) 3 * 4 * Dual licensed under the MIT and GPL licenses. 5 * This basically means you can use this code however you want for 6 * free, but don't claim to have written it yourself! 7 * Donations always accepted: http://www.JavascriptToolbox.com/donate/ 8 * 9 * Please do not link to the .js files on javascripttoolbox.com from 10 * your site. Copy the files locally to your server instead. 11 * 12 */ 13/** 14 * Table.js 15 * Functions for interactive Tables 16 * 17 * Copyright (c) 2007 Matt Kruse (javascripttoolbox.com) 18 * Dual licensed under the MIT and GPL licenses. 19 * 20 * @version 0.981 21 * 22 * @history 0.981 2007-03-19 Added Sort.numeric_comma, additional date parsing formats 23 * @history 0.980 2007-03-18 Release new BETA release pending some testing. Todo: Additional docs, examples, plus jQuery plugin. 24 * @history 0.959 2007-03-05 Added more "auto" functionality, couple bug fixes 25 * @history 0.958 2007-02-28 Added auto functionality based on class names 26 * @history 0.957 2007-02-21 Speed increases, more code cleanup, added Auto Sort functionality 27 * @history 0.956 2007-02-16 Cleaned up the code and added Auto Filter functionality. 28 * @history 0.950 2006-11-15 First BETA release. 29 * 30 * @todo Add more date format parsers 31 * @todo Add style classes to colgroup tags after sorting/filtering in case the user wants to highlight the whole column 32 * @todo Correct for colspans in data rows (this may slow it down) 33 * @todo Fix for IE losing form control values after sort? 34 */ 35 36/** 37 * Sort Functions 38 */ 39var Sort = (function(){ 40 var sort = {}; 41 // Default alpha-numeric sort 42 // -------------------------- 43 sort.alphanumeric = function(a,b) { 44 return (a==b)?0:(a<b)?-1:1; 45 }; 46 sort['default'] = sort.alphanumeric; // IE chokes on sort.default 47 48 // This conversion is generalized to work for either a decimal separator of , or . 49 sort.numeric_converter = function(separator) { 50 return function(val) { 51 if (typeof(val)=="string") { 52 val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g,"$1").replace(new RegExp("[^\\\d"+separator+"]","g"),'').replace(/,/,'.')) || 0; 53 } 54 return val || 0; 55 }; 56 }; 57 58 // Numeric Sort 59 // ------------ 60 sort.numeric = function (a, b) { 61 return sort.numeric.convert(a)-sort.numeric.convert(b); 62 }; 63 sort.numeric.convert = sort.numeric_converter("."); 64 65 // Numeric Sort - comma decimal separator 66 // -------------------------------------- 67 sort.numeric_comma = function(a,b) { 68 return sort.numeric_comma.convert(a)-sort.numeric_comma.convert(b); 69 }; 70 sort.numeric_comma.convert = sort.numeric_converter(","); 71 72 // Case-insensitive Sort 73 // --------------------- 74 sort.ignorecase = function(a,b) { 75 return sort.alphanumeric(sort.ignorecase.convert(a),sort.ignorecase.convert(b)); 76 }; 77 sort.ignorecase.convert = function(val) { 78 if (val==null) { return ""; } 79 return (""+val).toLowerCase(); 80 }; 81 82 83 sort.duration = function (a, b) { 84 return sort.numeric(sort.duration.convert(a), sort.duration.convert(b)); 85 }; 86 87 sort.duration.convert = function (val) { 88 // convert duration text into number of minutes 89 if (val == null) 90 return 9999999; 91 92 val = $.trim(val); 93 94 var dayInd = val.indexOf("days"); 95 var res = 0; 96 if (dayInd > -1) { 97 res += parseInt(val.subStr(0, dayInd), 10) * 480; 98 return res; 99 } 100 var timeArray = val.split(" ", 3); 101 102 for (var i = 0; i < timeArray.length; i++) { 103 var st = timeArray[i]; 104 var hrsInd = st.indexOf("hrs"); 105 if (hrsInd != -1) { 106 var s = st.slice(0, hrsInd); 107 res += parseInt(s, 10) * 60; 108 } else { 109 var minInd = st.indexOf("min"); 110 if (minInd != -1) { 111 var tmp = st.slice(0, minInd); 112 res += parseInt(tmp, 10); 113 } 114 } 115 } 116 return res; 117 }; 118 // Currency Sort 119 // ------------- 120 sort.currency = sort.numeric; // Just treat it as numeric! 121 sort.currency_comma = sort.numeric_comma; 122 123 // Date sort 124 // --------- 125 sort.date = function(a,b) { 126 return sort.numeric(sort.date.convert(a),sort.date.convert(b)); 127 }; 128 // Convert 2-digit years to 4 129 sort.date.fixYear=function(yr) { 130 yr = +yr; 131 if (yr<50) { yr += 2000; } 132 else if (yr<100) { yr += 1900; } 133 return yr; 134 }; 135 sort.date.formats = [ 136 // YY[YY]-MM-DD 137 { re:/(\d{2,4})-(\d{1,2})-(\d{1,2})/ , f:function(x){ return (new Date(sort.date.fixYear(x[1]),+x[2],+x[3])).getTime(); } } 138 // MM/DD/YY[YY] or MM-DD-YY[YY] 139 ,{ re:/(\d{1,2}
139)[\/-](\d{1,2})[\/-](\d{2,4})/ , f:function(x){ return (new Date(sort.date.fixYear(x[3]),+x[1],+x[2])).getTime(); } } 140 // Any catch-all format that new Date() can handle. This is not reliable except for long formats, for example: 31 Jan 2000 01:23:45 GMT 141 ,{ re:/(.*\d{4}.*\d+:\d+\d+.*)/, f:function(x){ var d=new Date(x[1]); if(d){return d.getTime();} } } 142 ]; 143 sort.date.convert = function(val) { 144 var m,v, f = sort.date.formats; 145 for (var i=0,L=f.length; i<L; i++) { 146 if (m=val.match(f[i].re)) { 147 v=f[i].f(m); 148 if (typeof(v)!="undefined") { return v; } 149 } 150 } 151 return 9999999999999; // So non-parsed dates will be last, not first 152 }; 153 154 return sort; 155})(); 156 157/** 158 * The main Table namespace 159 */ 160var Table = (function(){ 161 162 /** 163 * Determine if a reference is defined 164 */ 165 function def(o) {return (typeof o!="undefined");}; 166 167 /** 168 * Determine if an object or class string contains a given class. 169 */ 170 function hasClass(o,name) { 171 return new RegExp("(^|\\s)"+name+"(\\s|$)").test(o.className); 172 }; 173 174 /** 175 * Add a class to an object 176 */ 177 function addClass(o,name) { 178 var c = o.className || ""; 179 if (def(c) && !hasClass(o,name)) { 180 o.className += (c?" ":"") + name; 181 } 182 }; 183 184 /** 185 * Remove a class from an object 186 */ 187 function removeClass(o,name) { 188 var c = o.className || ""; 189 o.className = c.replace(new RegExp("(^|\\s)"+name+"(\\s|$)"),"$1"); 190 }; 191 192 /** 193 * For classes that match a given substring, return the rest 194 */ 195 function classValue(o,prefix) { 196 var c = o.className; 197 if (c.match(new RegExp("(^|\\s)"+prefix+"([^ ]+)"))) { 198 return RegExp.$2; 199 } 200 return null; 201 }; 202 203 /** 204 * Return true if an object is hidden. 205 * This uses the "russian doll" technique to unwrap itself to the most efficient 206 * function after the first pass. This avoids repeated feature detection that 207 * would always fall into the same block of code. 208 */ 209 function isHidden(o) { 210 if (window.getComputedStyle) { 211 var cs = window.getComputedStyle; 212 return (isHidden = function(o) { 213 return 'none'==cs(o,null).getPropertyValue('display'); 214 })(o); 215 } 216 else if (window.currentStyle) { 217 return(isHidden = function(o) { 218 return 'none'==o.currentStyle['display']; 219 })(o); 220 } 221 return (isHidden = function(o) { 222 return 'none'==o.style['display']; 223 })(o); 224 }; 225 226 /** 227 * Get a parent element by tag name, or the original element if it is of the tag type 228 */ 229 function getParent(o,a,b) { 230 if (o!=null && o.nodeName) { 231 if (o.nodeName==a || (b && o.nodeName==b)) { 232 return o; 233 } 234 while (o=o.parentNode) { 235 if (o.nodeName && (o.nodeName==a || (b && o.nodeName==b))) { 236 return o; 237 } 238 } 239 } 240 return null; 241 }; 242 243 /** 244 * Utility function to copy properties from one object to another 245 */ 246 function copy(o1,o2) { 247 for (var i=2;i<arguments.length; i++) { 248 var a = arguments[i]; 249 if (def(o1[a])) { 250 o2[a] = o1[a]; 251 } 252 } 253 } 254 255 // The table object itself 256 var table = { 257 //Class names used in the code 258 AutoStripeClassName:"table-autostripe", 259 StripeClassNamePrefix:"table-stripeclass:", 260 261 AutoSortClassName:"table-autosort", 262 AutoSortColumnPrefix:"table-autosort:", 263 AutoSortTitle:"Click to sort", 264 SortedAscendingClassName:"table-sorted-asc", 265 SortedDescendingClassName:"table-sorted-desc", 266 SortableClassName:"table-sortable", 267 SortableColumnPrefix:"table-sortable:", 268 NoSortClassName:"table-nosort", 269 270 AutoFilterClassName:"table-autofilter", 271 FilteredClassName:"table-filtered", 272 FilterableClassName:"table-filterable", 273 FilteredRowcountPrefix:"table-filtered-rowcount:", 274 RowcountPrefix:"table-rowcount:", 275 FilterAllLabel:"Filter: All", 276 277 AutoPageSizePrefix:"table-autopage:", 278 AutoPageJumpPrefix:"table-page:", 279 PageNumberPrefix:"table-page-number:", 280 PageCountPrefix:"table-page-count:" 281 }; 282 283 /** 284 * A place to store misc table information, rather than in the table objects themselves 285 */ 286 table.tabledata = {}; 287 288 /** 289 * Resolve a table given an element reference, and make sure it has a unique ID 290 */ 291 table.uniqueId=1; 292 table.resolve = function(o,args) { 293 if (o!=null && o.nodeName && o.nodeName!="TABLE") { 294 o = getParent(o,"TABLE"); 295 } 296 if (o==null) { return null; } 297 if (!o.id) { 298 var id = null; 299 do { var id = "TABLE_"+(table.uniqueId++); } 300 while (document.getElementById(id)!=null); 301 o.id = id; 302 } 303 this.tabledata[o.id] = this.tabledata[o.id] || {}; 304 if (args) { 305 copy(args,this.tabledata[o.id],"stripeclass","ignorehiddenrows","useinnertext","sorttype","col","desc","page","pagesize"); 306 } 307 return o; 308 }; 309 310 311 /** 312 * Run a function against each cell in a table header or footer, usually 313 * to add or remove css classes based on sorting, filtering, etc. 314 */ 315 table.processTableCells = function(t, type, func, arg) { 316 t = this.resolve(t); 317 if (t==null) { return; } 318 if (type!="TFOOT") { 319 this.processCells(t.tHead, func, arg); 320 } 321 if (type!="THEAD") { 322 this.processCells(t.tFoot, func, arg); 323 } 324 }; 325 326 /** 327 * Internal method used to process an arbitrary collection of cells. 328 * Referenced by processTableCells.
329 * It's done this way to avoid getElementsByTagName() which would also return nested table cells. 330 */ 331 table.processCells = function(section,func,arg) { 332 if (section!=null) { 333 if (section.rows && section.rows.length && section.rows.length>0) { 334 var rows = section.rows; 335 for (var j=0,L2=rows.length; j<L2; j++) { 336 var row = rows[j]; 337 if (row.cells && row.cells.length && row.cells.length>0) { 338 var cells = row.cells; 339 for (var k=0,L3=cells.length; k<L3; k++) { 340 var cellsK = cells[k]; 341 func.call(this,cellsK,arg); 342 } 343 } 344 } 345 } 346 } 347 }; 348 349 /** 350 * Get the cellIndex value for a cell. This is only needed because of a Safari 351 * bug that causes cellIndex to exist but always be 0. 352 * Rather than feature-detecting each time it is called, the function will 353 * re-write itself the first time it is called. 354 */ 355 table.getCellIndex = function(td) { 356 var tr = td.parentNode; 357 var cells = tr.cells; 358 if (cells && cells.length) { 359 if (cells.length>1 && cells[cells.length-1].cellIndex>0) { 360 // Define the new function, overwrite the one we're running now, and then run the new one 361 (this.getCellIndex = function(td) { 362 return td.cellIndex; 363 })(td); 364 } 365 // Safari will always go through this slower block every time. Oh well. 366 for (var i=0,L=cells.length; i<L; i++) { 367 if (tr.cells[i]==td) { 368 return i; 369 } 370 } 371 } 372 return 0; 373 }; 374 375 /** 376 * A map of node names and how to convert them into their "value" for sorting, filtering, etc. 377 * These are put here so it is extensible. 378 */ 379 table.nodeValue = { 380 'INPUT':function(node) { 381 if (def(node.value) && node.type && ((node.type!="checkbox" && node.type!="radio") || node.checked)) { 382 return node.value; 383 } 384 return ""; 385 }, 386 'SELECT':function(node) { 387 if (node.selectedIndex>=0 && node.options) { 388 // Sort select elements by the visible text 389 return node.options[node.selectedIndex].text; 390 } 391 return ""; 392 }, 393 'IMG': function (node) { 394 return node.name || ""; 395 } 396 }; 397 398 /** 399 * Get the text value of a cell. Only use innerText if explicitly told to, because 400 * otherwise we want to be able to handle sorting on inputs and other types 401 */ 402 table.getCellValue = function(td,useInnerText) { 403 if (useInnerText && def(td.innerText)) { 404 return td.innerText; 405 } 406 if (!td.childNodes) { 407 return ""; 408 } 409 var childNodes=td.childNodes; 410 var ret = ""; 411 for (var i=0,L=childNodes.length; i<L; i++) { 412 var node = childNodes[i]; 413 var type = node.nodeType; 414 // In order to get realistic sort results, we need to treat some elements in a special way. 415 // These behaviors are defined in the nodeValue() object, keyed by node name 416 if (type==1) { 417 var nname = node.nodeName; 418 if (this.nodeValue[nname]) { 419 ret += this.nodeValue[nname](node); 420 } 421 else { 422 ret += this.getCellValue(node); 423 } 424 } 425 else if (type==3) { 426 if (def(node.innerText)) { 427 ret += node.innerText; 428 } 429 else if (def(node.nodeValue)) { 430 ret += node.nodeValue; 431 } 432 } 433 } 434 return ret; 435 }; 436 437 /** 438 * Consider colspan and rowspan values in table header cells to calculate the actual cellIndex 439 * of a given cell. This is necessary because if the first cell in row 0 has a rowspan of 2, 440 * then the first cell in row 1 will have a cellIndex of 0 rather than 1, even though it really 441 * starts in the second column rather than the first. 442 * See: http://www.javascripttoolbox.com/temp/table_cellindex.html 443 */ 444 table.tableHeaderIndexes = {}; 445 table.getActualCellIndex = function(tableCellObj) { 446 if (!def(tableCellObj.cellIndex)) { return null; } 447 var tableObj = getParent(tableCellObj,"TABLE"); 448 var cellCoordinates = tableCellObj.parentNode.rowIndex+"-"+this.getCellIndex(tableCellObj); 449 450 // If it has already been computed, return the answer from the lookup table 451 if (def(this.tableHeaderIndexes[tableObj.id])) { 452 return this.tableHeaderIndexes[tableObj.id][cellCoordinates]; 453 } 454 455 var matrix = []; 456 this.tableHeaderIndexes[tableObj.id] = {}; 457 var thead = getParent(tableCellObj,"THEAD"); 458 var trs = thead.getElementsByTagName('TR'); 459 460 // Loop thru every tr and every cell in the tr, building up a 2-d array "grid" that gets 461 // populated with an "x" for each space that a cell takes up. If the first cell is colspan 462 // 2, it will fill in values [0] and [1] in the first array, so that the second cell will 463 // find the first empty cell in the first row (which will be [2]) and know that this is
464 // where it sits, rather than its internal .cellIndex value of [1]. 465 for (var i=0; i<trs.length; i++) { 466 var cells = trs[i].cells; 467 for (var j=0; j<cells.length; j++) { 468 var c = cells[j]; 469 var rowIndex = c.parentNode.rowIndex; 470 var cellId = rowIndex+"-"+this.getCellIndex(c); 471 var rowSpan = c.rowSpan || 1; 472 var colSpan = c.colSpan || 1; 473 var firstAvailCol; 474 if(!def(matrix[rowIndex])) { 475 matrix[rowIndex] = []; 476 } 477 var m = matrix[rowIndex]; 478 // Find first available column in the first row 479 for (var k=0; k<m.length+1; k++) { 480 if (!def(m[k])) { 481 firstAvailCol = k; 482 break; 483 } 484 } 485 this.tableHeaderIndexes[tableObj.id][cellId] = firstAvailCol; 486 for (var k=rowIndex; k<rowIndex+rowSpan; k++) { 487 if(!def(matrix[k])) { 488 matrix[k] = []; 489 } 490 var matrixrow = matrix[k]; 491 for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) { 492 matrixrow[l] = "x"; 493 } 494 } 495 } 496 } 497 // Store the map so future lookups are fast. 498 return this.tableHeaderIndexes[tableObj.id][cellCoordinates]; 499 }; 500 501 /** 502 * Sort all rows in each TBODY (tbodies are sorted independent of each other) 503 */ 504 table.sort = function(o,args) { 505 var t, tdata, sortconvert=null; 506 // Allow for a simple passing of sort type as second parameter 507 if (typeof(args)=="function") { 508 args={sorttype:args}; 509 } 510 args = args || {}; 511 512 // If no col is specified, deduce it from the object sent in 513 if (!def(args.col)) { 514 args.col = this.getActualCellIndex(o) || 0; 515 } 516 // If no sort type is specified, default to the default sort 517 args.sorttype = args.sorttype || Sort['default']; 518 519 // Resolve the table 520 t = this.resolve(o,args); 521 tdata = this.tabledata[t.id]; 522 523 // If we are sorting on the same column as last time, flip the sort direction 524 if (def(tdata.lastcol) && tdata.lastcol==tdata.col && def(tdata.lastdesc)) { 525 tdata.desc = !tdata.lastdesc; 526 } 527 else { 528 tdata.desc = !!args.desc; 529 } 530 531 // Store the last sorted column so clicking again will reverse the sort order 532 tdata.lastcol=tdata.col; 533 tdata.lastdesc=!!tdata.desc; 534 535 // If a sort conversion function exists, pre-convert cell values and then use a plain alphanumeric sort 536 var sorttype = tdata.sorttype; 537 if (typeof(sorttype.convert)=="function") { 538 sortconvert=tdata.sorttype.convert; 539 sorttype=Sort.alphanumeric; 540 } 541 542 // Loop through all THEADs and remove sorted class names, then re-add them for the col 543 // that is being sorted 544 this.processTableCells(t,"THEAD", 545 function(cell) { 546 if (hasClass(cell,this.SortableClassName)) { 547 removeClass(cell,this.SortedAscendingClassName); 548 removeClass(cell,this.SortedDescendingClassName); 549 // If the computed colIndex of the cell equals the sorted colIndex, flag it as sorted 550 if (tdata.col==table.getActualCellIndex(cell) && (classValue(cell,table.SortableClassName))) { 551 addClass(cell,tdata.desc?this.SortedAscendingClassName:this.SortedDescendingClassName); 552 } 553 } 554 } 555 ); 556 557 // Sort each tbody independently 558 var bodies = t.tBodies; 559 if (bodies==null || bodies.length==0) { return; } 560 561 // Define a new sort function to be called to consider descending or not 562 var newSortFunc = (tdata.desc)? 563 function(a,b){return sorttype(b[0],a[0]);} 564 :function(a,b){return sorttype(a[0],b[0]);}; 565 566 var useinnertext=!!tdata.useinnertext; 567 var col = tdata.col; 568 569 for (var i=0,L=bodies.length; i<L; i++) { 570 var tb = bodies[i], tbrows = tb.rows, rows = []; 571 572 // Allow tbodies to request that they not be sorted 573 if(!hasClass(tb,table.NoSortClassName)) { 574 // Create a separate array which will store the converted values and refs to the 575 // actual rows. This is the array that will be sorted. 576 var cRow, cRowIndex=0; 577 if (cRow=tbrows[cRowIndex]){ 578 // Funky loop style because it's considerably faster in IE 579 do { 580 if (rowCells = cRow.cells) { 581 var cellValue = (col<rowCells.length)?this.getCellValue(rowCells[col],useinnertext):null; 582 if (sortconvert) cellValue = sortconvert(cellValue); 583 rows[cRowIndex] = [cellValue,tbrows[cRowIndex]]; 584 } 585 } while (cRow=tbrows[++cRowIndex]) 586 } 587 588 // Do the actual sorting 589 rows.sort(newSortFunc); 590 591 // Move the rows to the correctly sorted order. Appending an existing DOM object just moves it! 592 cRowIndex=0; 593 var displayedCount=0; 594 var f=[removeClass,addClass]; 595 if (cRow=rows[cRowIndex]){ 596 do { 597 tb.appendChild(cRow[1]); 598 } while (cRow=rows[++cRowIndex]) 599 } 600 } 601 } 602 603 // If paging is enabled on the table, then we need to re-page because the order of rows has changed! 604 if (tdata.pagesize) { 605 this.page(t); // This will internally do the striping 606 } 607 else { 608 // Re-stripe if a class name was supplied 609 if (tdata.stripeclass) { 610 this.stripe(t,tdata.stripeclass,!!tdata.ignorehiddenrows); 611 } 612 } 613 }; 614 615 /** 616 * Apply a filter to rows in a table and hide those that do not match. 617 */ 618 table.filter = function(o,filters,args) { 619 var cell; 620 args = args || {}; 621 622 var t = this.resolve(o,args); 623 var tdata = this.tabledata[t.id]; 624 625 // If new filters were passed in, apply them to the table's list of filters 626 if (!filters) { 627 // If a null or blank value was sent in for 'filters' then that means reset the table to no filters 628 tdata.filters = null; 629 } 630 else { 631 // Allow for passing a select list in as the filter, since this is common design 632 if (filters.nodeName=="SELECT" && filters.type=="select-one" && filters.selectedIndex>-1) { 633 filters={ 'filter':filters.options[filters.selectedIndex].value }; 634 } 635 // Also allow for a regular input 636 if (filters.nodeName=="INPUT" && filters.type=="text") { 637 filters={ 'filter':"/^"+filters.value+"/" }; 638 } 639 // Force filters to be an array 640 if (typeof(filters)=="object" && !filters.length) { 641 filters = [filters]; 642 } 643 644 // Convert regular expression strings to RegExp objects and function strings to function objects 645 for (var i=0,L=filters.length; i<L; i++) {
646 var filter = filters[i]; 647 if (typeof(filter.filter)=="string") { 648 // If a filter string is like "/expr/" then turn it into a Regex 649 if (filter.filter.match(/^\/(.*)\/$/)) { 650 filter.filter = new RegExp(RegExp.$1); 651 filter.filter.regex=true; 652 } 653 // If filter string is like "function (x) { ... }" then turn it into a function 654 else if (filter.filter.match(/^function\s*\(([^\)]*)\)\s*\{(.*)}\s*$/)) { 655 filter.filter = Function(RegExp.$1,RegExp.$2); 656 } 657 } 658 // If some non-table object was passed in rather than a 'col' value, resolve it 659 // and assign it's column index to the filter if it doesn't have one. This way, 660 // passing in a cell reference or a select object etc instead of a table object 661 // will automatically set the correct column to filter. 662 if (filter && !def(filter.col) && (cell=getParent(o,"TD","TH"))) { 663 filter.col = this.getCellIndex(cell); 664 } 665 666 // Apply the passed-in filters to the existing list of filters for the table, removing those that have a filter of null or "" 667 if ((!filter || !filter.filter) && tdata.filters) { 668 delete tdata.filters[filter.col]; 669 } 670 else { 671 tdata.filters = tdata.filters || {}; 672 tdata.filters[filter.col] = filter.filter; 673 } 674 } 675 // If no more filters are left, then make sure to empty out the filters object 676 for (var j in tdata.filters) { var keep = true; } 677 if (!keep) { 678 tdata.filters = null; 679 } 680 } 681 // Everything's been setup, so now scrape the table rows 682 return table.scrape(o); 683 }; 684 685 /** 686 * "Page" a table by showing only a subset of the rows 687 */ 688 table.page = function(t,page,args) { 689 args = args || {}; 690 if (def(page)) { args.page = page; } 691 return table.scrape(t,args); 692 }; 693 694 /** 695 * Jump forward or back any number of pages 696 */ 697 table.pageJump = function(t,count,args) { 698 t = this.resolve(t,args); 699 return this.page(t,(table.tabledata[t.id].page||0)+count,args); 700 }; 701 702 /** 703 * Go to the next page of a paged table 704 */ 705 table.pageNext = function(t,args) { 706 return this.pageJump(t,1,args); 707 }; 708 709 /** 710 * Go to the previous page of a paged table 711 */ 712 table.pagePrevious = function(t,args) { 713 return this.pageJump(t,-1,args); 714 }; 715 716 /** 717 * Scrape a table to either hide or show each row based on filters and paging 718 */ 719 table.scrape = function(o,args) { 720 var col,cell,filterList,filterReset=false,filter; 721 var page,pagesize,pagestart,pageend; 722 var unfilteredrows=[],unfilteredrowcount=0,totalrows=0; 723 var t,tdata,row,hideRow; 724 args = args || {}; 725 726 // Resolve the table object 727 t = this.resolve(o,args); 728 tdata = this.tabledata[t.id]; 729 730 // Setup for Paging 731 var page = tdata.page; 732 if (def(page)) { 733 // Don't let the page go before the beginning 734 if (page<0) { tdata.page=page=0; } 735 pagesize = tdata.pagesize || 25; // 25=arbitrary default 736 pagestart = page*pagesize+1; 737 pageend = pagestart + pagesize - 1; 738 } 739 740 // Scrape each row of each tbody 741 var bodies = t.tBodies; 742 if (bodies==null || bodies.length==0) { return; } 743 for (var i=0,L=bodies.length; i<L; i++) { 744 var tb = bodies[i]; 745 for (var j=0,L2=tb.rows.length; j<L2; j++) { 746 row = tb.rows[j]; 747 hideRow = false; 748 749 // Test if filters will hide the row 750 if (tdata.filters && row.cells) { 751 var cells = row.cells; 752 var cellsLength = cells.length; 753 // Test each filter 754 for (col in tdata.filters) { 755 if (!hideRow) { 756 filter = tdata.filters[col]; 757 if (filter && col<cellsLength) { 758 var val = this.getCellValue(cells[col]); 759 if (filter.regex && val.search) { 760 hideRow=(val.search(filter)<0); 761 } 762 else if (typeof(filter)=="function") { 763 hideRow=!filter(val,cells[col]); 764 } 765 else { 766 hideRow = (val!=filter); 767 } 768 } 769 } 770 } 771 } 772 773 // Keep track of the total rows scanned and the total runs _not_ filtered out 774 totalrows++; 775 if (!hideRow) { 776 unfilteredrowcount++; 777 if (def(page)) { 778 // Temporarily keep an array of unfiltered rows in case the page we're on goes past 779 // the last page and we need to back up. Don't want to filter again! 780 unfilteredrows.push(row); 781 if (unfilteredrowcount<pagestart || unfilteredrowcount>pageend) { 782 hideRow = true; 783 } 784 } 785 } 786 787 row.style.display = hideRow?"none":"";
788 } 789 } 790 791 if (def(page)) { 792 // Check to see if filtering has put us past the requested page index. If it has, 793 // then go back to the last page and show it. 794 if (pagestart>=unfilteredrowcount) { 795 pagestart = unfilteredrowcount-(unfilteredrowcount%pagesize); 796 tdata.page = page = pagestart/pagesize; 797 for (var i=pagestart,L=unfilteredrows.length; i<L; i++) { 798 unfilteredrows[i].style.display=""; 799 } 800 } 801 } 802 803 // Loop through all THEADs and add/remove filtered class names 804 this.processTableCells(t,"THEAD", 805 function(c) { 806 ((tdata.filters && def(tdata.filters[table.getCellIndex(c)]) && hasClass(c,table.FilterableClassName))?addClass:removeClass)(c,table.FilteredClassName); 807 } 808 ); 809 810 // Stripe the table if necessary 811 if (tdata.stripeclass) { 812 this.stripe(t); 813 } 814 815 // Calculate some values to be returned for info and updating purposes 816 var pagecount = Math.floor(unfilteredrowcount/pagesize)+1; 817 if (def(page)) { 818 // Update the page number/total containers if they exist 819 if (tdata.container_number) { 820 tdata.container_number.innerHTML = page+1; 821 } 822 if (tdata.container_count) { 823 tdata.container_count.innerHTML = pagecount; 824 } 825 } 826 827 // Update the row count containers if they exist 828 if (tdata.container_filtered_count) { 829 tdata.container_filtered_count.innerHTML = unfilteredrowcount; 830 } 831 if (tdata.container_all_count) { 832 tdata.container_all_count.innerHTML = totalrows; 833 } 834 return { 'data':tdata, 'unfilteredcount':unfilteredrowcount, 'total':totalrows, 'pagecount':pagecount, 'page':page, 'pagesize':pagesize }; 835 }; 836 837 /** 838 * Shade alternate rows, aka Stripe the table. 839 */ 840 table.stripe = function(t,className,args) { 841 args = args || {}; 842 args.stripeclass = className; 843 844 t = this.resolve(t,args); 845 var tdata = this.tabledata[t.id]; 846 847 var bodies = t.tBodies; 848 if (bodies==null || bodies.length==0) { 849 return; 850 } 851 852 className = tdata.stripeclass; 853 // Cache a shorter, quicker reference to either the remove or add class methods 854 var f=[removeClass,addClass]; 855 for (var i=0,L=bodies.length; i<L; i++) { 856 var tb = bodies[i], tbrows = tb.rows, cRowIndex=0, cRow, displayedCount=0; 857 if (cRow=tbrows[cRowIndex]){ 858 // The ignorehiddenrows test is pulled out of the loop for a slight speed increase. 859 // Makes a bigger difference in FF than in IE. 860 // In this case, speed always wins over brevity! 861 if (tdata.ignoreHiddenRows) { 862 do { 863 f[displayedCount++%2](cRow,className); 864 } while (cRow=tbrows[++cRowIndex]) 865 } 866 else { 867 do { 868 if (!isHidden(cRow)) { 869 f[displayedCount++%2](cRow,className); 870 } 871 } while (cRow=tbrows[++cRowIndex]) 872 } 873 } 874 } 875 }; 876 877 /** 878 * Build up a list of unique values in a table column 879 */ 880 table.getUniqueColValues = function(t,col) { 881 var values={}, bodies = this.resolve(t).tBodies; 882 for (var i=0,L=bodies.length; i<L; i++) { 883 var tbody = bodies[i]; 884 for (var r=0,L2=tbody.rows.length; r<L2; r++) { 885 values[this.getCellValue(tbody.rows[r].cells[col])] = true; 886 } 887 } 888 var valArray = []; 889 for (var val in values) { 890 valArray.push(val); 891 } 892 return valArray.sort(); 893 }; 894 895 /** 896 * Scan the document on load and add sorting, filtering, paging etc ability automatically 897 * based on existence of class names on the table and cells. 898 */ 899 table.auto = function(args) { 900 var cells = [], tables = document.getElementsByTagName("TABLE"); 901 var val,tdata; 902 if (tables!=null) { 903 for (var i=0,L=tables.length; i<L; i++) { 904 var t = table.resolve(tables[i]); 905 tdata = table.tabledata[t.id]; 906 if (val=classValue(t,table.StripeClassNamePrefix)) { 907 tdata.stripeclass=val; 908 } 909 // Do auto-filter if necessary 910 if (hasClass(t,table.AutoFilterClassName)) { 911 table.autofilter(t); 912 } 913 // Do auto-page if necessary 914 if (val = classValue(t,table.AutoPageSizePrefix)) { 915 table.autopage(t,{'pagesize':+val}); 916 } 917 // Do auto-sort if necessary 918 if ((val = classValue(t,table.AutoSortColumnPrefix)) || (hasClass(t,table.AutoSortClassName))) { 919 table.autosort(t,{'col':(val==null)?null:+val}); 920 } 921 // Do auto-stripe if necessary 922 if (tdata.stripeclass && hasClass(t,table.AutoStripeClassName)) { 923 table.stripe(t); 924 } 925 } 926 } 927 }; 928 929 /** 930 * Add sorting functionality to a table header cell 931 */ 932 table.autosort = function(t,args) { 933 t = this.resolve(t,args); 934 var tdata = this.tabledata[t.id]; 935 this.processTableCells(t, "THEAD", function(c) { 936 var type = classValue(c,table.SortableColumnPrefix); 937 if (type!=null) { 938 type = type || "default"; 939 c.title =c.title || table.AutoSortTitle; 940 addClass(c,table.SortableClassName); 941 c.onclick = Function("","Table.sort(this,{'sorttype':Sort['"+type+"']})"); 942 // If we are going to auto sort on a column, we need to keep track of what kind of sort it will be 943 if (args.col!=null) { 944 if (args.col==table.getActualCellIndex(c)) { 945 tdata.sorttype=Sort['"+type+"']; 946 } 947 } 948 } 949 } ); 950 if (args.col!=null) { 951 table.sort(t,args); 952 } 953 }; 954 955 /** 956 * Add paging functionality to a table 957 */ 958 table.autopage = function(t,args) { 959 t = this.resolve(t,args); 960 var tdata = this.tabledata[t.id]; 961 if (tdata.pagesize) { 962 this.processTableCells(t, "THEAD,TFOOT", function(c) { 963 var type = classValue(c,table.AutoPageJumpPrefix); 964 if (type=="next") { type = 1; } 965 else if (type=="previous") { type = -1; } 966 if (type!=null) { 967 c.onclick = Function("","Table.pageJump(this,"+type+")"); 968 } 969 } ); 970 if (val = classValue(t,table.PageNumberPrefix)) { 971 tdata.container_number = document.getElementById(val); 972 } 973 if (val = classValue(t,table.PageCountPrefix)) {
974 tdata.container_count = document.getElementById(val); 975 } 976 return table.page(t,0,args); 977 } 978 }; 979 980 /** 981 * A util function to cancel bubbling of clicks on filter dropdowns 982 */ 983 table.cancelBubble = function(e) { 984 e = e || window.event; 985 if (typeof(e.stopPropagation)=="function") { e.stopPropagation(); } 986 if (def(e.cancelBubble)) { e.cancelBubble = true; } 987 }; 988 989 /** 990 * Auto-filter a table 991 */ 992 table.autofilter = function(t,args) { 993 args = args || {}; 994 t = this.resolve(t,args); 995 var tdata = this.tabledata[t.id],val; 996 table.processTableCells(t, "THEAD", function(cell) { 997 if (hasClass(cell,table.FilterableClassName)) { 998 var cellIndex = table.getCellIndex(cell); 999 var colValues = table.getUniqueColValues(t,cellIndex); 1000 if (colValues.length>0) { 1001 if (typeof(args.insert)=="function") { 1002 func.insert(cell,colValues); 1003 } 1004 else { 1005 var sel = '<select onchange="Table.filter(this,this)" onclick="Table.cancelBubble(event)" class="'+table.AutoFilterClassName+'"><option value="">'+table.FilterAllLabel+'</option>'; 1006 for (var i=0; i<colValues.length; i++) { 1007 sel += '<option value="'+colValues[i]+'">'+colValues[i]+'</option>'; 1008 } 1009 sel += '</select>'; 1010 cell.innerHTML += "<br>"+sel; 1011 } 1012 } 1013 } 1014 }); 1015 if (val = classValue(t,table.FilteredRowcountPrefix)) { 1016 tdata.container_filtered_count = document.getElementById(val); 1017 } 1018 if (val = classValue(t,table.RowcountPrefix)) { 1019 tdata.container_all_count = document.getElementById(val); 1020 } 1021 }; 1022 1023 /** 1024 * Attach the auto event so it happens on load. 1025 * use jQuery's ready() function if available 1026 */ 1027 if (typeof(jQuery)!="undefined") { 1028 jQuery(table.auto); 1029 } 1030 else if (window.addEventListener) { 1031 window.addEventListener( "load", table.auto, false ); 1032 } 1033 else if (window.attachEvent) { 1034 window.attachEvent( "onload", table.auto ); 1035 } 1036 1037 return table; 1038})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.