Click here to Skip to main content
15,899,754 members
Articles / Web Development / ASP.NET
Article

HTML Table Sorting and Manual Scrollbars

Rate me:
Please Sign up or sign in to vote.
4.30/5 (4 votes)
14 May 2007CPOL 34.3K   287   23   1
This article explains how to sort an HTML table and add your own scrollbar

Introduction

This article explains how to sort HTML tables and add manual scrollbars.

Using the Code

The following code explains the sorting functionality. The function sorttable(tableid,sortType) requires two parameters tableid and sorttype asc or desc.

JavaScript
//
function SortableTable(oTable, oSortTypes) {
 this.sortTypes = oSortTypes || [];
 this.sortColumn = null;
 this.descending = null;
 var oThis = this;
 this._headerOnclick = function (e) {
  oThis.headerOnclick(e);
 };
 if (oTable) {
  this.setTable( oTable );
  this.document = oTable.ownerDocument || oTable.document;
 }
 else {
  this.document = document;
 }
 // only Internet Explorer needs this
 var win = this.document.defaultView || this.document.parentWindow;
 this._onunload = function () {
  oThis.destroy();
 };
 if (win && typeof win.attachEvent != "undefined") {
  win.attachEvent("onunload", this._onunload);
 }
}
SortableTable.gecko = navigator.product == "Gecko";
SortableTable.msie = /msie/i.test(navigator.userAgent);
// Mozilla is faster when doing the DOM manipulations on
// an orphaned element. Microsoft Internet Explorer is not
SortableTable.removeBeforeSort = SortableTable.gecko;
SortableTable.prototype.onsort = function () {};
// default sort order. true -> descending, false -> ascending
SortableTable.prototype.defaultDescending = false;
// shared between all instances. This is intentional to allow external files
// to modify the prototype
SortableTable.prototype._sortTypeInfo = {};
SortableTable.prototype.setTable = function (oTable) {
 if ( this.tHead )
  this.uninitHeader();
 this.element = oTable;
 this.setTHead( oTable.tHead );
 this.setTBody( oTable.tBodies[0] );
};
SortableTable.prototype.setTHead = function (oTHead) {
 if (this.tHead && this.tHead != oTHead )
  this.uninitHeader();
 this.tHead = oTHead;
 this.initHeader( this.sortTypes );
};
SortableTable.prototype.setTBody = function (oTBody) {
 this.tBody = oTBody;
};
SortableTable.prototype.setSortTypes = function ( oSortTypes ) {
 if ( this.tHead )
  this.uninitHeader();
 this.sortTypes = oSortTypes || [];
 if ( this.tHead )
  this.initHeader( this.sortTypes );
};
// adds arrow containers and events
// also binds sort type to the header cells so that reordering columns does
// not break the sort types
SortableTable.prototype.initHeader = function (oSortTypes) {
 if (!this.tHead) return;
 var cells = this.tHead.rows[0].cells;
 var doc = this.tHead.ownerDocument || this.tHead.document;
 this.sortTypes = oSortTypes || [];
 var l = cells.length;
 var img, c;
 for (var i = 0; i < l; i++) {
  c = cells[i];
  if (this.sortTypes[i] != null && this.sortTypes[i] != "None") {
   img = doc.createElement("IMG");
   img.src = "images/blank.png";
   c.appendChild(img);
   if (this.sortTypes[i] != null)
    c._sortType = this.sortTypes[i];
   if (typeof c.addEventListener != "undefined")
    c.addEventListener("click", this._headerOnclick, false);
   else if (typeof c.attachEvent != "undefined")
    c.attachEvent("onclick", this._headerOnclick);
   else
    c.onclick = this._headerOnclick;
  }
  else
  {
   c.setAttribute( "_sortType", oSortTypes[i] );
   c._sortType = "None";
  }
 }
 this.updateHeaderArrows();
};
// remove arrows and events
SortableTable.prototype.uninitHeader = function () {
 if (!this.tHead) return;
 var cells = this.tHead.rows[0].cells;
 var l = cells.length;
 var c;
 for (var i = 0; i < l; i++) {
  c = cells[i];
  if (c._sortType != null && c._sortType != "None") {
   c.removeChild(c.lastChild);
   if (typeof c.removeEventListener != "undefined")
    c.removeEventListener("click", this._headerOnclick, false);
   else if (typeof c.detachEvent != "undefined")
    c.detachEvent("onclick", this._headerOnclick);
   c._sortType = null;
   c.removeAttribute( "_sortType" );
  }
 }
};
SortableTable.prototype.updateHeaderArrows = function () {
 if (!this.tHead) return;
 var cells = this.tHead.rows[0].cells;
 var l = cells.length;
 var img;
 for (var i = 0; i < l; i++) {
  if (cells[i]._sortType != null && cells[i]._sortType != "None") {
   img = cells[i].lastChild;
   if (i == this.sortColumn)
    img.className = "sort-arrow " + (this.descending ? "descending" : "ascending");
   else
    img.className = "sort-arrow";
  }
 }
};
SortableTable.prototype.headerOnclick = function (e) {
 // find TD element
 var el = e.target || e.srcElement;
 while (el.tagName != "TD")
  el = el.parentNode;
 this.sort(SortableTable.msie ? SortableTable.getCellIndex(el) : el.cellIndex);
};
// IE returns wrong cellIndex when columns are hidden
SortableTable.getCellIndex = function (oTd) {
 var cells = oTd.parentNode.childNodes
 var l = cells.length;
 var i;
 for (i = 0; cells[i] != oTd && i < l; i++)
  ;
 return i;
};
SortableTable.prototype.getSortType = function (nColumn) {
 return this.sortTypes[nColumn] || "String";
};
// only nColumn is required
// if bDescending is left out the old value is taken into account
// if sSortType is left out the sort type is found from the sortTypes array
SortableTable.prototype.sort = function (nColumn, bDescending, sSortType) {
 if (!this.tBody) return;
 if (sSortType == null)
  sSortType = this.getSortType(nColumn);
 // exit if None
 if (sSortType == "None")
  return;
 if (bDescending == null) {
  if (this.sortColumn != nColumn)
   this.descending = this.defaultDescending;
  else
   this.descending = !this.descending;
 }
 else
  this.descending = bDescending;
 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();
};
SortableTable.prototype.asyncSort = function (nColumn, bDescending, sSortType) {
 var oThis = this;
 this._asyncsort = function () {
  oThis.sort(nColumn, bDescending, sSortType);
 };
 window.setTimeout(this._asyncsort, 1);
};
SortableTable.prototype.getCache = function (sType, nColumn) {
 if (!this.tBody) return [];
 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;
};
SortableTable.prototype.destroyCache = function (oArray) {
 var l = oArray.length;
 for (var i = 0; i < l; i++) {
  oArray[i].value = null;
  oArray[i].element = null;
  oArray[i] = null;
 }
};
SortableTable.prototype.getRowValue = function (oRow, sType, nColumn) {
 // if we have defined a custom getRowValue use that
 if (this._sortTypeInfo[sType] && this._sortTypeInfo[sType].getRowValue)
  return this._sortTypeInfo[sType].getRowValue(oRow, nColumn);
 var s;
 var c = oRow.cells[nColumn];
 if (typeof c.innerText != "undefined")
  s = c.innerText;
 else
  s = SortableTable.getInnerText(c);
 return this.getValueFromString(s, sType);
};
SortableTable.getInnerText = function (oNode) {
 var s = "";
 var cs = oNode.childNodes;
 var l = cs.length;
 for (var i = 0; i < l; i++) {
  switch (cs[i].nodeType) {
   case 1: //ELEMENT_NODE
    s += SortableTable.getInnerText(cs[i]);
    break;
   case 3: //TEXT_NODE
    s += cs[i].nodeValue;
    break;
  }
 }
 return s;
};
SortableTable.prototype.getValueFromString = function (sText, sType) {
 if (this._sortTypeInfo[sType])
  return this._sortTypeInfo[sType].getValueFromString( sText );
 return sText;
 /*
 switch (sType) {
  case "Number":
   return Number(sText);
  case "CaseInsensitiveString":
   return sText.toUpperCase();
  case "Date":
   var parts = sText.split("-");
   var d = new Date(0);
   d.setFullYear(parts[0]);
   d.setDate(parts[2]);
   d.setMonth(parts[1] - 1);
   return d.valueOf();
 }
 return sText;
 */
 };
