Table Sorter Proof of Concept : Table Grid : GUI Components JAVASCRIPT DHTML TUTORIALS


JAVASCRIPT DHTML TUTORIALS » GUI Components » Table Grid »

 

Table Sorter Proof of Concept




<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>Table Sorter Proof of Concept</title>

    <style type="text/css">
table {
  width: 80%;
  margin: 1em auto;
  border-collapse: collapse;
}

thead th,
tfoot th {
  padding: 0.5em;
  text-align: left;
  border: 1px solid black;
  background-color: #AAF;
}

tfoot td {
  border-top: 1px solid black;
}

tbody td {
  padding: 0.5em;
  border-left: 1px solid black;
  border-right: 1px solid black;
}

tbody tr.odd {
  background-color: #DDF;
}

td.numeric,
th.numeric {
  text-align: right;
}

    </style>
        <!-- common.js -->
    <script type='text/javascript'>
    /**
 * addEvent written by Dean Edwards, 2005
 * with input from Tino Zijdel
 *
 * http://dean.edwards.name/weblog/2005/10/add-event/
 **/
function addEvent(element, type, handler) {
  // assign each event handler a unique ID
  if (!handler.$$guidhandler.$$guid = addEvent.guid++;
  // create a hash table of event types for the element
  if (!element.eventselement.events = {};
  // create a hash table of event handlers for each element/event pair
  var handlers = element.events[type];
  if (!handlers) {
    handlers = element.events[type{};
    // store the existing event handler (if there is one)
    if (element["on" + type]) {
      handlers[0= element["on" + type];
    }
  }
  // store the event handler in the hash table
  handlers[handler.$$guid= handler;
  // assign a global event handler to do all the work
  element["on" + type= handleEvent;
};
// a counter used to create unique IDs
addEvent.guid = 1;

function removeEvent(element, type, handler) {
  // delete the event handler from the hash table
  if (element.events && element.events[type]) {
    delete element.events[type][handler.$$guid];
  }
};

function handleEvent(event) {
  var returnValue = true;
  // grab the event object (IE uses a global event object)
  event = event || fixEvent(window.event);
  // get a reference to the hash table of event handlers
  var handlers = this.events[event.type];
  // execute each event handler
  for (var i in handlers) {
    this.$$handleEvent = handlers[i];
    if (this.$$handleEvent(event=== false) {
      returnValue = false;
    }
  }
  return returnValue;
};

function fixEvent(event) {
  // add W3C standard event methods
  event.preventDefault = fixEvent.preventDefault;
  event.stopPropagation = fixEvent.stopPropagation;
  return event;
};
fixEvent.preventDefault = function() {
  this.returnValue = false;
};
fixEvent.stopPropagation = function() {
  this.cancelBubble = true;
};

// end from Dean Edwards


/**
 * Creates an Element for insertion into the DOM tree.
 * From http://simon.incutio.com/archive/2003/06/15/javascriptWithXML
 *
 @param element the element type to be created.
 *        e.g. ul (no angle brackets)
 **/
function createElement(element) {
  if (typeof document.createElementNS != 'undefined') {
    return document.createElementNS('http://www.w3.org/1999/xhtml', element);
  }
  if (typeof document.createElement != 'undefined') {
    return document.createElement(element);
  }
  return false;
}

/**
 * "targ" is the element which caused this function to be called
 * from http://www.quirksmode.org/js/events_properties.html
 **/
function getEventTarget(e) {
  var targ;
  if (!e) {
    e = window.event;
  }
  if (e.target) {
    targ = e.target;
  else if (e.srcElement) {
    targ = e.srcElement;
  }
  if (targ.nodeType == 3) { // defeat Safari bug
    targ = targ.parentNode;
  }

  return targ;
}


    </script>
    <!-- css.js -->
    <script type='text/javascript'>
/**
 * Written by Neil Crosby. 
 * http://www.workingwith.me.uk/
 *
 * Use this wherever you want, but please keep this comment at the top of this file.
 *
 * Copyright (c) 2006 Neil Crosby
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 **/
var css = {
  /**
   * Returns an array containing references to all elements
   * of a given tag type within a certain node which have a given class
   *
   @param node    the node to start from 
   *          (e.g. document, 
   *              getElementById('whateverStartpointYouWant')
   *          )
   @param searchClass the class we're wanting
   *          (e.g. 'some_class')
   @param tag     the tag that the found elements are allowed to be
   *          (e.g. '*', 'div', 'li')
   **/
  getElementsByClass : function(node, searchClass, tag) {
    var classElements = new Array();
    var els = node.getElementsByTagName(tag);
    var elsLen = els.length;
    var pattern = new RegExp("(^|\s)"+searchClass+"(\s|$)");
    
    
    for (var i = 0, j = 0; i < elsLen; i++) {
      if (this.elementHasClass(els[i], searchClass) ) {
        classElements[j= els[i];
        j++;
      }
    }
    return classElements;
  },


  /**
   * PRIVATE.  Returns an array containing all the classes applied to this
   * element.
   *
   * Used internally by elementHasClass(), addClassToElement() and 
   * removeClassFromElement().
   **/
  privateGetClassArray: function(el) {
    return el.className.split(' ')
  },

  /**
   * PRIVATE.  Creates a string from an array of class names which can be used 
   * by the className function.
   *
   * Used internally by addClassToElement().
   **/
  privateCreateClassString: function(classArray) {
    return classArray.join(' ');
  },

  /**
   * Returns true if the given element has been assigned the given class.
   **/
  elementHasClass: function(el, classString) {
    if (!el) {
      return false;
    }
    
    var regex = new RegExp('\b'+classString+'\b');
    if (el.className.match(regex)) {
      return true;
    }

    return false;
  },

  /**
   * Adds classString to the classes assigned to the element with id equal to
   * idString.
   **/
  addClassToId: function(idString, classString) {
    this.addClassToElement(document.getElementById(idString), classString);
  },

  /**
   * Adds classString to the classes assigned to the given element.
   * If the element already has the class which was to be added, then
   * it is not added again.
   **/
  addClassToElement: function(el, classString) {
    var classArray = this.privateGetClassArray(el);

    if (this.elementHasClass(el, classString)) {
      return// already has element so don't need to add it
    }

    classArray.push(classString);

    el.className = this.privateCreateClassString(classArray);
  },

  /**
   * Removes the given classString from the list of classes assigned to the
   * element with id equal to idString
   **/
  removeClassFromId: function(idString, classString) {
    this.removeClassFromElement(document.getElementById(idString), classString);
  },

  /**
   * Removes the given classString from the list of classes assigned to the
   * given element.  If the element has the same class assigned to it twice, 
   * then only the first instance of that class is removed.
   **/
  removeClassFromElement: function(el, classString) {
    var classArray = this.privateGetClassArray(el);

    for (x in classArray) {
      if (classString == classArray[x]) {
        classArray[x'';
        break;
      }
    }

    el.className = this.privateCreateClassString(classArray);
  }
}    
    </script>
    <!-- standardista-table-sorting.js -->
    <script type='text/javascript'>
/**
 * Written by Neil Crosby. 
 * http://www.workingwith.me.uk/articles/scripting/standardista_table_sorting
 *
 * This module is based on Stuart Langridge's "sorttable" code.  Specifically, 
 * the determineSortFunction, sortCaseInsensitive, sortDate, sortNumeric, and
 * sortCurrency functions are heavily based on his code.  This module would not
 * have been possible without Stuart's earlier outstanding work.
 *
 * Use this wherever you want, but please keep this comment at the top of this file.
 *
 * Copyright (c) 2006 Neil Crosby
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal 
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 * SOFTWARE.
 **/
var standardistaTableSorting = {

  that: false,
  isOdd: false,

  sortColumnIndex : -1,
  lastAssignedId : 0,
  newRows: -1,
  lastSortedTable: -1,

  /**
   * Initialises the Standardista Table Sorting module
   **/
  init : function() {
    // first, check whether this web browser is capable of running this script
    if (!document.getElementsByTagName) {
      return;
    }
    
    this.that = this;
    
    this.run();
    
  },
  
  /**
   * Runs over each table in the document, making it sortable if it has a class
   * assigned named "sortable" and an id assigned.
   **/
  run : function() {
    var tables = document.getElementsByTagName("table");
    
    for (var i=0; i < tables.length; i++) {
      var thisTable = tables[i];
      
      if (css.elementHasClass(thisTable, 'sortable')) {
        this.makeSortable(thisTable);
      }
    }
  },
  
  /**
   * Makes the given table sortable.
   **/
  makeSortable : function(table) {
  
    // first, check if the table has an id.  if it doesn't, give it one
    if (!table.id) {
      table.id = 'sortableTable'+this.lastAssignedId++;
    }
    
    // if this table does not have a thead, we don't want to know about it
    if (!table.tHead || !table.tHead.rows || == table.tHead.rows.length) {
      return;
    }
    
    // we'll assume that the last row of headings in the thead is the row that 
    // wants to become clickable
    var row = table.tHead.rows[table.tHead.rows.length - 1];
    
    for (var i=0; i < row.cells.length; i++) {
    
      // create a link with an onClick event which will 
      // control the sorting of the table
      var linkEl = createElement('a');
      linkEl.href = '#';
      linkEl.onclick = this.headingClicked;
      linkEl.setAttribute('columnId', i);
      linkEl.title = 'Click to sort';
      
      // move the current contents of the cell that we're 
      // hyperlinking into the hyperlink
      var innerEls = row.cells[i].childNodes;
      for (var j = 0; j < innerEls.length; j++) {
        linkEl.appendChild(innerEls[j]);
      }
      
      // and finally add the new link back into the cell
      row.cells[i].appendChild(linkEl);

      var spanEl = createElement('span');
      spanEl.className = 'tableSortArrow';
      spanEl.appendChild(document.createTextNode('u00A0u00A0'));
      row.cells[i].appendChild(spanEl);

    }
  
    if (css.elementHasClass(table, 'autostripe')) {
      this.isOdd = false;
      var rows = table.tBodies[0].rows;
    
      // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
      for (var i=0;i<rows.length;i++) { 
        this.doStripe(rows[i]);
      }
    }
  },
  
  headingClicked: function(e) {
    
    var that = standardistaTableSorting.that;
    
    // linkEl is the hyperlink that was clicked on which caused
    // this method to be called
    var linkEl = getEventTarget(e);
    
    // directly outside it is a td, tr, thead and table
    var td     = linkEl.parentNode;
    var tr     = td.parentNode;
    var thead  = tr.parentNode;
    var table  = thead.parentNode;
    
    // if the table we're looking at doesn't have any rows
    // (or only has one) then there's no point trying to sort it
    if (!table.tBodies || table.tBodies[0].rows.length <= 1) {
      return false;
    }

    // the column we want is indicated by td.cellIndex
    var column = linkEl.getAttribute('columnId') || td.cellIndex;
    //var column = td.cellIndex;
    
    // find out what the current sort order of this column is
    var arrows = css.getElementsByClass(td, 'tableSortArrow', 'span');
    var previousSortOrder = '';
    if (arrows.length > 0) {
      previousSortOrder = arrows[0].getAttribute('sortOrder');
    }
    
    // work out how we want to sort this column using the data in the first cell
    // but just getting the first cell is no good if it contains no data
    // so if the first cell just contains white space then we need to track
    // down until we find a cell which does contain some actual data
    var itm = ''
    var rowNum = 0;
    while ('' == itm && rowNum < table.tBodies[0].rows.length) {
      itm = that.getInnerText(table.tBodies[0].rows[rowNum].cells[column]);
      rowNum++;
    }
    var sortfn = that.determineSortFunction(itm);

    // if the last column that was sorted was this one, then all we need to 
    // do is reverse the sorting on this column
    if (table.id == that.lastSortedTable && column == that.sortColumnIndex) {
      newRows = that.newRows;
      newRows.reverse();
    // otherwise, we have to do the full sort
    else {
      that.sortColumnIndex = column;
      var newRows = new Array();

      for (var j = 0; j < table.tBodies[0].rows.length; j++) { 
        newRows[j= table.tBodies[0].rows[j]
      }

      newRows.sort(sortfn);
    }

    that.moveRows(table, newRows);
    that.newRows = newRows;
    that.lastSortedTable = table.id;
    
    // now, give the user some feedback about which way the column is sorted
    
    // first, get rid of any arrows in any heading cells
    var arrows = css.getElementsByClass(tr, 'tableSortArrow', 'span');
    for (var j = 0; j < arrows.length; j++) {
      var arrowParent = arrows[j].parentNode;
      arrowParent.removeChild(arrows[j]);

      if (arrowParent != td) {
        spanEl = createElement('span');
        spanEl.className = 'tableSortArrow';
        spanEl.appendChild(document.createTextNode('u00A0u00A0'));
        arrowParent.appendChild(spanEl);
      }
    }
    
    // now, add back in some feedback 
    var spanEl = createElement('span');
    spanEl.className = 'tableSortArrow';
    if (null == previousSortOrder || '' == previousSortOrder || 'DESC' == previousSortOrder) {
      spanEl.appendChild(document.createTextNode(u2191'));
      spanEl.setAttribute('sortOrder', 'ASC');
    else {
      spanEl.appendChild(document.createTextNode(u2193'));
      spanEl.setAttribute('sortOrder', 'DESC');
    }
    
    td.appendChild(spanEl);
    
    return false;
  },

  getInnerText : function(el) {
    
    if ('string' == typeof el || 'undefined' == typeof el) {
      return el;
    }
    
    if (el.innerText) {
      return el.innerText;  // Not needed but it is faster
    }

    var str = el.getAttribute('standardistaTableSortingInnerText');
    if (null != str && '' != str) {
      return str;
    }
    str = '';

    var cs = el.childNodes;
    var l = cs.length;
    for (var i = 0; i < l; i++) {
      // 'if' is considerably quicker than a 'switch' statement, 
      // in Internet Explorer which translates up to a good time 
      // reduction since this is a very often called recursive function
      if (== cs[i].nodeType) { // ELEMENT NODE
        str += this.getInnerText(cs[i]);
        break;
      else if (== cs[i].nodeType) { //TEXT_NODE
        str += cs[i].nodeValue;
        break;
      }
    }
    
    // set the innertext for this element directly on the element
    // so that it can be retrieved early next time the innertext
    // is requested
    el.setAttribute('standardistaTableSortingInnerText', str);
    
    return str;
  },

  determineSortFunction : function(itm) {
    
    var sortfn = this.sortCaseInsensitive;
    
    if (itm.match(/^dd[/-]dd[/-]dddd$/)) {
      sortfn = this.sortDate;
    }
    if (itm.match(/^dd[/-]dd[/-]dd$/)) {
      sortfn = this.sortDate;
    }
    if (itm.match(/^[?/)) {
      sortfn = this.sortCurrency;
    }
    if (itm.match(/^d?.?d+$/)) {
      sortfn = this.sortNumeric;
    }
    if (itm.match(/^[+-]?d*.?d+([eE]-?d+)?$/)) {
      sortfn = this.sortNumeric;
    }
        if (itm.match(/^([01]?dd?|2[0-4]d|25[0-5]).([01]?dd?|2[0-4]d|25[0-5]).([01]?dd?|2[0-4]d|25[0-5]).([01]?dd?|2[0-4]d|25[0-5])$/)) {
            sortfn = this.sortIP;
       }

    return sortfn;
  },
  
  sortCaseInsensitive : function(a, b) {
    var that = standardistaTableSorting.that;
    
    var aa = that.getInnerText(a.cells[that.sortColumnIndex]).toLowerCase();
    var bb = that.getInnerText(b.cells[that.sortColumnIndex]).toLowerCase();
    if (aa==bb) {
      return 0;
    else if (aa<bb) {
      return -1;
    else {
      return 1;
    }
  },
  
  sortDate : function(a,b) {
    var that = standardistaTableSorting.that;

    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
    var aa = that.getInnerText(a.cells[that.sortColumnIndex]);
    var bb = that.getInnerText(b.cells[that.sortColumnIndex]);
    
    var dt1, dt2, yr = -1;
    
    if (aa.length == 10) {
      dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
    else {
      yr = aa.substr(6,2);
      if (parseInt(yr50) { 
        yr = '20'+yr; 
      else 
        yr = '19'+yr; 
      }
      dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
    }
    
    if (bb.length == 10) {
      dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
    else {
      yr = bb.substr(6,2);
      if (parseInt(yr50) { 
        yr = '20'+yr; 
      else 
        yr = '19'+yr; 
      }
      dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
    }
    
    if (dt1==dt2) {
      return 0;
    else if (dt1<dt2) {
      return -1;
    }
    return 1;
  },

  sortCurrency : function(a,b) { 
    var that = standardistaTableSorting.that;

    var aa = that.getInnerText(a.cells[that.sortColumnIndex]).replace(/[^0-9.]/g,'');
    var bb = that.getInnerText(b.cells[that.sortColumnIndex]).replace(/[^0-9.]/g,'');
    return parseFloat(aa- parseFloat(bb);
  },

  sortNumeric : function(a,b) { 
    var that = standardistaTableSorting.that;

    var aa = parseFloat(that.getInnerText(a.cells[that.sortColumnIndex]));
    if (isNaN(aa)) { 
      aa = 0;
    }
    var bb = parseFloat(that.getInnerText(b.cells[that.sortColumnIndex]))
    if (isNaN(bb)) { 
      bb = 0;
    }
    return aa-bb;
  },

  makeStandardIPAddress : function(val) {
    var vals = val.split('.');

    for (x in vals) {
      val = vals[x];

      while (> val.length) {
        val = '0'+val;
      }
      vals[x= val;
    }

    val = vals.join('.');

    return val;
  },

  sortIP : function(a,b) { 
    var that = standardistaTableSorting.that;

    var aa = that.makeStandardIPAddress(that.getInnerText(a.cells[that.sortColumnIndex]).toLowerCase());
    var bb = that.makeStandardIPAddress(that.getInnerText(b.cells[that.sortColumnIndex]).toLowerCase());
    if (aa==bb) {
      return 0;
    else if (aa<bb) {
      return -1;
    else {
      return 1;
    }
  },

  moveRows : function(table, newRows) {
    this.isOdd = false;

    // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
    for (var i=0;i<newRows.length;i++) { 
      var rowItem = newRows[i];

      this.doStripe(rowItem);

      table.tBodies[0].appendChild(rowItem)
    }
  },
  
  doStripe : function(rowItem) {
    if (this.isOdd) {
      css.addClassToElement(rowItem, 'odd');
    else {
      css.removeClassFromElement(rowItem, 'odd');
    }
    
    this.isOdd = !this.isOdd;
  }

}

function standardistaTableSortingInit() {
  standardistaTableSorting.init();
}

addEvent(window, 'load', standardistaTableSortingInit)    
    </script>
  </head>
  <body>
    <table class='sortable'>
      <thead>
        <tr>
          <td></td>
          <th colspan='2'>Name</th>
          <td></td>
          <td></td>
          <td></td>
        </tr>
        <tr>
          <th>Date</th>
          <th>Forename</th>
          <th>Surname</th>
          <th>Number</th>
          <th>Price</th>
          <th>IP Address</th>
          <th>Scientific</th>
        </tr>
      </thead>
      <tfoot>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <th class='numeric'>385</th>
          <th class='numeric'>$160.91</th>
          <td></td>
          <td></td>
        </tr>
      </tfoot>
      <tbody>
        <tr>
          <td>21/01/2006</td>
          <td>Neil</td>
          <td>Crosby</td>
          <td class='numeric'>123</td>
          <td class='numeric'>$1.96</td>
          <td>192.168.1.1</td>
          <td>-12E2</td>
        </tr>
        <tr class='odd'>
          <td>01/02/2006</td>
          <td>Becca</td>
          <td>Courtley</td>
          <td class='numeric'>122</td>
          <td class='numeric'>$23.95</td>
          <td>192.167.2.1</td>
          <td>12E2</td>
        </tr>
        <tr>
          <td>17/11/2004</td>
          <td>David</td>
          <td>Freidman</td>
          <td class='numeric'>048</td>
          <td class='numeric'>$14.00</td>
          <td>192.168.2.1</td>
          <td>13e-2</td>
        </tr>
        <tr class='odd'>
          <td>17/10/2004</td>
          <td>Sylvia</td>
          <td>Tyler</td>
          <td class='numeric'>43</td>
          <td class='numeric'>$104.00</td>
          <td>192.168.2.17</td>
          <td>12.1e2</td>
        </tr>
        <tr>
          <td>17/11/2005</td>
          <td>Carl</td>
          <td>Conway</td>
          <td class='numeric'>49</td>
          <td class='numeric'>$17.00</td>
          <td>192.168.02.13</td>
          <td>12e3</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>
           
       



-

Leave a Comment / Note


 
Verification is used to prevent unwanted posts (spam). .

Follow Navioo On Twitter

JAVASCRIPT DHTML TUTORIALS

 Navioo GUI Components
» Table Grid