1 | /*----------------------------------------------------------------------------\ |
---|
2 | | Sortable Table 1.12 | |
---|
3 | |-----------------------------------------------------------------------------| |
---|
4 | | Created by Erik Arvidsson | |
---|
5 | | (http://webfx.eae.net/contact.html#erik) | |
---|
6 | | For WebFX (http://webfx.eae.net/) | |
---|
7 | |-----------------------------------------------------------------------------| |
---|
8 | | A DOM 1 based script that allows an ordinary HTML table to be sortable. | |
---|
9 | |-----------------------------------------------------------------------------| |
---|
10 | | Copyright (c) 1998 - 2006 Erik Arvidsson | |
---|
11 | |-----------------------------------------------------------------------------| |
---|
12 | | Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
---|
13 | | use this file except in compliance with the License. You may obtain a copy | |
---|
14 | | of the License at http://www.apache.org/licenses/LICENSE-2.0 | |
---|
15 | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
---|
16 | | Unless required by applicable law or agreed to in writing, software | |
---|
17 | | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
---|
18 | | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
---|
19 | | License for the specific language governing permissions and limitations | |
---|
20 | | under the License. | |
---|
21 | |-----------------------------------------------------------------------------| |
---|
22 | | 2003-01-10 | First version | |
---|
23 | | 2003-01-19 | Minor changes to the date parsing | |
---|
24 | | 2003-01-28 | JScript 5.0 fixes (no support for 'in' operator) | |
---|
25 | | 2003-02-01 | Sloppy typo like error fixed in getInnerText | |
---|
26 | | 2003-07-04 | Added workaround for IE cellIndex bug. | |
---|
27 | | 2003-11-09 | The bDescending argument to sort was not correctly working | |
---|
28 | | | Using onclick DOM0 event if no support for addEventListener | |
---|
29 | | | or attachEvent | |
---|
30 | | 2004-01-13 | Adding addSortType and removeSortType which makes it a lot | |
---|
31 | | | easier to add new, custom sort types. | |
---|
32 | | 2004-01-27 | Switch to use descending = false as the default sort order. | |
---|
33 | | | Change defaultDescending to suit your needs. | |
---|
34 | | 2004-03-14 | Improved sort type None look and feel a bit | |
---|
35 | | 2004-08-26 | Made the handling of tBody and tHead more flexible. Now you | |
---|
36 | | | can use another tHead or no tHead, and you can chose some | |
---|
37 | | | other tBody. | |
---|
38 | | 2006-04-25 | Changed license to Apache Software License 2.0 | |
---|
39 | |-----------------------------------------------------------------------------| |
---|
40 | | Created 2003-01-10 | All changes are in the log above. | Updated 2006-04-25 | |
---|
41 | \----------------------------------------------------------------------------*/ |
---|
42 | |
---|
43 | |
---|
44 | function SortableTable(oTable, oSortTypes) { |
---|
45 | |
---|
46 | this.sortTypes = oSortTypes || []; |
---|
47 | |
---|
48 | this.sortColumn = null; |
---|
49 | this.descending = null; |
---|
50 | |
---|
51 | var oThis = this; |
---|
52 | this._headerOnclick = function (e) { |
---|
53 | oThis.headerOnclick(e); |
---|
54 | }; |
---|
55 | |
---|
56 | if (oTable) { |
---|
57 | this.setTable( oTable ); |
---|
58 | this.document = oTable.ownerDocument || oTable.document; |
---|
59 | } |
---|
60 | else { |
---|
61 | this.document = document; |
---|
62 | } |
---|
63 | |
---|
64 | |
---|
65 | // only IE needs this |
---|
66 | var win = this.document.defaultView || this.document.parentWindow; |
---|
67 | this._onunload = function () { |
---|
68 | oThis.destroy(); |
---|
69 | }; |
---|
70 | if (win && typeof win.attachEvent != "undefined") { |
---|
71 | win.attachEvent("onunload", this._onunload); |
---|
72 | } |
---|
73 | } |
---|
74 | |
---|
75 | SortableTable.gecko = navigator.product == "Gecko"; |
---|
76 | SortableTable.msie = /msie/i.test(navigator.userAgent); |
---|
77 | // Mozilla is faster when doing the DOM manipulations on |
---|
78 | // an orphaned element. MSIE is not |
---|
79 | SortableTable.removeBeforeSort = SortableTable.gecko; |
---|
80 | |
---|
81 | SortableTable.prototype.onsort = function () {}; |
---|
82 | |
---|
83 | // default sort order. true -> descending, false -> ascending |
---|
84 | SortableTable.prototype.defaultDescending = false; |
---|
85 | |
---|
86 | // shared between all instances. This is intentional to allow external files |
---|
87 | // to modify the prototype |
---|
88 | SortableTable.prototype._sortTypeInfo = {}; |
---|
89 | |
---|
90 | SortableTable.prototype.setTable = function (oTable) { |
---|
91 | if ( this.tHead ) |
---|
92 | this.uninitHeader(); |
---|
93 | this.element = oTable; |
---|
94 | this.setTHead( oTable.tHead ); |
---|
95 | this.setTBody( oTable.tBodies[0] ); |
---|
96 | }; |
---|
97 | |
---|
98 | SortableTable.prototype.setTHead = function (oTHead) { |
---|
99 | if (this.tHead && this.tHead != oTHead ) |
---|
100 | this.uninitHeader(); |
---|
101 | this.tHead = oTHead; |
---|
102 | this.initHeader( this.sortTypes ); |
---|
103 | }; |
---|
104 | |
---|
105 | SortableTable.prototype.setTBody = function (oTBody) { |
---|
106 | this.tBody = oTBody; |
---|
107 | }; |
---|
108 | |
---|
109 | SortableTable.prototype.setSortTypes = function ( oSortTypes ) { |
---|
110 | if ( this.tHead ) |
---|
111 | this.uninitHeader(); |
---|
112 | this.sortTypes = oSortTypes || []; |
---|
113 | if ( this.tHead ) |
---|
114 | this.initHeader( this.sortTypes ); |
---|
115 | }; |
---|
116 | |
---|
117 | // adds arrow containers and events |
---|
118 | // also binds sort type to the header cells so that reordering columns does |
---|
119 | // not break the sort types |
---|
120 | SortableTable.prototype.initHeader = function (oSortTypes) { |
---|
121 | if (!this.tHead) return; |
---|
122 | var cells = this.tHead.rows[0].cells; |
---|
123 | var doc = this.tHead.ownerDocument || this.tHead.document; |
---|
124 | this.sortTypes = oSortTypes || []; |
---|
125 | var l = cells.length; |
---|
126 | var img, c; |
---|
127 | for (var i = 0; i < l; i++) { |
---|
128 | c = cells[i]; |
---|
129 | if (this.sortTypes[i] != null && this.sortTypes[i] != "None") { |
---|
130 | img = doc.createElement("IMG"); |
---|
131 | img.src = "html/images/blank.png"; |
---|
132 | c.appendChild(img); |
---|
133 | if (this.sortTypes[i] != null) |
---|
134 | c._sortType = this.sortTypes[i]; |
---|
135 | if (typeof c.addEventListener != "undefined") |
---|
136 | c.addEventListener("click", this._headerOnclick, false); |
---|
137 | else if (typeof c.attachEvent != "undefined") |
---|
138 | c.attachEvent("onclick", this._headerOnclick); |
---|
139 | else |
---|
140 | c.onclick = this._headerOnclick; |
---|
141 | } |
---|
142 | else |
---|
143 | { |
---|
144 | c.setAttribute( "_sortType", oSortTypes[i] ); |
---|
145 | c._sortType = "None"; |
---|
146 | } |
---|
147 | } |
---|
148 | this.updateHeaderArrows(); |
---|
149 | }; |
---|
150 | |
---|
151 | // remove arrows and events |
---|
152 | SortableTable.prototype.uninitHeader = function () { |
---|
153 | if (!this.tHead) return; |
---|
154 | var cells = this.tHead.rows[0].cells; |
---|
155 | var l = cells.length; |
---|
156 | var c; |
---|
157 | for (var i = 0; i < l; i++) { |
---|
158 | c = cells[i]; |
---|
159 | if (c._sortType != null && c._sortType != "None") { |
---|
160 | c.removeChild(c.lastChild); |
---|
161 | if (typeof c.removeEventListener != "undefined") |
---|
162 | c.removeEventListener("click", this._headerOnclick, false); |
---|
163 | else if (typeof c.detachEvent != "undefined") |
---|
164 | c.detachEvent("onclick", this._headerOnclick); |
---|
165 | c._sortType = null; |
---|
166 | c.removeAttribute( "_sortType" ); |
---|
167 | } |
---|
168 | } |
---|
169 | }; |
---|
170 | |
---|
171 | SortableTable.prototype.updateHeaderArrows = function () { |
---|
172 | if (!this.tHead) return; |
---|
173 | var cells = this.tHead.rows[0].cells; |
---|
174 | var l = cells.length; |
---|
175 | var img; |
---|
176 | for (var i = 0; i < l; i++) { |
---|
177 | if (cells[i]._sortType != null && cells[i]._sortType != "None") { |
---|
178 | img = cells[i].lastChild; |
---|
179 | if (i == this.sortColumn) |
---|
180 | img.className = "sort-arrow " + (this.descending ? "descending" : "ascending"); |
---|
181 | else |
---|
182 | img.className = "sort-arrow"; |
---|
183 | } |
---|
184 | } |
---|
185 | }; |
---|
186 | |
---|
187 | SortableTable.prototype.headerOnclick = function (e) { |
---|
188 | // find TD element |
---|
189 | var el = e.target || e.srcElement; |
---|
190 | while (el.tagName != "TD") |
---|
191 | el = el.parentNode; |
---|
192 | |
---|
193 | this.sort(SortableTable.msie ? SortableTable.getCellIndex(el) : el.cellIndex); |
---|
194 | }; |
---|
195 | |
---|
196 | // IE returns wrong cellIndex when columns are hidden |
---|
197 | SortableTable.getCellIndex = function (oTd) { |
---|
198 | var cells = oTd.parentNode.childNodes |
---|
199 | var l = cells.length; |
---|
200 | var i; |
---|
201 | for (i = 0; cells[i] != oTd && i < l; i++) |
---|
202 | ; |
---|
203 | return i; |
---|
204 | }; |
---|
205 | |
---|
206 | SortableTable.prototype.getSortType = function (nColumn) { |
---|
207 | return this.sortTypes[nColumn] || "String"; |
---|
208 | }; |
---|
209 | |
---|
210 | // only nColumn is required |
---|
211 | // if bDescending is left out the old value is taken into account |
---|
212 | // if sSortType is left out the sort type is found from the sortTypes array |
---|
213 | |
---|
214 | SortableTable.prototype.sort = function (nColumn, bDescending, sSortType) { |
---|
215 | if (!this.tBody) return; |
---|
216 | if (sSortType == null) |
---|
217 | sSortType = this.getSortType(nColumn); |
---|
218 | |
---|
219 | // exit if None |
---|
220 | if (sSortType == "None") |
---|
221 | return; |
---|
222 | |
---|
223 | if (bDescending == null) { |
---|
224 | if (this.sortColumn != nColumn) |
---|
225 | this.descending = this.defaultDescending; |
---|
226 | else |
---|
227 | this.descending = !this.descending; |
---|
228 | } |
---|
229 | else |
---|
230 | this.descending = bDescending; |
---|
231 | |
---|
232 | this.sortColumn = nColumn; |
---|
233 | |
---|
234 | if (typeof this.onbeforesort == "function") |
---|
235 | this.onbeforesort(); |
---|
236 | |
---|
237 | var f = this.getSortFunction(sSortType, nColumn); |
---|
238 | var a = this.getCache(sSortType, nColumn); |
---|
239 | var tBody = this.tBody; |
---|
240 | |
---|
241 | a.sort(f); |
---|
242 | |
---|
243 | if (this.descending) |
---|
244 | a.reverse(); |
---|
245 | |
---|
246 | if (SortableTable.removeBeforeSort) { |
---|
247 | // remove from doc |
---|
248 | var nextSibling = tBody.nextSibling; |
---|
249 | var p = tBody.parentNode; |
---|
250 | p.removeChild(tBody); |
---|
251 | } |
---|
252 | |
---|
253 | // insert in the new order |
---|
254 | var l = a.length; |
---|
255 | for (var i = 0; i < l; i++) |
---|
256 | tBody.appendChild(a[i].element); |
---|
257 | |
---|
258 | if (SortableTable.removeBeforeSort) { |
---|
259 | // insert into doc |
---|
260 | p.insertBefore(tBody, nextSibling); |
---|
261 | } |
---|
262 | |
---|
263 | this.updateHeaderArrows(); |
---|
264 | |
---|
265 | this.destroyCache(a); |
---|
266 | |
---|
267 | if (typeof this.onsort == "function") |
---|
268 | this.onsort(); |
---|
269 | }; |
---|
270 | |
---|
271 | SortableTable.prototype.asyncSort = function (nColumn, bDescending, sSortType) { |
---|
272 | var oThis = this; |
---|
273 | this._asyncsort = function () { |
---|
274 | oThis.sort(nColumn, bDescending, sSortType); |
---|
275 | }; |
---|
276 | window.setTimeout(this._asyncsort, 1); |
---|
277 | }; |
---|
278 | |
---|
279 | SortableTable.prototype.getCache = function (sType, nColumn) { |
---|
280 | if (!this.tBody) return []; |
---|
281 | var rows = this.tBody.rows; |
---|
282 | var l = rows.length; |
---|
283 | var a = new Array(l); |
---|
284 | var r; |
---|
285 | for (var i = 0; i < l; i++) { |
---|
286 | r = rows[i]; |
---|
287 | a[i] = { |
---|
288 | value: this.getRowValue(r, sType, nColumn), |
---|
289 | element: r |
---|
290 | }; |
---|
291 | }; |
---|
292 | return a; |
---|
293 | }; |
---|
294 | |
---|
295 | SortableTable.prototype.destroyCache = function (oArray) { |
---|
296 | var l = oArray.length; |
---|
297 | for (var i = 0; i < l; i++) { |
---|
298 | oArray[i].value = null; |
---|
299 | oArray[i].element = null; |
---|
300 | oArray[i] = null; |
---|
301 | } |
---|
302 | }; |
---|
303 | |
---|
304 | SortableTable.prototype.getRowValue = function (oRow, sType, nColumn) { |
---|
305 | // if we have defined a custom getRowValue use that |
---|
306 | if (this._sortTypeInfo[sType] && this._sortTypeInfo[sType].getRowValue) |
---|
307 | return this._sortTypeInfo[sType].getRowValue(oRow, nColumn); |
---|
308 | |
---|
309 | var s; |
---|
310 | var c = oRow.cells[nColumn]; |
---|
311 | if (typeof c.innerText != "undefined") |
---|
312 | s = c.innerText; |
---|
313 | else |
---|
314 | s = SortableTable.getInnerText(c); |
---|
315 | return this.getValueFromString(s, sType); |
---|
316 | }; |
---|
317 | |
---|
318 | SortableTable.getInnerText = function (oNode) { |
---|
319 | var s = ""; |
---|
320 | var cs = oNode.childNodes; |
---|
321 | var l = cs.length; |
---|
322 | for (var i = 0; i < l; i++) { |
---|
323 | switch (cs[i].nodeType) { |
---|
324 | case 1: //ELEMENT_NODE |
---|
325 | s += SortableTable.getInnerText(cs[i]); |
---|
326 | break; |
---|
327 | case 3: //TEXT_NODE |
---|
328 | s += cs[i].nodeValue; |
---|
329 | break; |
---|
330 | } |
---|
331 | } |
---|
332 | return s; |
---|
333 | }; |
---|
334 | |
---|
335 | SortableTable.prototype.getValueFromString = function (sText, sType) { |
---|
336 | if (this._sortTypeInfo[sType]) |
---|
337 | return this._sortTypeInfo[sType].getValueFromString( sText ); |
---|
338 | return sText; |
---|
339 | /* |
---|
340 | switch (sType) { |
---|
341 | case "Number": |
---|
342 | return Number(sText); |
---|
343 | case "CaseInsensitiveString": |
---|
344 | return sText.toUpperCase(); |
---|
345 | case "Date": |
---|
346 | var parts = sText.split("-"); |
---|
347 | var d = new Date(0); |
---|
348 | d.setFullYear(parts[0]); |
---|
349 | d.setDate(parts[2]); |
---|
350 | d.setMonth(parts[1] - 1); |
---|
351 | return d.valueOf(); |
---|
352 | } |
---|
353 | return sText; |
---|
354 | */ |
---|
355 | }; |
---|
356 | |
---|
357 | SortableTable.prototype.getSortFunction = function (sType, nColumn) { |
---|
358 | if (this._sortTypeInfo[sType]) |
---|
359 | return this._sortTypeInfo[sType].compare; |
---|
360 | return SortableTable.basicCompare; |
---|
361 | }; |
---|
362 | |
---|
363 | SortableTable.prototype.destroy = function () { |
---|
364 | this.uninitHeader(); |
---|
365 | var win = this.document.parentWindow; |
---|
366 | if (win && typeof win.detachEvent != "undefined") { // only IE needs this |
---|
367 | win.detachEvent("onunload", this._onunload); |
---|
368 | } |
---|
369 | this._onunload = null; |
---|
370 | this.element = null; |
---|
371 | this.tHead = null; |
---|
372 | this.tBody = null; |
---|
373 | this.document = null; |
---|
374 | this._headerOnclick = null; |
---|
375 | this.sortTypes = null; |
---|
376 | this._asyncsort = null; |
---|
377 | this.onsort = null; |
---|
378 | }; |
---|
379 | |
---|
380 | // Adds a sort type to all instance of SortableTable |
---|
381 | // sType : String - the identifier of the sort type |
---|
382 | // fGetValueFromString : function ( s : string ) : T - A function that takes a |
---|
383 | // string and casts it to a desired format. If left out the string is just |
---|
384 | // returned |
---|
385 | // fCompareFunction : function ( n1 : T, n2 : T ) : Number - A normal JS sort |
---|
386 | // compare function. Takes two values and compares them. If left out less than, |
---|
387 | // <, compare is used |
---|
388 | // fGetRowValue : function( oRow : HTMLTRElement, nColumn : int ) : T - A function |
---|
389 | // that takes the row and the column index and returns the value used to compare. |
---|
390 | // If left out then the innerText is first taken for the cell and then the |
---|
391 | // fGetValueFromString is used to convert that string the desired value and type |
---|
392 | |
---|
393 | SortableTable.prototype.addSortType = function (sType, fGetValueFromString, fCompareFunction, fGetRowValue) { |
---|
394 | this._sortTypeInfo[sType] = { |
---|
395 | type: sType, |
---|
396 | getValueFromString: fGetValueFromString || SortableTable.idFunction, |
---|
397 | compare: fCompareFunction || SortableTable.basicCompare, |
---|
398 | getRowValue: fGetRowValue |
---|
399 | }; |
---|
400 | }; |
---|
401 | |
---|
402 | // this removes the sort type from all instances of SortableTable |
---|
403 | SortableTable.prototype.removeSortType = function (sType) { |
---|
404 | delete this._sortTypeInfo[sType]; |
---|
405 | }; |
---|
406 | |
---|
407 | SortableTable.basicCompare = function compare(n1, n2) { |
---|
408 | if (n1.value < n2.value) |
---|
409 | return -1; |
---|
410 | if (n2.value < n1.value) |
---|
411 | return 1; |
---|
412 | return 0; |
---|
413 | }; |
---|
414 | |
---|
415 | SortableTable.idFunction = function (x) { |
---|
416 | return x; |
---|
417 | }; |
---|
418 | |
---|
419 | SortableTable.toUpperCase = function (s) { |
---|
420 | return s.toUpperCase(); |
---|
421 | }; |
---|
422 | |
---|
423 | SortableTable.toDate = function (s) { |
---|
424 | var parts = s.split("-"); |
---|
425 | var d = new Date(0); |
---|
426 | d.setFullYear(parts[0]); |
---|
427 | d.setDate(parts[2]); |
---|
428 | d.setMonth(parts[1] - 1); |
---|
429 | return d.valueOf(); |
---|
430 | }; |
---|
431 | |
---|
432 | |
---|
433 | // add sort types |
---|
434 | SortableTable.prototype.addSortType("Number", Number); |
---|
435 | SortableTable.prototype.addSortType("CaseInsensitiveString", SortableTable.toUpperCase); |
---|
436 | SortableTable.prototype.addSortType("Date", SortableTable.toDate); |
---|
437 | SortableTable.prototype.addSortType("String"); |
---|
438 | // None is a special case |
---|