[5933] | 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 |
---|