Implementation

Since a lot of the implementaion follows the old version I'm going to try to concentrate on the more important parts. Espescially how the sorting is done and how this was done to maximize performance.

Sorting

Just like in the old version the Array sort is used, but unlike the old version most of the work that was done in the compare function has been moved out of this function and is done once before the sorting starts.

Caching the Data

Before the sorting is started an array is made that contains JavaScript objects which contains the needed information to do the actual sorting of the table. This information consists of the value used to sort the rows as well as a reference to the actual row element so that we can reorder the rows once the sorting is done.

SortableTable.prototype.getCache = function (sType, nColumn) {
   var rows = this.tBody.rows;
   var l = rows.length;
   var a = new Array(l);
   var r;
   for (var i = 0; i < l; i++) {
      r = rows[i];
      a[i] = {
         value:   this.getRowValue(r, sType, nColumn),
         element: r
      };
   };
   return a;
};

One thing to note here is that the length is calculated once before the loop. The reason for this is that getting rows.length is an expensive operation in IE and we do not want to do it once for every row.

Once we have this array we do not need to interact with the DOM during the actual sorting of the array.

The sort method

The function getCache prepares the data and once that is done the actual code to sort the table is more or less book keeping. The compare function to use when sorting is returned by the method getSortFunction. In its current state this function just returns a funtion that compares the value properties of the object inside the cache array. One might want to modify this for more esoteric data types where the operator < is not defined.

Once the cache array has been sorted the table rows are reinserted in the same order as the cache array. The property element of the JS object in the array is used to find the correct row element.

SortableTable.prototype.sort = function (nColumn, bDescending, sSortType) {
   if (sSortType == null)
      sSortType = this.getSortType(nColumn);

   // exit if None
   if (sSortType == "None")
      return;

   if (bDescending == null) {
      if (this.sortColumn != nColumn)
         this.descending = true;
      else
         this.descending = !this.descending;
   }

   this.sortColumn = nColumn;

   if (typeof this.onbeforesort == "function")
      this.onbeforesort();

   var f = this.getSortFunction(sSortType, nColumn);
   var a = this.getCache(sSortType, nColumn);
   var tBody = this.tBody;

   a.sort(f);

   if (this.descending)
      a.reverse();

   if (SortableTable.removeBeforeSort) {
      // remove from doc
      var nextSibling = tBody.nextSibling;
      var p = tBody.parentNode;
      p.removeChild(tBody);
   }

   // insert in the new order
   var l = a.length;
   for (var i = 0; i < l; i++)
      tBody.appendChild(a[i].element);

   if (SortableTable.removeBeforeSort) {
      // insert into doc
      p.insertBefore(tBody, nextSibling);
   }

   this.updateHeaderArrows();

   this.destroyCache(a);

   if (typeof this.onsort == "function")
      this.onsort();
};

In some browser (namely Mozilla) the DOM manipulations are faster when done on a disconnected DOM tree. Therefore when SortableTable.removeBeforeSort is true we first disconnect the tree before reorganizing the rows and once the reorganization is done we reinsert the table.

Setting up the Header

Sort Arrows

When the SortableTable is constructed images are added to the table header cells. These images then changes their background image to indicate that the column is sorted.

Hooking up the events

Also at creation, event listeners are added to the table header cells. In this case we use addEventListener if available and if not we try to use attachEvent. These are set to call this._headerOnclick, which is just an encapsulation of this.headerOnclick(). There are 2 reasons why it is done in this way. The first is so that this will point to the JS object inside the function headerOnclick and not on the table cell element. The other reason is that we need to be able to detach the event listener when destroying the JS object. If we used an anonymous function this would not have been possible.

function SortableTable(oTable, oSortTypes) {
   ...
   var oThis = this;
   this._headerOnclick = function (e) {
      oThis.headerOnclick(e);
   };
   ...
}

SortableTable.prototype.initHeader = function (oSortTypes) {
   var cells = this.tHead.rows[0].cells;
   var l = cells.length;
   var img, c;
   for (var i = 0; i < l; i++) {
      c = cells[i];
      img = this.document.createElement("IMG");
      img.src = "images/blank.png";
      c.appendChild(img);
      if (oSortTypes[i] != null) {
         c._sortType = oSortTypes[i];
      }
      if ("addEventListener" in c)
         c.addEventListener("click", this._headerOnclick, false);
      else if ("attachEvent" in c)
         c.attachEvent("onclick", this._headerOnclick);
   }
   this.updateHeaderArrows();
};

Sortable Table
Implementation
API
Demo
Download

Author: Erik Arvidsson