﻿/**
* 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 || 0 == 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];

    // we rather know that it is the first row of the heading in the thead that should be clickable
    var row = table.tHead.rows[0];

    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('\u00A0\u00A0'));
      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('\u00A0\u00A0'));
        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 (1 == cs[i].nodeType) { // ELEMENT NODE
        str += this.getInnerText(cs[i]);
        break;
      } else if (3 == 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(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) {
      sortfn = this.sortDate;
    }
    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) {
      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]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|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(yr) < 50) {
        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(yr) < 50) {
        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 (3 > 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];

      //if (rowItem.style.display != 'none') {
      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;
  },

  filter: function(term, _id, cellNr) {

    var suche = term.value.toLowerCase();
    var table = document.getElementById(_id);
    var ele;
    for (var r = 2; r < table.rows.length; r++) {
      ele = table.rows[r].cells[cellNr].innerHTML.replace(/<[^>]+>/g, "");
      if (ele.toLowerCase().indexOf(suche) >= 0)
        table.rows[r].style.display = '';
      else table.rows[r].style.display = 'none';
    }
  },

  filter2: function(phrase, _id, _startRow) {
    this.isOdd = false;
    var words = phrase.value.toLowerCase().split(" ");
    var table = document.getElementById(_id);
    var ele;
    for (var r = _startRow; r < table.rows.length; r++) {
      ele = table.rows[r].innerHTML.replace(/<[^>]+>/g, "");
      var displayStyle = 'none';
      for (var i = 0; i < words.length; i++) {
        if (ele.toLowerCase().indexOf(words[i]) >= 0)
          displayStyle = '';
        else {
          displayStyle = 'none';
          break;
        }
      }
      table.rows[r].style.display = displayStyle;
      if (displayStyle != 'none')
        this.doStripe(table.rows[r]);
    }
  },
  /*filter3 for repeaters no striping*/
  filter3: function(phrase, _id, _startRow) {  
    var words = phrase.value.toLowerCase().split(" ");
    var table = document.getElementById(_id);
    var ele;
    for (var r = _startRow; r < table.rows.length; r++) {
      ele = table.rows[r].innerHTML.replace(/<[^>]+>/g, "");
      var displayStyle = 'none';
      for (var i = 0; i < words.length; i++) {
        if (ele.toLowerCase().indexOf(words[i]) >= 0)
          displayStyle = '';
        else {
          displayStyle = 'none';
          break;
        }
      }
      table.rows[r].style.display = displayStyle;      
    }
  }
}

/*end filter3*/
function standardistaTableSortingInit() {
  standardistaTableSorting.init();
}

addEvent(window, 'load', standardistaTableSortingInit)