PageSourceSearch

https://waterfalls.co.nz/scripts/mytable.js

js waterfalls.co.nz collected 2026-10-02 05:09:28 UTC 31,988 bytes, 1,038 lines download raw bytes

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.