﻿// © 2007-2009, Applied Geographics, Inc.  All rights reserved.

// Define our namespaces
Type.registerNamespace("Ag");
Type.registerNamespace("Ag.UI");

// Ag.UI.GridViewRowMode (enum)

Ag.UI.GridViewRowMode = function() {
  /// <field name="readOnly" type="Number" integer="true" static="true">Use the item template to display row data</field>
  /// <field name="edit" type="Number" integer="true" static="true">Use the edit template to let the user modify row data</field>
  if (arguments.length !== 0) throw Error.parameterCount();
  throw Error.notImplemented();
}

Ag.UI.GridViewRowMode.prototype = {
  readOnly: 0,
  edit: 1
}

Ag.UI.GridViewRowMode.registerEnum("Ag.UI.GridViewRowMode");

// Ag.UI.GridViewSelectionMode (enum)

Ag.UI.GridViewSelectionMode = function() {
  /// <field name="none" type="Number" integer="true" static="true">Rows cannot be selected</field>
  /// <field name="single" type="Number" integer="true" static="true">Only one row can be selected at a time</field>
  /// <field name="multiple" type="Number" integer="true" static="true">Multiple rows can be selected</field>
  if (arguments.length !== 0) throw Error.parameterCount();
  throw Error.notImplemented();
}

Ag.UI.GridViewSelectionMode.prototype = {
  none: 0,
  single: 1,
  multiple: 2
}

Ag.UI.GridViewSelectionMode.registerEnum("Ag.UI.GridViewSelectionMode");

// Ag.UI.GridViewSortEventArgs (class)

Ag.UI.GridViewSortEventArgs = function(sortDirection, sortExpression) {
  /// <param name="sortDirection" type="String">ASC or DESC</param>
  /// <param name="sortExpression" type="String">Sort expression defined in the column field</param>
  Ag.UI.GridViewSortEventArgs.initializeBase(this);

  this.sortDirection = sortDirection;
  this.sortExpression = sortExpression;
}

Ag.UI.GridViewSortEventArgs.prototype = {

  // Public properties

  get_sortDirection: function() {
    /// <value type="String">You can also use the public field sortDirection</value>
    return this.sortDirection;
  },

  get_sortExpression: function() {
    /// <value type="String">You can also use the public field sortExpression</value>
    return this.sortExpression;
  }
}

Ag.UI.GridViewSortEventArgs.registerClass("Ag.UI.GridViewSortEventArgs", Sys.EventArgs);

// Ag.UI.GridView (control)

Ag.UI.GridView = function(element) {
  /// <summary>The GridView's constructor</summary>
  /// <param name="element">A table or an element containing one or more tables</param>
  /// <remarks>
  /// sort is not a supported property for data binding but it is
  /// supported as data-sort on th elements as a way to specify the sort expression.
  /// </remarks>
  Ag.UI.GridView.initializeBase(this, [element]);

  // Private Members

  this._allowSorting = false;
  this._alternatingRowCssClass = "";
  this._currentRowMode = Ag.UI.GridViewRowMode.readOnly;
  this._dataBound = false;
  this._fillEmptyWithSpace = false;
  this._container = null;
  this._containerCount = 0;
  this._editIndex = -1;
  this._editRowSource = null;
  this._editTemplate = null;
  this._itemTemplate = null;
  this._selectedIndexes = [];
  this._selectedRowCssClass = "";
  this._selectionMode = Ag.UI.GridViewSelectionMode.none;
  this._sortExpression = "";
  this._sortAsc = true;

  this._th_click_delegate = Function.createDelegate(this, this._th_click);
  this._tr_click_delegate = Function.createDelegate(this, this._tr_click);

  //this._chunkSize = 128;
}

