﻿/******************

JavaScript Table representation loaded from an XHTML request

This tabel should be inherited from to provide more 
specific functionality.

Table is based upon a two dimentional array, 
(array of arrays in javascript).

Associated with this is an array describing the column
names.

Must include:
zinherit.js
ajaxobject.js

***********************************************************/

/*TODO
Table classname
Move col styling from toTable into stylesheet (padding)
Cross Browser onclick action for input elements (checkbox)
*/

//Class definition/constructor
function AjaxTable(id,url,onload)
{
  //Inherit properties from AjaxObject
  AjaxObject.call(this,url,this.onJSONload,null,"POST");

  //Local variables
  this.tableid = id;
  this.rowid = 0;//Which field in the database is to be used as the row id.
  this.ontableload = onload;
  this.data = new Array();
  this.columnnames = new Array();
  this.columnheadings = new Array();
  this.dataview = new DataView(this);
  this.showselect = false;
  this.sortascimage = "images/triangasc.gif";
  this.sortdescimage = "images/triangdesc.gif";
  this.tablerendertarget = null;
}
  //inherit methods from AjaxObject
  AjaxTable.prototype.inheritFrom(AjaxObject);
    
  AjaxTable.prototype.getRow         = function(rownum)
  {
    return this.data[rownum];
  };
  AjaxTable.prototype.addRow         = function(row)
  {
    this.data.push(row);
  };
  AjaxTable.prototype.onRowRender    = function(tablerow,rowdata)
  {
    //Allow inheriting classes to modify the row before it is added to the table.
  };
  AjaxTable.prototype.onColClick = function(colnum)
  {
    //alert('colsort');
    this.Sort(colnum);
    this.render();
  }
  AjaxTable.prototype.getCount       = function()
  {
    return this.data.length;
  };
  AjaxTable.prototype.onRowCheck = function(table,rownum,checked)
  {
    //alert(stophere);
    if(rownum == -1)
    {
      //This is the table header, so we need to check all / uncheck all
      for(var i=0;i<this.getCount();i++)
      {
          this.getRow(i).selected = checked 
          //This just manipulates the data, not the rendered HTML control
          cb = document.getElementById(this.tableid + 'cb' + this.getRow(i)[this.rowid]);
          cb.checked = checked;
      }
    }
    else
    {
      this.getRow(rownum).selected = checked;
      this.checkHeader(table);  
    }
  }
  AjaxTable.prototype.checkHeader = function(table)
  {
    //Loop through table - if all are selected then check cbAll
    var checkall = true;
    for (var i = 0; i < table.getCount(); i++)
    {
      if (!table.getRow(i).selected)
      {
        checkall = false;
        break;
      }
    }
    cball = document.getElementById(this.tableid + 'cbAll');
    cball.checked = checkall;
  }
  AjaxTable.prototype.BulkSetCB = function(arrselect)
  {
    // moves through the list of selected elements and causes the row to be checked
    var rowID;
    var rowNum;
    for (var i = 0; i < arrselect.length; i++)
    {
      rowID = arrselect[i];
      // for the rowID get the rowNum.
      if(rowID != "")
      {
        rowNum = this.RowNumFromRowID(rowID);
        
        if(rowNum >= 0)
        {
          // call the onRowCheck method
          this.getRow(rowNum).selected = true;
          
        }
      }
    } 
  }
  
  AjaxTable.prototype.RowNumFromRowID = function(rowID)
  {
      var curRowID;
      var foundNum = -1;
     for(var i=0;i<this.getCount();i++)
      {
          curRowId = this.getRow(i)[this.rowid];
          
          if(curRowId == rowID)
          {
            foundNum = i;
            break;
          }
      }
      return foundNum;
        
  }
  
  
  
  
  
  AjaxTable.prototype.HTMLTable      = function(cols)
  {
    if(!cols){
      cols = new Array(this.getColumnCount());
    }
    var table    = document.createElement("table");
    table.setAttribute('id',this.tableid);
    table.className = "stationstable";
    var colgroup = document.createElement("colgroup");
    //Columns
    if(this.showselect)
    {
      var checkcol = document.createElement("col");
      checkcol.style.padding = "2px 2px 2px 2px";
      checkcol.width = "20px";
      colgroup.appendChild(checkcol);
    }
    for(var i=0;i<cols.length;i++)
    {
      var col = document.createElement("col");
      col.style.padding = "2px 2px 2px 12px";
      colgroup.appendChild(col);
    }
    table.appendChild(colgroup);
    //Table Header
    var tablehead = document.createElement("thead");
    var tablerow  = document.createElement("tr");
    if(this.showselect)
    {
      //Need to determine if the check all checkbox should be checked by default
      var checkall = true;
      for(var i=0;i<this.data.length;i++)
      {
        var row = this.getRow(i);
        if(!row.selected)
        {
          checkall = false;
          break;
        }
      }
      var tablecell = this.__appendCheckBox__("th",-1,checkall);
      tablerow.appendChild(tablecell);  
    }
    for(var i=0;i<cols.length;i++)
    {
      var colnum = cols[i];
      //works
      var tablecell = this.__tableheader__(colnum);
      tablerow.appendChild(tablecell);
    }
    tablehead.appendChild(tablerow);
    table.appendChild(tablehead);
    //Table Body
    var tablebody = document.createElement("tbody");
    for(var rownum=0;rownum<this.data.length;rownum++)
    {
      var datarow = this.getRow(rownum);
      var row = document.createElement("tr");
      if(rownum % 2 == 0)
        row.className = "even";
      else
        row.className = "odd";
      if(this.showselect)
      {
        var checkcell = this.__appendCheckBox__('td',rownum,datarow.selected);
        row.appendChild(checkcell);
      }
      for(var i=0;i<cols.length;i++)
      {
        var cell = document.createElement("td");
        var celltext = document.createTextNode(datarow[cols[i]]);
        cell.appendChild(celltext);
        row.appendChild(cell);
      }
      this.onRowRender(row,datarow);
      tablebody.appendChild(row);
    }
    table.appendChild(tablebody);
    return table;
  }; 
  //Consider http://www.json.org/json.js
