UNPKG

14.3 kBJavaScriptView Raw
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
44function 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
75SortableTable.gecko = navigator.product == "Gecko";
76SortableTable.msie = /msie/i.test(navigator.userAgent);
77// Mozilla is faster when doing the DOM manipulations on
78// an orphaned element. MSIE is not
79SortableTable.removeBeforeSort = SortableTable.gecko;
80
81SortableTable.prototype.onsort = function () {};
82
83// default sort order. true -> descending, false -> ascending
84SortableTable.prototype.defaultDescending = false;
85
86// shared between all instances. This is intentional to allow external files
87// to modify the prototype
88SortableTable.prototype._sortTypeInfo = {};
89
90SortableTable.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
98SortableTable.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
105SortableTable.prototype.setTBody = function (oTBody) {
106 this.tBody = oTBody;
107};
108
109SortableTable.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
120SortableTable.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 = "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
152SortableTable.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
171SortableTable.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
187SortableTable.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
197SortableTable.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
206SortableTable.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
214SortableTable.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
271SortableTable.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
279SortableTable.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
295SortableTable.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
304SortableTable.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
318SortableTable.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
335SortableTable.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
357SortableTable.prototype.getSortFunction = function (sType, nColumn) {
358 if (this._sortTypeInfo[sType])
359 return this._sortTypeInfo[sType].compare;
360 return SortableTable.basicCompare;
361};
362
363SortableTable.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
393SortableTable.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
403SortableTable.prototype.removeSortType = function (sType) {
404 delete this._sortTypeInfo[sType];
405};
406
407SortableTable.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
415SortableTable.idFunction = function (x) {
416 return x;
417};
418
419SortableTable.toUpperCase = function (s) {
420 return s.toUpperCase();
421};
422
423SortableTable.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
434SortableTable.prototype.addSortType("Number", Number);
435SortableTable.prototype.addSortType("CaseInsensitiveString", SortableTable.toUpperCase);
436SortableTable.prototype.addSortType("Date", SortableTable.toDate);
437SortableTable.prototype.addSortType("String");
438// None is a special case