Ag.UI.GridView.prototype = {

  // Public Methods

  initialize: function() {
    /// <summary>Check for templates</summary>
    Ag.UI.GridView.callBaseMethod(this, "initialize");

    var container = this.get_element();
    var jTemp = $(container).find("tr[data-template='item']");

    if (jTemp.length > 0) {
      var template = jTemp.get(0);
      this._itemTemplate = template.cloneNode(true);
      $common.setVisible(this._itemTemplate, true);
      this._container = template.parentNode;
      $common.removeElement(template);

      jTemp = $(this._container).find("tr[data-template='edit']");

      if (jTemp.length > 0) {
        template = jTemp.get(0);
        this._editTemplate = template.cloneNode(true);
        $common.setVisible(this._editTemplate, true);
        $common.removeElement(template);
      }

      this._containerCount = this._container.rows.length;
    }

    if (this._allowSorting) {
      var headerCells = container.getElementsByTagName("th");

      for (var i = 0; i < headerCells.length; i++) {
        var sortExpression = null;
        var sortNode = headerCells[i].attributes.getNamedItem("data-sort");

        if (sortNode != null) {
          sortExpression = sortNode.nodeValue;
        }
        else if (typeof (headerCells[i].abbr) !== "undefined") {
          sortExpression = headerCells[i].abbr;
        }

        if (sortExpression != null && sortExpression.length > 0) {
          headerCells[i].sortExpression = sortExpression;
          $addHandler(headerCells[i], "click", this._th_click_delegate);
        }
      }
    }
  },

  dispose: function() {
    /// <summary>Dispose this control</summary>
    /// <remarks>
    /// Clear any header handlers
    /// </remarks>
    Ag.UI.GridView.callBaseMethod(this, "dispose");
  },

  clearSelection: function() {
    /// <summary>Clears the current selection</summary>

    for (var i = 0; i < this._selectedIndexes.length; ++i) {
      var rowIndex = this._selectedIndexes[i] + this._containerCount;
      Sys.UI.DomElement.removeCssClass(this._container.rows[rowIndex], this._selectedRowCssClass);
    }

    this._selectedIndexes = [];
  },

  addRow: function(index) {
    /// <summary>Adds an empty row to the GridView and a corresponding null to its data source</summary>
    /// <remarks>
    /// If an index is not provided, the end of the table is assumed.  Returns the index of the added row.
    /// New rows can be added while a row is in edit mode.
    /// </remarks>
    var fn = "addRow()";
    this._checkDataBound(fn);

    if (index == null) {
      index = this.get_rowCount();
    }
    else {
      this._checkIndex(fn, index, this.get_rowCount());
    }

    var newRow = this._itemTemplate.cloneNode(true);

    if (index == this.get_rowCount()) {
      this._dataSource.push(null);
      this._container.appendChild(newRow);
    }
    else {
      this._dataSource.splice(index, 0, null);
      this._container.insertBefore(newRow, this._container.rows[index + this._containerCount]);
    }

    this._shadeAlternating(this._container, index, this._containerCount);
    this._shiftSelection(index, 1);

    if (this._editIndex >= index) {
      this._editIndex += 1;
    }

    return index;
  },

  dataBind: function() {
    /// <summary>Bind the data to the GridView</summary>
    /// <remarks>
    /// All old cells are removed, the selection is cleared, and row editing ends.
    /// </remarks>
    this._currentRowMode = Ag.UI.GridViewRowMode.readOnly;
    this._dataBound = true;
    this._editIndex = -1;
    this._editRowSource = null;
    this._selectedIndexes = [];

    while (this._container.rows.length > this._containerCount)
      this._container.deleteRow(this._container.rows.length - 1);

    if (this._dataSource != null) {
      var fragment = document.createDocumentFragment();

      for (var i = 0; i < this._dataSource.length; i++) {
        var newRow = this._itemTemplate.cloneNode(true);

        this._dataBindNodes(newRow, this._dataSource[i]);
        this._fillEmpties(newRow);

        if (this._selectionMode > Ag.UI.GridViewSelectionMode.none) {
          $addHandler(newRow, "click", this._tr_click_delegate);
        }

        fragment.appendChild(newRow);
      }

      this._shadeAlternating(fragment, 0, 0);
      this._container.appendChild(fragment);
    }
  },

  dataBindRow: function(index, a2, a3) {
    /// <summary>Binds data to a row in the GridView and sets its edit mode</summary>
    /// <remarks>
    /// The default action is to bind data from the GridView's data source to the row in read-only mode.
    /// You can optionally provide an edit mode, an alternative row source object for binding, or both.
    /// To save an edited row, use get_editObject to retrieve the edits as an object, then bind the 
    /// row in read-only mode with that object.  Only one row in the GridView can be edited at a time.
    /// </remarks>
    var fn = "dataBindRow()";
    this._checkDataBound(fn);
    this._checkIndex(fn, index);

    var mode = Ag.UI.GridViewRowMode.readOnly;
    var rowSource = this._dataSource[index];

    switch (arguments.length) {
      case 2:
        if (typeof a2 == 'number') {
          mode = a2;
        }
        else {
          rowSource = a2;
        }
        break;

      case 3:
        mode = a2;
        rowSource = a3;
        break;
    }

    var newRow;

    if (mode == Ag.UI.GridViewRowMode.edit) {
      if (this._currentRowMode == mode) {
        throw Error.invalidOperation("Cannot edit a row while another is currently in edit mode");
      }

      this._editIndex = index;
      this._editRowSource = rowSource;
      newRow = this._editTemplate.cloneNode(true);
    }
    else {
      if (index == this._editIndex) {
        this._editIndex = -1;
        this._editRowSource = null;
      }

      this._dataSource[index] = rowSource;
      newRow = this._itemTemplate.cloneNode(true);
    }

    this._dataBindNodes(newRow, rowSource);
    this._fillEmpties(newRow);

    if (this._alternatingRowCssClass != null && index % 2 == 1) {
      Sys.UI.DomElement.addCssClass(newRow, this._alternatingRowCssClass);
    }

    if (this._selectedRowCssClass != null && Array.contains(this._selectedIndexes, index)) {
      Sys.UI.DomElement.addCssClass(newRow, this._selectedRowCssClass);
    }

    if (this._selectionMode > Ag.UI.GridViewSelectionMode.none) {
      $addHandler(newRow, "click", this._tr_click_delegate);
    }

    this._container.insertBefore(newRow, this._container.rows[index + this._containerCount]);
    this._container.removeChild(this._container.rows[index + this._containerCount + 1]);
  },

  deleteRow: function(index) {
    /// <summary>Deletes a row from the GridView and the corresponding object from the GridView's data source.</summary>
    /// <remarks>
    /// A row in edit mode can be deleted.  In this case the GridView will revert to read-only mode.
    /// </remarks>
    var fn = "deleteRow()";
    this._checkDataBound(fn);
    this._checkIndex(fn, index);

    this._dataSource.splice(index, 1);
    this._container.removeChild(this._container.rows[index + this._containerCount]);

    this._shadeAlternating(this._container, index, this._containerCount);
    this._shiftSelection(index + 1, -1);

    if (this._editIndex == index) {
      this._currentRowMode = Ag.UI.GridViewRowMode.readOnly;
      this._editIndex = -1;
      this._editRowSource = null;
    }
    else if (this._editIndex > index) {
      this._editIndex -= 1;
    }
  },

  getRow: function(index) {
    /// <summary>Returns the HTML TR element at the specified index.</summary>
    return this._container.rows[index + this._containerCount];
  },

  getRowIndex: function(elem) {
    /// <summary>Returns the index of the specified row or the row containing the specified element.</summary>
    while (elem.tagName != "TR") {
      elem = elem.parentNode;
    }

    return $(this._container).children().index(elem) - this._containerCount;
  },

  // Public Properties

  get_allowSorting: function() {
    /// <value type="Boolean">Gets or sets a value indicating whether sorting is enabled</value>
    return this._allowSorting;
  },

  set_allowSorting: function(value) {
    this._allowSorting = value;
  },

  get_alternatingRowCssClass: function() {
    /// <value type="String">Gets or sets the alternating row class</value>
    return this._alternatingRowCssClass;
  },

  set_alternatingRowCssClass: function(sAlternatingRowCssClass) {
    this._alternatingRowCssClass = sAlternatingRowCssClass;
  },

  get_currentRowMode: function() {
    /// <value type="Ag.UI.GridViewRowMode">Gets the current row data-entry mode of the GridView control</value>
    return this._currentRowMode;
  },

  get_editIndex: function() {
    /// <value type="Number">Gets the index of the row currently in edit mode</value>
    /// <remarks>
    /// Returns -1 if all rows are read-only.
    /// </remarks>
    return this._editIndex;
  },

  get_editObject: function() {
    /// <value type="Object">Returns a new object with properties based on bound fields in the edit template</value>
    var rowSource = this._editRowSource;
    var o = {};

    if (rowSource != null) {
      for (var p in rowSource) {
        if (rowSource[p] !== null && rowSource[p] !== "")
          o[p] = rowSource[p];
      }
    }

    if (this._editIndex > -1) {
      this._getNodeValues(this._container.rows[this._editIndex], o);
    }

    return o;
  },

  get_fillEmptyWithSpace: function() {
    /// <value type="Boolean">Gets or sets whether each empty cell should be filled with a space after databinding</value>
    /// <remarks>
    /// Use this for drawing table cell borders correctly in IE 7 and below.  For IE 8 and all other browsers use the
    /// empty-cells property in CSS.
    /// </remarks>
    return this._fillEmptyWithSpace;
  },

  set_fillEmptyWithSpace: function(value) {
    this._fillEmptyWithSpace = value;
  },

  get_rowCount: function() {
    /// <value type="Array">Gets the number of rows currently shown in the GridView</value>
    return this._container.rows.length - this._containerCount;
  },

  get_selectedIndexes: function() {
    /// <value type="Array">Gets or sets the indexes of the selected rows</value>
    return this._selectedIndexes;
  },

  set_selectedIndexes: function(arrayOfIndexes) {
    this.clearSelection();

    if (arrayOfIndexes == null) {
      arrayOfIndexes = [];
    }
    else {
      for (var i = 0; i < arrayOfIndexes.length; ++i) {
        var rowIndex = arrayOfIndexes[i] + this._containerCount;

        if (this._containerCount <= rowIndex && rowIndex < this._container.rows.length) {
          Sys.UI.DomElement.addCssClass(this._container.rows[rowIndex], this._selectedRowCssClass);
        }
        else {
          throw Error.argumentOutOfRange("selectedIndexes", arrayOfIndexes[i]);
        }
      }
    }

    this._selectedIndexes = arrayOfIndexes;
  },

  get_selectedRowCssClass: function() {
    /// <value type="String">Gets or sets the selected row class</value>
    return this._selectedRowCssClass;
  },

  set_selectedRowCssClass: function(sSelectedRowCssClass) {
    this._selectedRowCssClass = sSelectedRowCssClass;
  },

  get_selectionMode: function() {
    /// <value type="Ag.UI.GridViewSelectionMode">Gets or sets the selection mode of the grid</value>
    return this._selectionMode;
  },

  set_selectionMode: function(value) {
    this._selectionMode = value;
  },

  get_sortDirection: function() {
    /// <value type="String">ASC or DESC</value>
    return (this._sortAsc ? "ASC" : "DESC");
  },

  set_sortDirection: function(value) {
    this._sortAsc = (value !== "DESC");
  },

  get_sortExpression: function() {
    /// <value type="String">Gets or sets the sort expression</value>
    /// <remarks>
    /// If you pass the existing sort column, the sortDirection (ASC/DESC) flag gets flipped.
    /// </remarks>
    return this._sortExpression;
  },

  set_sortExpression: function(value) {
    if (this._sortExpression !== value) {
      this._sortExpression = value;
      this._sortAsc = true;
    } else {
      this._sortAsc = !this._sortAsc;
    }
  },

  // Public events

  add_sort: function(handler) {
    /// <summary>Occurs when the GridView requests to be sorted</summary>
    this.get_events().addHandler("sort", handler);
  },

  remove_sort: function(handler) {
    this.get_events().removeHandler("sort", handler);
  },

  add_selectionChanged: function(handler) {
    /// <summary>Occurs when the user selects or deselects a row</summary>
    this.get_events().addHandler("selectionChanged", handler);
  },

  remove_selectionChanged: function(handler) {
    this.get_events().removeHandler("selectionChanged", handler);
  },

  // Private Methods

  _checkDataBound: function(name) {
    if (!this._dataBound) {
      throw Error.invalidOperation(name + " is only allowed on a read-only GridView after databinding");
    }
  },

  _checkIndex: function(name, index, maxIndex) {
    if (maxIndex == null) {
      maxIndex = this.get_rowCount() - 1;
    }

    if (index < 0 || maxIndex < index) {
      throw Error.argumentOutOfRange(name, index);
    }
  },

  _fillEmpties: function(row) {
    if (this._fillEmptyWithSpace) {
      for (var i = 0; i < row.childNodes.length; ++i) {
        var cell = row.childNodes.item(i);

        if (cell.nodeName != "#text") {
          if (!cell.firstChild) {
            cell.appendChild(document.createTextNode(" "));
          }
          else if (cell.childNodes.length == 1 && cell.firstChild.nodeName == "#text" && cell.firstChild.data.length == 0) {
            cell.firstChild.data = " ";
          }
        }
      }
    }
  },

  _onSort: function() {
    var handler = this.get_events().getHandler("sort");

    if (handler !== null)
      handler(this, new Ag.UI.GridViewSortEventArgs(this.get_sortDirection(), this._sortExpression));
  },

  _onSelectionChanged: function() {
    var handler = this.get_events().getHandler("selectionChanged");

    if (handler !== null) {
      handler(this, Sys.EventArgs.Empty);
    }
  },

  _shadeAlternating: function(container, startIndex, rowOffset) {
    if (this._alternatingRowCssClass != null) {
      for (var i = startIndex; i < container.childNodes.length; ++i) {
        var row = container.childNodes.item(i + rowOffset);

        if ((i % 2) == 1) {
          Sys.UI.DomElement.addCssClass(row, this._alternatingRowCssClass);
        }
        else {
          Sys.UI.DomElement.removeCssClass(row, this._alternatingRowCssClass);
        }
      }
    }
  },

  _shiftSelection: function(startIndex, offset) {
    for (var i = 0; i < this._selectedIndexes.length; ++i) {
      if (this._selectedIndexes[i] >= startIndex) {
        this._selectedIndexes[i] += offset;
      }
    }
  },

  // Private DOM Event Handlers

  _th_click: function(e) {
    /// <summary>The user has clicked a header</summary>
    this.set_sortExpression(e.target.sortExpression);
    this._onSort();
  },

  _tr_click: function(e) {
    /// <summary>The user has clicked a row for selection or deselection</summary>
    var row = e.target;

    if (row.tagName != "TR") {
      if (row.tagName == "TD") {
        row = row.parentNode;
      }
      else {
        return;
      }
    }

    var rowIndex = $(this._container).children().index(row);
    var i = rowIndex - this._containerCount;
    var isSelected = Array.contains(this._selectedIndexes, i);

    switch (this._selectionMode) {
      case Ag.UI.GridViewSelectionMode.single:
        this.clearSelection();
        break;

      case Ag.UI.GridViewSelectionMode.multiple:
        if (isSelected) {
          Sys.UI.DomElement.removeCssClass(this._container.rows[rowIndex], this._selectedRowCssClass);
          Array.remove(this._selectedIndexes, i);
        }
        break;
    }

    if (!isSelected) {
      Sys.UI.DomElement.addCssClass(this._container.rows[rowIndex], this._selectedRowCssClass);
      this._selectedIndexes.push(i);
    }

    this._onSelectionChanged();
  }
}

// Register the class as a control
Ag.UI.GridView.registerClass("Ag.UI.GridView", Ag.UI.DataBoundControl);

if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();