//  AjaxTable.prototype.getJSON        = function()
//  {
//    return buildJSON(this.data);
//  }  
  AjaxTable.prototype.onJSONload = function()
  {
    var json = this.httpreq.responseText;
    this.setJSON(json);
  }
  AjaxTable.prototype.setJSON = function(json)
  {
    var jsonobj = eval('(' + json + ')');
    this.columnnames = jsonobj.columnnames;
    //If datatable has no content then don't bother setting column headings from returned json
    if(jsonobj.datatable.length > 0)
    {
      if(this.columnheadings.length < jsonobj.datatable[0].length)
        this.columnheadings = jsonobj.columnnames;
    }
    this.data = jsonobj.datatable;
    this.ontableload.call(this);
  }
  AjaxTable.prototype.getColumnCount = function()
  {
    if(this.data[0])
      return this.data[0].length;
    else 
      return 0;
  }  
  AjaxTable.prototype.getColumnName  = function(colnum)
  {
      return this.columnnames[colnum];
  };
  AjaxTable.prototype.getColumnHeading = function(colnum)
  {
    if(this.columnheadings[colnum])
      return this.columnheadings[colnum]
    else
      return this.columnnames[colnum];
  };
  AjaxTable.prototype.getColumnByName = function(colname)
  {
    var lcolname = colname.toLowerCase();
    for(var i=0;i<this.getColumnCount();i++)
    {
      if(this.columnnames[i].toLowerCase() == lcolname)
      {
        return i;
      }
    }
  };
  
  AjaxTable.prototype.displayTable = function(tabletarget)
  {
    this.tablerendertarget = document.getElementById(tabletarget);
    this.render();
  };
  
  AjaxTable.prototype.render = function()
  {
    if(this.tablerendertarget != null)
    {
      this.tablerendertarget.innerHTML = ""; 
      var table = this.dataview.getDataTable(); 
      this.tablerendertarget.appendChild(table);
    }
  };
      
  //Private Helper Functions
  AjaxTable.prototype.__appendCheckBox__ = function(tagname,rownum,defaultchecked)
  {
    var tablecell=document.createElement(tagname);
    var inputelem = document.createElement('INPUT');
    inputelem.type = 'checkbox';
    if(rownum == -1)
      inputelem.id = this.tableid + 'cbAll';
    else
      inputelem.id = this.tableid + 'cb' + this.getRow(rownum)[this.rowid];
    //TODO - this doesn't work for IE!
    //inputelem.onclick = onclickscript;
    //Instead use
    var ajtable = this;
    inputelem.onclick = function() {
      ajtable.onRowCheck(ajtable,rownum,this.checked);
    }
    inputelem.defaultChecked = defaultchecked;
    tablecell.appendChild(inputelem);
    return tablecell;
  }
  AjaxTable.prototype.__tableheader__ = function(colnum)
  {
    var tablecell=document.createElement('TH');
    var colname = document.createTextNode(this.getColumnHeading(colnum));
    tablecell.appendChild(colname);
    //If this is the current sort column then append an image
    if(this.dataview.sortcol == colnum)
    {
      var sortimage = document.createElement('IMG');
      if(this.dataview.sortasc)
        sortimage.setAttribute('src',this.sortascimage);
      else
        sortimage.setAttribute('src',this.sortdescimage);
      tablecell.appendChild(sortimage);  
    }
    var ajtable = this;
    tablecell.onclick = function() {
      ajtable.onColClick(colnum);
      return false;
    }
    return tablecell;
  }
  
  
  /* Sort Implementation */
  AjaxTable.prototype.Sort = function(column,asc)
  {
    if(this.data[0])
    {
      this.dataview.sort = (this.columnnames[column]);
      
      if(asc == null)
      {
        if(this.dataview.sortcol == column)
          this.dataview.sortasc = !this.dataview.sortasc;
        else
          this.dataview.sortasc = true; 
      }
      else
        this.dataview.sortasc = asc; 
      
      this.dataview.sortcol = column;
      
      if(this.dataview.sortasc)
        this.dataview.sort += " ASC";
      else
        this.dataview.sort += " DESC";
        
      var type = typeof(this.data[0][column]);
      if (type == 'string')
      {
        if(this.dataview.sortasc)
          this.data.sort(function(row1,row2){
            var s1 = row1[column];
            var s2 = row2[column];
            return s1.localeCompare(s2);
          });
        else
          this.data.sort(function(row1,row2){
            return row2[column].localeCompare(row1[column]);
          });
      }
      else
      {
        if(this.dataview.sortasc)
          this.data.sort(function(row1,row2){
            return row1[column] - row2[column];
          });
        else
          this.data.sort(function(row1,row2){
            return row2[column] - row1[column];
          });
      }
    }
  }
  
  //Allow inherited classes to return typed tables
  AjaxTable.prototype.CreateTable = function()
  {
    var ajtable = new AjaxTable();
    ajtable.columnnames = this.columnnames;
    return ajtable;
  }
  
  /* Filter Implementation */
  AjaxTable.prototype.Filter = function(filterarray,dataview)
  {
    var ftable = this.CreateTable();
    if(dataview)
      ftable.dataview = dataview;
    //Loop through data and check to see if each row passes the filter expression
    for(var i=0;i<this.data.length;i++)
    {
      var row = this.data[i];
      var addrow = true;
      for(var j=0;j<filterarray.length;j++)
      {
        if(addrow)
        {
          var filter = filterarray[j];
          var value = row[filter.ColumnNumber];
          switch(filter.Comparison)
          {
              case "like": 
                var regex = new RegExp(filter.regex,"gi")
                regex.lastIndex = 0;
                var match = regex.test(value);
                if (match)
                  addrow = true;
                else
                  addrow = false;
                break;
              case "=":
                var regex = new RegExp(filter.regex,"gi")
                regex.lastIndex = 0;
                if (regex.test(value))
                  addrow = true;
                else
                  addrow = false;
                break;
              case "!=":
                var regex = new RegExp(filter.regex,"gi")
                regex.lastIndex = 0;
                if (!regex.test(value))
                  addrow = true;
                else
                  addrow = false;
                break;
              case "<":
                if(value < filter.Criteria)
                  addrow = true;
                else
                  addrow = false;
                break;  
              case "<=":
                if(value <= filter.Criteria)
                  addrow = true;
                else
                  addrow = false;
                break;  
              case "=":
                if(value = filter.Criteria)
                  addrow = true;
                else
                  addrow = false;
                break; 
              case ">=":
                if(value >= filter.Criteria)
                  addrow = true;
                else
                  addrow = false;
                break; 
              case ">":
                if(value > filter.Criteria)
                  addrow = true;
                else
                  addrow = false;
                break; 
              case "in":
                //Make an array of allowed values
                addrow = false;
                arrallowed = filter.Criteria.split(",");
                for ( var k = 0; k < arrallowed.length; k++ ) {
                  if (arrallowed[k] == value)
                  {
                    addrow = true;
                  }
                }
                break; 
           }//switch
         }
      }//filterarray
      if(addrow)
        ftable.addRow(row);
    }//data rows
    return ftable;
  }
  
  AjaxTable.prototype.SelectedIDs = function()
  {
    selected = new Array();
    for(var i=0;i<this.data.length;i++)
    {
      var row = this.getRow(i);
      if(row.selected)
        selected.push(row[this.rowid]);
    }
    return selected.join(',');
  }
  
