source: rtems-tools/tester/covoar/table.js @ 3e187ba

5
Last change on this file since 3e187ba was 100f517, checked in by Chris Johns <chrisj@…>, on 05/09/14 at 11:50:37

covoar: Merger the covoar source from rtems-testing.git.

Use waf to build covoar.

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