

var ts_version = "1.30";
var ts_browser_agt = navigator.userAgent.toLowerCase();   
var ts_browser_is_ie = ((ts_browser_agt.indexOf("msie") != -1) && (ts_browser_agt.indexOf("opera") == -1));

var ml_tsort = {
  ///////////////////////////////////////////////////
  // configurable constants, modify as needed!
  sort_col_title : "Klik hier om te sorteren!",
  sort_col_asc_title : "Sorted in ascending order",
  sort_col_desc_title : "Sorted in descending order",
  sort_col_class : "abc",
  sort_col_style : "text-decoration:none; font-weight:bold; color:black",
  sort_col_class_post_sort : "def",
  sort_col_style_post_sort : "text-decoration:none; font-weight:bold; color:red",
  sort_col_mouseover : "this.style.color='blue'",
  use_ctrl_alt_click : true,
  sort_only_sortable : true,
  minimum_sort_row : 4,
  no_sort_above_click : false,
  default_sort_asc : true,
  forcesort_msg : 'This column can only be sorted in one way!',
  nosort_title : 'No sorting on this column!',
  cookie_days : 0, 


  default_date_style : 'euro', // assume 'us' or 'euro'(pean) style of date format when sorting time column
  table_head_row : 0, // do not sort the first ? number of rows of the table with headers
  table_nohead_row : 1, // do not sort the first ? number of rows of the table without headers
  skiprows : 1, // internally used, don't edit this one!


  table_content_might_change : false, 
  preserve_style : "",
  tell_me_time_consumption : false,
  

  smallest_int : -2147483648000, // date parse is in milliseconds, hence the 000
  cookie_table_idx : 0, // used to generate unique id when cookie's enabled by user but user did not give each table a unique id (the recommended way)
  
  get_doc_name : function()
  {
    var regex = /.+\/(.+)\??/;
    var results = regex.exec(window.location.href);
    if(results) return results[1].replace(/[^A-Za-z0-9]+/g, '');
  },
  
  set_vars : function(event)
  {
    var e = (event)? event : window.event;
    var element = (event)? ((event.target)? event.target : event.srcElement) : window.event.srcElement;
    ml_tsort.clicked_td = ml_tsort.getParent(element,'TD') || ml_tsort.getParent(element,'TH');
    ml_tsort.clicked_table = ml_tsort.getParent(element,'TABLE');

    if(!ml_tsort.clicked_table || ml_tsort.clicked_table.rows.length < 1 || !ml_tsort.clicked_td) return;
    var clicked_cell = ml_tsort.clicked_table.rows[0].cells[ml_tsort.clicked_td.cellIndex];
    if (e.ctrlKey && e.altKey && ml_tsort.use_ctrl_alt_click) ml_tsort.resortTable(clicked_cell);
  },
  
  set_skiprows : function(td)
  {
    ml_tsort.clicked_tr = ml_tsort.getParent(td,'TR');
    ml_tsort.clicked_table = ml_tsort.getParent(td,'TABLE');
    if(ml_tsort.clicked_tr)
    {
      if(ml_tsort.no_sort_above_click)
      {
        ml_tsort.skiprows = 0;
        if(ml_tsort.clicked_table && ml_tsort.clicked_table.rows.length >= ml_tsort.minimum_sort_row)
        {
          for (var j=0; j < ml_tsort.clicked_table.rows.length; j++)
          {
            if(ml_tsort.clicked_table.rows[j] == ml_tsort.clicked_tr) break;
            if(ml_tsort.clicked_table.rows[j].parentNode.tagName.toLowerCase() == 'tbody')
              ml_tsort.skiprows++;
          }
        }
      }
      else
        ml_tsort.skiprows = (ml_tsort.table_has_header(ml_tsort.clicked_table))? ml_tsort.table_head_row : ml_tsort.table_nohead_row;
    }
  },

  makeSortable: function(table) 
  {
    if (table.rows && table.rows.length > 0) 
    {
      var rowidx = table.getAttribute("ts_linkrow") || 0;
      var firstRow = table.rows[rowidx];
    }
    if (!firstRow) return;
    var sortCell;
    // make clickable links on the row
    if(this.cookie_days > -1) 
    {
      if(!table.id)
      {
        this.cookie_table_idx = this.cookie_table_idx + 1;
        table.cookieid = this.get_doc_name() + this.cookie_table_idx;
      }
      else table.cookieid = this.get_doc_name() + table.id;
      var tmp = this.get_cookie(table.cookieid);
      if(tmp)
      {
        var oldVals = tmp.split(':');
        sortCell = firstRow.cells[oldVals[0]];
        sortCell.setAttribute('sortdir', oldVals[1]);
      }
    }
    for (var i=0;i<firstRow.cells.length;i++) 
    {
      var cell = firstRow.cells[i];
      if(cell.getAttribute("ts_nosort"))
      {
        if(this.nosort_title) cell.innerHTML = '<span title="'+ this.nosort_title +'">' + cell.innerHTML + '</span>';
      }
      else
      {
        var txt = cell.innerHTML;
        if(cell.getAttribute("sortdir")) 
        {
          if(sortCell) cell.removeAttribute('sortdir');
          else sortCell = cell;
        }
        cell.innerHTML = '<a style="'+this.sort_col_style+'" onMouseOver="this.oldstyle=this.style.cssText;'+this.sort_col_mouseover+'" onMouseOut="this.style.cssText=this.oldstyle;" class="'+this.sort_col_class+'" href="#" title="'+this.sort_col_title+'" onclick="javascript:ml_tsort.resortTable(this.parentNode);return false">'+txt+'</a>';
      }
    }
    if(sortCell) this.resortTable(sortCell);
  },
  
  sortables_init : function() 
  {
      // Find all tables with class sortable and make them sortable
      if (!document.getElementsByTagName) return;
      var tbls = document.getElementsByTagName("table");
      for (var ti=0;ti<tbls.length;ti++) {
          thisTbl = tbls[ti];
          if(!ml_tsort.sort_only_sortable || thisTbl.className.match(/sortable/i))
            ml_tsort.makeSortable(thisTbl);
      }
  },
  
  getParent : function(el, pTagName) 
  {
  	if (el == null) return null;
  	else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
  		return el;
  	else
  		return this.getParent(el.parentNode, pTagName);
  },
  
  getInnerText : function(el) 
  {
  	if (typeof el == "string") return el;
  	if (typeof el == "undefined") { return el };
  	if (el.innerText) return el.innerText;	//Not needed but it is faster
  	var str = "";
  	
  	var cs = el.childNodes;
  	var l = cs.length;
  	for (var i = 0; i < l; i++) {
  		switch (cs[i].nodeType) {
  			case 1: //ELEMENT_NODE
  				str += this.getInnerText(cs[i]);
  				break;
  			case 3:	//TEXT_NODE
  				str += cs[i].nodeValue;
  				break;
  		}
  	}
  	return str;
  },
  
  match_date_format : function(value, format)
  {
    format = format.replace(/\//g, '[-,. \\/]');
    this.set_date_array(format);
    if(!isNaN(this.convert_date(this.getInnerText(value.cells[this.sort_column_index])))) return true;
    return false;
  },
    
  get_sortfn : function(warn_type, user_type)
  {
    var sortfn = this.sort_caseinsensitive;
    var table = this.clicked_table;
    var td = this.clicked_td;
    var column = this.sort_column_index;
    if(!table || table.rows.length < ml_tsort.minimum_sort_row || !td) return sortfn;
    var type = user_type || table.rows[0].cells[column].getAttribute("ts_type");
    this.replace_pattern = '';

    // find first non-empty data cell
    var itm;
    for(var rowcount = 0, i = 0; i < table.rows.length; i++)
    {
      if(rowcount++ < ml_tsort.skiprows) continue; // skip head rows
      var t = table.rows[i].parentNode.tagName.toLowerCase();
      if(t == 'tbody' && table.rows[i].cells.length >= column + 1)
      {
        itm = this.getInnerText(table.rows[i].cells[column]);
        if(itm.match(/\S/)) break;
      }
    }
    if(i == table.rows.length) return sortfn;
    itm = ml_trim(itm);
    // Work out a type for the column if necessary
    if(!type || (!user_type && this.table_content_might_change)) // if table might change and the type's not set by user at this moment, then we can't rely on previously set type
    {
      // let's try really hard to check dates separated by - or / or .
      if (this.default_date_style == 'euro' && this.match_date_format(table.rows[i], 'D/M/Y'))
      { sortfn = this.sort_date; type = 'euro_date' }
      else if (this.match_date_format(table.rows[i], 'M/D/Y'))
      { sortfn = this.sort_date; type = 'date' }
      else if (itm.match(/^[ĄŁ€$]/))
      { sortfn = this.sort_currency; type = 'money' }
      else if (itm.match(/^\d{1,3}(\.\d{1,3}){3}$/))
      { sortfn = this.sort_ip; type = 'ip' }
      else if (itm.match(/^[+-]?\s*[0-9]+(?:\.[0-9]+)?(?:\s*[eE]\s*[+-]?\s*\d+)?$/))
      { sortfn = this.sort_numeric; type = 'number' }
    }
    else if(type == 'string') sortfn = this.sort_caseinsensitive;
    else if(type == 'date')
    {
      if(this.match_date_format(table.rows[i], 'M/D/Y')) sortfn = this.sort_date;
    }
    else if(type == 'euro_date')
    {
      if(this.match_date_format(table.rows[i], 'D/M/Y')) sortfn = this.sort_date;
    }
    else if(type == 'year_month_date')
    {
      if(this.match_date_format(table.rows[i], 'Y/M/D')) sortfn = this.sort_date;
      
    }
    else if(type == 'year_date_month')
    {
      if(this.match_date_format(table.rows[i], 'Y/D/M')) sortfn = this.sort_date;
    }
    else if(type == 'other_date')
    {
      this.set_date_array(td.getAttribute("ts_date_format").replace(/\//g, '[-. \\/]'));
      sortfn = this.sort_date;
    }
    else if(type == 'number') sortfn = this.sort_numeric;
    else if(type == 'ip') sortfn = this.sort_ip;
    else if(type == 'money') sortfn = this.sort_currency;
//       else if(type == 'custom') sortfn = function(aa,bb) { a = this.getInnerText(aa.cells[this.sort_column_index]); b = this.getInnerText(bb.cells[this.sort_column_index]); eval(td.getAttribute("ts_sortfn")) }; // the coding here is shorter but interestingly it's also slower
    else if(type == 'custom') { this.custom_code = td.getAttribute("ts_sortfn"); sortfn = this.custom_sortfn }
    else if(warn_type) { alert("unsupported sorting type!"); }
    table.rows[0].cells[column].setAttribute("ts_type", type); // store type for the convenience of both program and user (no need to use context menu to set type again)
    return sortfn;
  },

  table_has_header : function(table)
  {
    if(table.tHead && table.tHead.rows.length > 0) return true;
    return false;
  },

  resortTable : function(td) 
  {
    if(td == null) return;
    var column = td.cellIndex;
    var table = this.getParent(td,'TABLE');
    this.sort_column_index = column;
    
    // let's make a decision that ts_ attributes should always be denoted on first row (not linkrow) for user convenience.
    if(table.rows[0].cells[column].getAttribute("ts_nosort")) return;

    this.clicked_table = table;
    if(table == null || table.rows.length < ml_tsort.minimum_sort_row) return;
//     this.set_skiprows(this.clicked_td || td);
    this.set_skiprows(td);
    this.clicked_td = td; // this order is important when user specify no_sort_above_click

    // set the data rows
    var newRows = new Array();
    var headcount = 0;
    for (var i=0,j=0,rowcount=0;j<table.rows.length;j++)
    {
      var t = table.rows[j].parentNode.tagName.toLowerCase();
      if(t == 'tbody')
      {
        if(rowcount++ < ml_tsort.skiprows) continue;
        else if(table.rows[j].cells.length >= column + 1) newRows[i++] = table.rows[j];
      }
      else if(t == 'thead') headcount++;
    }
    if(newRows.length == 0) return;
    var time2 = new Date();
  
    // now let's do a lot just to save a little time, if possible at all. ;)
    var lastSortCell = table.getAttribute("ts_sortcell") || 0;
    lastSortCell--; // the processing is used for IE, which treats no attribute as 0, while FF treats 0 as still true.
    var lastSkipRows = table.getAttribute("ts_skiprows") || -1;
    var lastSortType = table.rows[0].cells[column].getAttribute("ts_type");
    var sortingSameColumnAndRow = (table == this.last_sorted_table && column == lastSortCell
      && (!ml_tsort.no_sort_above_click || ml_tsort.skiprows == lastSkipRows));
    table.setAttribute('ts_skiprows',ml_tsort.skiprows);

    var initSortDir = td.getAttribute("sortdir");
    var lastSortDir;
    if(td.getAttribute("ts_forcesort"))
      lastSortDir = (td.getAttribute("ts_forcesort") == 'desc')? 'asc' : 'desc';
    else
      lastSortDir = (sortingSameColumnAndRow)? table.getAttribute("ts_sortdir") : (
                    (initSortDir == 'desc')? 'asc' : (initSortDir == 'asc')? 'desc' : (
                    (this.default_sort_asc)? 'desc' : 'asc'));
      
    // check if we really need to sort
    if(!td.getAttribute("ts_forcesort") && !this.table_content_might_change && sortingSameColumnAndRow)
      newRows.reverse();
    else if(td.getAttribute("ts_forcesort") && !this.table_content_might_change 
            && sortingSameColumnAndRow && td.getAttribute("ts_forcesort") == table.getAttribute("ts_sortdir")) 
    { if(this.forcesort_msg) alert(this.forcesort_msg); return; }  
    else
    {
      var sortfn = this.get_sortfn(true);  
      table.setAttribute("ts_sortcell", column+1);
      newRows.sort(sortfn);
      if (lastSortDir == 'asc') newRows.reverse();
    }
    // set style of heading
    var rowidx = table.getAttribute("ts_linkrow") || 0;
    if(lastSortCell > -1 && table.rows[rowidx].cells[lastSortCell].firstChild.style)
    {
      table.rows[rowidx].cells[lastSortCell].firstChild.oldstyle = this.sort_col_style;
      table.rows[rowidx].cells[lastSortCell].firstChild.style.cssText = this.sort_col_style;
      table.rows[rowidx].cells[lastSortCell].firstChild.className = this.sort_col_class;
    }
    if(table.rows[rowidx].cells[column].firstChild.style)
    {
      table.rows[rowidx].cells[column].firstChild.oldstyle = this.sort_col_style_post_sort;
      table.rows[rowidx].cells[column].firstChild.style.cssText = this.sort_col_style_post_sort;
      table.rows[rowidx].cells[column].firstChild.className = this.sort_col_class_post_sort;
    }
    if (lastSortDir == 'desc') table.setAttribute('ts_sortdir','asc');
    else table.setAttribute('ts_sortdir','desc');
    // set cookie if needed
    if(this.cookie_days > -1) this.set_cookie(table.cookieid, column + ':' + table.getAttribute('ts_sortdir'));
    // has to use tagName otherwise IE complains
    if(lastSortCell > -1 && table.rows[rowidx].cells[lastSortCell].firstChild.tagName) table.rows[rowidx].cells[lastSortCell].firstChild.title = this.sort_col_title;
    if(table.rows[rowidx].cells[column].firstChild.tagName) table.rows[rowidx].cells[column].firstChild.title = ((lastSortDir == 'desc')? this.sort_col_asc_title : this.sort_col_desc_title);

    this.last_sorted_table = table;
       
    var time3 = new Date();
    
    var ps = table.getAttribute("preserve_style") || this.preserve_style;
    if(ps == 'row' && !ts_browser_is_ie) 
    {
      var tmp = new Array(newRows.length);
      for (var i = 0; i < newRows.length; i++) tmp[i] = newRows[i].innerHTML;
      for (var i = 0; i < newRows.length; i++) table.rows[i+headcount+ml_tsort.skiprows].innerHTML = tmp[i];
    }
    else if(ps == 'cell' || (ps == 'row' && ts_browser_is_ie)) 
    {
      var tmp = new Array(newRows.length);
      for (var i = 0; i < newRows.length; i++)
        for (var j = 0; j < newRows[i].cells.length; j++)
        {
          if(!tmp[i]) tmp[i] = new Array(newRows[i].cells.length);
          tmp[i][j] = newRows[i].cells[j].innerHTML;
        }
      for (var i = 0; i < newRows.length; i++)
        for (var j = 0; j < newRows[i].cells.length; j++)
          table.rows[i+headcount+ml_tsort.skiprows].cells[j].innerHTML = tmp[i][j];
    }
    else
    {
      for (var i=0;i<newRows.length;i++) // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
        table.tBodies[0].appendChild(newRows[i]);
    } 
    var time4 = new Date();
    if(this.tell_me_time_consumption)
    {
      alert('it took ' + this.diff_time(time3, time2) + ' seconds to do sorting!');
      alert('it took ' + this.diff_time(time4, time3) + ' seconds to do redrawing!');
    }
  },
  
  diff_time : function(time2, time1) 
  {
    return (time2.getTime() - time1.getTime())/1000;
  },
  
  // Mingyi Note: it seems ridiculous to do so much processing for
  // customizable date conversion, should try to find a zbetter way
  // of doing it.
  set_date_array : function(f) 
  {
    var tmp = [['D', f.indexOf('D')], ['M', f.indexOf('M')], ['Y', f.indexOf('Y')]];
    tmp.sort(function(a,b){ return a[1] - b[1]});
    this.date_order_array = new Array(3);
    for(var i = 0; i < 3; i++) this.date_order_array[tmp[i][0]] = '$' + (i + 2);
    this.replace_pattern = f.replace(/[DMY]([^DMY]+)[DMY]([^DMY]+)[DMY]/, '^(.*?)(\\d+)$1(\\d+)$2(\\d+)(.*)$');
  },
  
  process_year : function(y) 
  {
    var tmp = parseInt(y);
    if(tmp < 32) return '20' + y; 
  	else if(tmp < 100) return '19' + y;
  	else return y;
  },
  
  // convert to MM/DD/YYYY (or M/D/YYYY) format
  convert_date : function(a)
  {
    var re = 'RegExp.$1+RegExp.'+this.date_order_array['M']+'+\'/\'+RegExp.'+this.date_order_array['D']+'+\'/\'+this.process_year(RegExp.'+this.date_order_array['Y']+')+RegExp.$5';
    var code = 'if(a.match(/'+this.replace_pattern+'/)) (' + re + '); else a;';
    return Date.parse(eval(code));
  },
  
  sort_date : function(a,b) 
  {
    var atext = ml_tsort.getInnerText(a.cells[ml_tsort.sort_column_index]);
    var btext = ml_tsort.getInnerText(b.cells[ml_tsort.sort_column_index]);
    var aa, bb;
    // basically I have to do the conversion due to the potential usage of double digit years
    if(atext && atext.match(/\S/))
    {
      aa = ml_tsort.convert_date(atext);
      if(isNaN(aa)) aa = Date.parse(atext);
      if(isNaN(aa)) aa = 0;
    }
    else aa = ml_tsort.smallest_int;
    if(btext && btext.match(/\S/))
    {
      bb = ml_tsort.convert_date(btext);
      if(isNaN(bb)) bb = Date.parse(btext);
      if(isNaN(bb)) bb = 0;
    }
    else bb = ml_tsort.smallest_int;
    return aa - bb;
  },
  
  // assume no scientific number in currency (if assumption incorrect, just use
  // same code for this.sort_numeric will do)
  sort_currency : function(a,b) 
  { 
      return ml_tsort.sort_num(ml_tsort.getInnerText(a.cells[ml_tsort.sort_column_index]).replace(/[^-0-9.+]/g,''),
                         ml_tsort.getInnerText(b.cells[ml_tsort.sort_column_index]).replace(/[^-0-9.+]/g,''));
  },
  
  // let's allow scientific notation but also be strict on number format
  sort_num : function(a, b) 
  {
      var aa, bb;
      if(a && a.match(/\S/))
      {
        if(!isNaN(a)) aa = a;
        else if(a && a.match(/^[^0-9.+-]*([+-]?\s*[0-9]+(?:\.[0-9]+)?(?:\s*[eE]\s*[+-]?\s*\d+)?)/))
          aa = parseFloat(RegExp.$1.replace(/\s+/g, ''));
        else aa = 0;
      }
      else aa = ml_tsort.smallest_int;
      if(b && b.match(/\S/))
      {
        if(!isNaN(b)) bb = b;
        else if(b && b.match(/^[^0-9.+-]*([+-]?\s*[0-9]+(?:\.[0-9]+)?(?:\s*[eE]\s*[+-]?\s*\d+)?)/))
          bb = parseFloat(RegExp.$1.replace(/\s+/g, ''));
        else bb = 0;
      }
      else bb = ml_tsort.smallest_int;
      return aa - bb;
  },
  
  sort_numeric : function(a,b) 
  {
      return ml_tsort.sort_num(ml_tsort.getInnerText(a.cells[ml_tsort.sort_column_index]),
                         ml_tsort.getInnerText(b.cells[ml_tsort.sort_column_index])); 
  },
  
  sort_ip : function(a,b) 
  {
      var aa = ml_tsort.getInnerText(a.cells[ml_tsort.sort_column_index]).split('.');
      var bb = ml_tsort.getInnerText(b.cells[ml_tsort.sort_column_index]).split('.');
      return ml_tsort.sort_num(aa[0], bb[0]) || ml_tsort.sort_num(aa[1], bb[1]) || 
             ml_tsort.sort_num(aa[2], bb[2]) || ml_tsort.sort_num(aa[3], bb[3]);
  },
   
  sort_caseinsensitive : function(a,b) 
  {
      var aa = ml_tsort.getInnerText(a.cells[ml_tsort.sort_column_index]).toLowerCase();
      var bb = ml_tsort.getInnerText(b.cells[ml_tsort.sort_column_index]).toLowerCase();
      if (aa==bb) return 0;
      if (aa<bb) return -1;
      return 1;
  },
  
  custom_sortfn : function(aa,bb) 
  {
    var a = ml_tsort.getInnerText(aa.cells[ml_tsort.sort_column_index]);
    var b = ml_tsort.getInnerText(bb.cells[ml_tsort.sort_column_index]);
    return eval(ml_tsort.custom_code);
  },
  
  set_cookie : function(name, value)
  {
  	var date = new Date();
  	var expires = '';
  	if(ml_tsort.cookie_days > 0)
  	{
  	  date.setTime(date.getTime() + ml_tsort.cookie_days * 24 * 360000);
  		expires = '; expires=' + date.toGMTString();
  	}
  	document.cookie = name + '=' + value + expires;
  },
  
  get_cookie : function(name)
  {
    if(document.cookie)
    {
      var st = document.cookie.indexOf(name);
      if (st > -1)
      {
        st = st + name.length + 1; 
        var end = document.cookie.indexOf(';', st);
        if(end == -1) end = document.cookie.length;
        return unescape(document.cookie.substring(st, end));
      }
    }
  }

};

function ml_trim(text)
{
  if(!text) return text;
  var tmp = text.replace(/^\s+/, '');
  return tmp.replace(/\s+$/, '');
}

function ts_addEvent(elm, evType, fn, useCapture)
// addEvent and removeEvent
// cross-browser event handling for IE5+,  NS6 and Mozilla
// By Scott Andrew
{
  if (elm.addEventListener){
    elm.addEventListener(evType, fn, useCapture);
    return true;
  } else if (elm.attachEvent){
    var r = elm.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be removed");
    return false;
  }
}
ts_addEvent(document, "click", ml_tsort.set_vars);
ts_addEvent(window, "load", ml_tsort.sortables_init);

