//-----------------------------------------------------------------------------
// @CLASS	: DataViewer
// @REQUIRES: Prototype Library version 1.5 (prototype.js)
// @PURPOSE	: Control that builds a table that views and manipulates data.
//-----------------------------------------------------------------------------
var DataViewer = Class.create();
DataViewer.prototype =
{
	initialize: function(containerId, tableId, tableClassName, errorPanelId, dataSourceFunction, modifyFieldFunction, modifyRowFunction)
	{
		this.url = null;
		this.json = null;
		this.items = null;
		this.request = null;
		this.errorCount = 0;
		this.table = null;
		this.columns = 0;
		this.lastUrl = null;
		this.lastSortHeader = null;
		this.disabled = false; // Set this to "disable" the DataViewer
		this.state = {
			sortBy		: '',
			sortDir		: 'asc',
			lastSortBy	: '',
			rows		: null,
			currentRow	: 0,
			totalRows	: null
		};
		if (arguments.length == 1)
			this.init(arguments[0]);
		else
		{	// Backwards compat
			this.containerId = containerId;
			this.tableId = tableId;
			this.tableClassName = tableClassName;
			this.errorPanelId = errorPanelId;
			this.dataSourceFunction = dataSourceFunction;
			this.modifyFieldFunction = modifyFieldFunction;
			this.modifyRowFunction = modifyRowFunction;
			this.beforeLoadFunction = null;
			this.afterLoadFunction = null;
		}
		this.initializeTable(this.tableId);
	},

	init: function(c)
	{
		if (typeof(c) != 'object' || c == null)
			return false;
		this.containerId = this.get(c,'containerId');
		this.tableId = this.get(c, 'tableId', 'dataViewerTable');
		this.tableClassName = this.get(c, 'tableClassName', 'dataViewerTable');
		this.errorPanelId = this.get(c, 'errorPanelId', null);
    this.emptyMessage = this.get(c, 'emptyMessage', 'No records found');
		this.dataSourceFunction = this.get(c, 'dataSourceFunction');
		this.modifyFieldFunction = this.get(c, 'modifyFieldFunction', null);
		this.modifyRowFunction = this.get(c, 'modifyRowFunction', null);
		this.beforeLoadFunction = this.get(c, 'beforeLoadFunction', null);
		this.afterLoadFunction = this.get(c, 'afterLoadFunction', null);
		this.rowHoverClass = this.get(c, 'rowHoverClass', null);
		this.rowHover = (this.rowHoverClass ? true : false);
		this.rowsPerPage = this.state.rows = this.get(c, 'rowsPerPage', 10);
		
    this.deleteItemUrl = this.get(c, 'deleteItemUrl', null);
    this.deleteItemConfirm = this.get(c, 'deleteItemConfirm', 'Delete the highlighted row?  This cannot be undone.');
    this.deleteItemStarted = this.get(c, 'deleteItemStarted', null);
    this.deleteItemFinished = this.get(c, 'deleteItemFinished', null);
    this.deleteIconClass = this.get(c,'deleteIconClass',null);
    this.deleteIconColumn = this.get(c, 'deleteIconColumn', 0);
    this.deleteRowClass = this.get(c,'deleteRowClass', null);
		return true;
	},

	get: function(config, member, defaultValue)
	{
		// If no default provided, then value is required
		if (config && (member in config))
			return eval('config.' + member);
		if (typeof(defaultValue) == 'undefined')
			throw 'DataViewer configuration must contain a value for ' + member;
		return defaultValue;
	},

	load: function(url)
	{
    // this.abort();
		this.lastUrl = url;
		this.state.currentRow = 1;
		this.state.direction = 1;
		this.url = (url ? url : (this.dataSouceFunction ? this.dataSourceFunction(null,this) : null));
		if (this.url) this.request = new Ajax.Request(
			url,
			{
			method: 'get',
			parameters: '',
			onComplete: function(rs) { this.populate(rs.responseText); }.bind(this),
			onException: function(ob, ex) { ajaxHelperOnError(ob, ex, this, this.errorPanelId); }.bind(this)
			});

		// this.deleteAllRows();

		// Notification
		if (this.beforeLoadFunction) this.beforeLoadFunction(this, url);
	},

  abort: function()
  {
    if (this.request != null)
    {
      this.request.abort();
      this.request = null;
    }
  },

	reload: function()
	{
    var url = (this.dataSourceFunction ? this.dataSourceFunction('',this) : this.lastUrl);
		if (url)
		{
      this.load(url);
      return true;
    }
    return false;
	},

	initializeTable: function(tableId)
	{
		this.table = document.createElement('table');
		this.table.id = tableId;
		this.table.className = this.tableClassName;
		var container = $(this.containerId);
		if (container == null)
      throw "No DataViewer container with id=" + this.containerId;
		container.appendChild(this.table);
	},

	deleteAllRows: function(headersToo)
	{
		var g = this.table;
		if (g) while(g.childNodes.length > 0)
		{
			var c = g.childNodes[0];
			if (c && (c.nodeName != 'thead' || headersToo))
				g.removeChild(g.childNodes[0]);
		}
		// this.selectRow(null);
		return true;
	},

	createHeader: function(headers)
	{
		var thead = document.createElement('thead');
		var tr = document.createElement('tr');
		var th = null;
		var header = null;
		for(var i=0; i < headers.length; i++)
		{
			th = document.createElement('th');
			header = headers[i];
      if (header != null) // Happens when an array ends with a comma
      {
        if (header.sort)
        {
          th.className = 'sortable';
          th.setAttribute('sort', header.sort);
          if (this.state.sortBy == header.sort)
          {
            Element.addClassName(th,'sorted');
            this.lastSortHeader = th;
          }
          Event.observe(th, 'click', function(e) {
            if (this.disabled) return;
            var elem = Event.findElement(e,'th');
            var name = (elem ? elem.getAttribute('sort') : '');
            if (this.state.lastSortBy == this.state.sortBy)
              this.state.sortDir = (this.state.sortDir == 'asc' ? 'desc' : 'asc');
            else
            {
              this.state.lastSortBy = this.state.sortBy;
              this.state.sortDir = 'asc';
            }
            this.state.sortBy = name;
            if (this.lastSortHeader)
              Element.removeClassName(this.lastSortHeader,'sorted');
            if (elem)
            {
              Element.addClassName(elem,'sorted');
              this.lastSortHeader = elem;
            }
            this.table.style.cursor = 'wait';
            var url = (this.dataSourceFunction ? this.dataSourceFunction(name,this) : null);
            if (url) this.load(url);
          }.bind(this));

        }
        th.className = 'th' + i;
        th.innerHTML = (header.text ? header.text : '');
        tr.appendChild(th);
      }
		}
		thead.appendChild(tr);
		return thead;
	},

	createRow: function(item, isGroupie)
	{
		var tr = document.createElement('tr');
		var fields = item.fields;
		var isChild = (item.child == 1 ? true : false);
		var td = null;
		var cols = 0;
    if (typeof(fields) == 'undefined') alert('DataViewer.createRow() error: No fields in datasource');

    var cn = '';
    if (isChild) cn += 'childRow ';
    if (isGroupie) cn += 'groupie ';
    if (cn.length > 0) tr.className = cn;

		// Give the callback function a crack at the row
		if (this.modifyRowFunction) this.modifyRowFunction(item,fields,tr,this);
  
    // Want a delete control added?
    var hasDelete = (this.deleteItemUrl && this.deleteIconClass); // BOTH must be set to have a delete contorl

		for(var i=0; i<fields.length; i++)
		{
      var n = null;
      if (hasDelete && this.deleteIconColumn == i)
        n = this.createDeleteControl(item);

      var value = (this.modifyFieldFunction ? this.modifyFieldFunction(item,fields,i,this) : fields[i]);
      if (value != null || n != null)
      {
        cols++;
        td = document.createElement('td');
        td.className = 'td' + i;
        if (typeof(value) == 'object') td.appendChild(value);
        else if (n) { n.innerHTML = value; td.appendChild(n); }
        else td.innerHTML = value; // Can return Markup or DOM node
        tr.appendChild(td);
      }
		}
		if (cols > this.columns)
			this.columns = cols;

    if (this.rowHoverClass)
    {
      Event.observe(tr, 'mouseover', function(ev, tr) { if (this.rowHover && this.disabled == false) Element.addClassName(tr, this.rowHoverClass); }.bindAsEventListener(this, tr));
      Event.observe(tr, 'mouseout', function(ev, tr) { Element.removeClassName(tr, this.rowHoverClass); }.bindAsEventListener(this, tr));
    }
		return tr;
	},

  createDeleteControl: function(item)
  {
    var n = document.createElement('a');
    n.href = 'javascript:void(0)';
    n.title = 'Click here to delete this item';
    n.innerHTML = '&nbsp;';
    if (this.deleteIconClass) n.className = this.deleteIconClass;
    Event.observe(n, 'click', this.deleteItem.bindAsEventListener(this, item));
    return n;
  },
  
  deleteItem: function(event, item)
  {
    var n = Event.element(event);
    if (this.disabled || n == null ||
        typeof(item) == 'undefined' || item == null ||
        this.deleteItemUrl == null ||
        typeof(this.deleteItemUrl) != 'function')
        return;

    event.stop();

    n = this.findAncestor(n, 'TR');
    if (n && this.deleteRowClass)
      Element.addClassName(n, this.deleteRowClass);
    
    var url = this.deleteItemUrl(item);
    if (confirm(this.deleteItemConfirm))
    {
      this.disabled = true;
      if (this.deleteItemStarted) this.deleteItemStarted(item);
      
      if (n) n.parentNode.removeChild(n);
      // ajaxHelperShowError('Deleting...<br/>', null, this.errorPanelId);
      
      this.lastItem = item;
      var request = new Ajax.Request(url, {
        method: 'get',
        onComplete: function(rs) {
            ajaxHelperLoadJson(rs.responseText); // Just make sure it parses
            this.disabled = false;
            ajaxHelperClearErrors(this.errorPanelId);
            if (this.deleteItemFinished) this.deleteItemFinished(true, this.lastItem);
            this.lastItem = null;
        }.bind(this, item),
        onException: function(ob, ex) {
            this.disabled = false;
            ajaxHelperOnError(ob, ex, null, this.errorPanelId);
            try { if (this.deleteItemFinished) this.deleteItemFinished(false); } catch(e) { }
        }.bind(this)
      });      
    }
    else if (n && this.deleteRowClass)
      Element.removeClassName(n, this.deleteRowClass);
  },
  
  findAncestor: function(n, nodeName)
  {
    nodeName = nodeName.toUpperCase();
    while(n != null)
    {
      if (n.nodeName.toUpperCase() == nodeName) return n;
      n = n.parentNode;
    }
    return null;
  },
  
	populate: function(json, keepPagination)
	{
		var items = ajaxHelperLoadJson(json);
		if (items) items = items.data;
		else return; // Ignore when nothing came back in the json

		this.json = json;
		this.items = items;

		// By default, reset the pagination
		if (!keepPagination) this.state.currentRow = 0;

		var tbody = document.createElement('tbody');
		var tr = null;
		var item = null;
		var count = 0;
		var found = 0;
		var thead = null;
		var rows = this.state.rows;
		var group = null;
		var lastGroup = null;
		this.state.extraRows = 0;
		this.state.totalRows = items.length;

		// The Header, if present, should be the first item
		if (items.length > 0 &&
        typeof(items[0].header) == 'object')
        thead = this.createHeader(items[0].header);

		// Make sure the start point isn't out of bounds
    var trs = Array(); // Array of TRs
    var dir = (this.state.direction < 0 ? -1 : 1);
    var i = (rows > 0 ? (this.state.currentRow+(dir > 0 ? 1 : 0)) : 0); // starting index
    while(i > 0 && i < items.length)
    {
      item = items[i];
      group = (typeof(item['group']) != 'undefined' && item['group']) ? item['group']: null;

      tr = this.createRow(item, (group && group == lastGroup));
      if (rows > 0 && count > (rows-1))
      {
        if (group && group == lastGroup) this.state.extraRows++; // Keep adjacent groups together
        else break;
      }
      lastGroup = group;
      trs[trs.length] = tr;
      count++; found++;
      items[i] = null; // Clean up memory
      i += dir;
    }

		// New current row
		if (dir < 0) this.state.currentRow = i;

    var initial = (dir < 0 ? (trs.length-1) : 0);
    for(i = initial; i >= 0 && i < trs.length; i+=dir) tbody.appendChild(trs[i]);
    trs = null;

		this.deleteAllRows();

		if (found <= 0)
		{
			var tb = document.createElement('tbody');
			var tr = document.createElement('tr');
			var td = document.createElement('td');
			td.innerHTML = this.emptyMessage;
			tr.className = 'empty';
			tr.appendChild(td);
			tbody.appendChild(tr);
		}
		else if (thead)
		{
			this.table.appendChild(thead);
    }
    
    // Pagination?
    var needsPrev = (this.state.currentRow > 0);
    var needsNext = (this.state.rows &&
          (this.state.currentRow + this.state.rows + this.state.extraRows) < (this.state.totalRows-1));
    
    if (found > 0 && this.state.rows > 0 && (needsPrev || needsNext))
    {
      var tr = document.createElement('tr');
      var an = null;

      // Prev paginator control
      var td = document.createElement('td');
      td.colSpan = (this.columns > 2 ? Math.ceil(this.columns/2) : 1);
      td.className = 'paginator left';
      if (needsPrev)
      {
        an = document.createElement('a');
        an.href='#';
        Event.observe(an, 'click', function(e) {
          if (this.disabled) return;
          if (this.state.currentRow <= 0) this.state.currentRow = 1;
          this.state.direction = -1;
          this.populate(this.json, true);
          Event.stop(e);
        }.bind(this));
        an.innerHTML = '&lt;&lt; Previous ' + (this.state.rows) + ' of ' + (items.length-1);
        td.appendChild(an);
      }
      tr.appendChild(td);

      // Next paginator control
      td = document.createElement('td');
      td.colSpan = (this.columns > 2 ? (this.columns - Math.ceil(this.columns/2)) : 1);
      td.className = 'paginator right';
      if (needsNext)
      {
        an = document.createElement('a');
        an.href='#';
        Event.observe(an, 'click', function(e) {
          if (this.disabled) return;
          this.state.currentRow += (this.state.rows + this.state.extraRows);
          this.state.direction = 1;
          this.populate(this.json, true);
          Event.stop(e);
        }.bind(this));
        an.innerHTML = 'Next ' + (this.state.rows) + ' of ' + (items.length-1) + ' &gt;&gt;';
        td.appendChild(an);
      }
      tr.appendChild(td);

      tbody.appendChild(tr);
    }

		this.table.appendChild(tbody);
		this.table.style.cursor = 'pointer';

		// Notification
		if (this.afterLoadFunction) this.afterLoadFunction(this, tbody);
	}
};