SortableTable.prototype.getSortFunction = function (sType, nColumn) {
 if (this._sortTypeInfo[sType])
  return this._sortTypeInfo[sType].compare;
 return SortableTable.basicCompare;
};
SortableTable.prototype.destroy = function () {
 this.uninitHeader();
 var win = this.document.parentWindow;
 if (win && typeof win.detachEvent != "undefined") { // only IE needs this
  win.detachEvent("onunload", this._onunload);
 }
 this._onunload = null;
 this.element = null;
 this.tHead = null;
 this.tBody = null;
 this.document = null;
 this._headerOnclick = null;
 this.sortTypes = null;
 this._asyncsort = null;
 this.onsort = null;
};
// Adds a sort type to all instance of SortableTable
// sType : String - the identifier of the sort type
// fGetValueFromString : function ( s : string ) : T - A function that takes a
//    string and casts it to a desired format. If left out the string is just
//    returned
// fCompareFunction : function ( n1 : T, n2 : T ) : Number - A normal JS sort
//    compare function. Takes two values and compares them. If left out less than,
//    <, compare is used
// fGetRowValue : function( oRow : HTMLTRElement, nColumn : int ) : T - A function
//    that takes the row and the column index and returns the value used to compare.
//    If left out then the innerText is first taken for the cell and then the
//    fGetValueFromString is used to convert that string the desired value and type
SortableTable.prototype.addSortType = 
    function (sType, fGetValueFromString, fCompareFunction, fGetRowValue) {
 this._sortTypeInfo[sType] = {
  type:    sType,
  getValueFromString: fGetValueFromString || SortableTable.idFunction,
  compare:   fCompareFunction || SortableTable.basicCompare,
  getRowValue:  fGetRowValue
 };
};
// this removes the sort type from all instances of SortableTable
SortableTable.prototype.removeSortType = function (sType) {
 delete this._sortTypeInfo[sType];
};
SortableTable.basicCompare = function compare(n1, n2) {
 if (n1.value < n2.value)
  return -1;
 if (n2.value < n1.value)
  return 1;
 return 0;
};
SortableTable.idFunction = function (x) {
 return x;
};
SortableTable.toUpperCase = function (s) {
 return s.toUpperCase();
};
SortableTable.toDate = function (s) {
 var parts = s.split("-");
 var d = new Date(0);
 d.setFullYear(parts[0]);
 d.setDate(parts[2]);
 d.setMonth(parts[1] - 1);
 return d.valueOf();
};
// add sort types
SortableTable.prototype.addSortType("Number", Number);
SortableTable.prototype.addSortType("CaseInsensitiveString", SortableTable.toUpperCase);
SortableTable.prototype.addSortType("Date", SortableTable.toDate);
SortableTable.prototype.addSortType("String");
// None is a special case

Points of Interest

While writing this script, I have learnt JavaScript tips and techniques. I love JAVASCRIPT and think that it is COOL.

History

  • 15th May, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Pakistan Pakistan
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralDemo project needs work Pin
jpyle12-Sep-07 10:49
jpyle12-Sep-07 10:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.