//  function buildJSON(data)
//  {
//    var jsontable = new Array;
//    for(var i=0;i<data.length;i++)
//    {
//      var jsonrow = new StringBuffer();
//      jsonrow.append('[');
//      for(var j=0;j<data[i].length;j++)
//      {
//        jsonrow.append(safename(data[i][j]));
//        if(j<data[i].length-1)
//        jsonrow.append(',');
//      }
//      jsonrow.append(']');
//      jsontable.push(jsonrow);
//    }
//    return '[' + jsontable.join(',') + ']';
//  }
//  function safename(name)
//  {
//    if(getType(name) == 'string')
//    {
//      return '"' + name + '"';
//    }
//    else return name;
//  }
//  //Helper Classes
//  function StringBuffer()
//  {
//    this.buffer = [];
//  }
//  StringBuffer.prototype.append = function(string)
//  {
//    this.buffer.push(string);
//    return this;
//  }
//  StringBuffer.prototype.toString = function()
//  {
//    return this.buffer.join("");
//  } 

function DataView(parent)
{
  //Local variables
  this.parenttable = parent;
  this.view = null;
  this.showcolumns = null;
  this.rowfilter = "";
  this.sort = "";
  this.sortcol = 0;
  this.sortasc = true;
}

  DataView.prototype.View = function()
  {
  	//Split RowFilter on AND
    var rowfilter = this.rowfilter.split(" AND ");//Use RegEx to split on AND and OR
    var filterarray = new Array();
    //For each element in rowfilter extract the appropriate column and filter by the criteria
    var rerowfilter = /\s*(\w+)\s*(=|<|>|<=|>=|\!=|LIKE)\s*["|'](%?[\w.&\- ]+%?)["|']/ig;
    var recontainsrowfilter = /\s*(\w+)\s*(IN)\s*\(([\w.\-'", ]*)\)/ig;
    for(i=0;i<rowfilter.length;i++)
    {
      //Regex seems to act very wierd when reused - especially in Firefox.
      //Need to set lastIndex to start again.
      rerowfilter.lastIndex = 0;
      var test = rowfilter[i];
      var match = null;
      match = rerowfilter.test(test);
      if(!match)
      {
        recontainsrowfilter.lastIndex = 0;
        match = recontainsrowfilter.test(test);
      }
      if(match)
      {
        var fp = new FilterPattern(RegExp.$1,this.parenttable.getColumnByName(RegExp.$1),RegExp.$2,RegExp.$3);
        filterarray.push(fp);
      }

    }
    //Apply the filters
    this.view = this.parenttable.Filter(filterarray,this);
    
    //And sort
    var sortstring = this.sort;
    var resort = /(\w+)\s*(asc|desc)?/ig;
    resort.lastIndex = 0;
    resort.test(sortstring)
    if(!RegExp.$2 || RegExp.$2.toLowerCase() == 'asc')
      this.sortasc=true;
    else
      this.sortasc=false;
    
    this.view.Sort(this.parenttable.getColumnByName(RegExp.$1),this.sortasc);

    return this.view;
  }

  DataView.prototype.getDataTable = function()
  {
    this.View()
    return this.view.HTMLTable(this.showcolumns);
  }
    
    
function FilterPattern(columnname,columnnumber,comparison,criteria)
{
  this.ColumnName   = columnname;
  this.ColumnNumber = columnnumber;
  this.Comparison   = comparison.toLowerCase();
  this.Criteria     = criteria;
  //Build regexp
  //Replace * or % with any number of any charater
  var rewildcard    = /\*|%/ig;
  this.regex        = "^" +  criteria.replace(rewildcard,"[a-zA-Z_0-9/ \(\)&\\.'-]*") + "\s*$";
  //new RegExp(regex,"gi");
}
  