/**
 * @fileoverview Zapatec Editable Grid widget extension.
 *
 * <pre>
 * Copyright (c) 2004-2006 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 * </pre>
 */

/* $Id: zpgrid-editable.js 5092 2006-11-14 12:12:15Z alex $ */

// Emulate window.event in Mozilla for keydown event
Zapatec.Widget.emulateWindowEvent(['keydown']);

/**
 * Editable Grid extension.
 *
 * <pre>
 * Note: zpgrid-core.js must be included before this module. If plugin modules
 * like zpgrid-xml.js are used, they must be included before this module as
 * well.
 *
 * <strong>Input data formats differences form Zapatec.Grid:</strong>
 *
 * <strong>JSON:</strong>
 *
 * "noedit: true" property in field definition makes column not editable.
 *
 * "list: true" property in field definition means that cells in the column will
 * not be edited directly. Instead there will appear selectbox with the list of
 * possible values for the cell. Cell definition should contain "values"
 * property with all possible values:
 *
 * values: ["value1", "value2", ...]
 *
 * Default (selected) value must be assigned to "v" property of the cell.
 *
 * <strong>HTML:</strong>
 *
 * Special "zpGridTypeNoedit" class makes column not editable.
 *
 * Special "zpGridTypeList" class means that cells in the column will not be
 * edited directly. Instead there will appear selectbox with the list of
 * possible values for the cell. Cell definition should contain select element:
 * <xmp>
 * <select>
 *   <option>value1</option>
 *   <option selected>value2</option>
 *   ...
 * </select>
 * </xmp>
 * Special field types can be used alone or in conjunction with other field
 * types, e.g. class="zpGridTypeInt zpGridTypeNoedit" or
 * class="zpGridTypeFloat zpGridTypeList".
 *
 * <strong>XML:</strong>
 *
 * "noedit=true" attribute in field definition makes column not editable.
 *
 * "list=true" attribute in field definition means that cells in the column will
 * not be edited directly. Instead there will appear selectbox with the list of
 * possible values for the cell. Cell definition should contain select element:
 * <xmp>
 * <select>
 *   <option>value1</option>
 *   <option selected>value2</option>
 *   ...
 * </select>
 * </xmp>
 *
 * <strong>In addition to config options defined in base Zapatec.Widget class
 * and Zapatec.Grid class provides following config options:</strong>
 *
 * "callbackCellEdit" [function] Callback function to call when grid cell is
 * turned into editable state. Receives Zapatec.EditableGrid and cell object.
 * 
 * "callbackCellReadOnly" [function] Callback function to call when grid cell is
 * turned into read-only state. Receives Zapatec.EditableGrid and cell object.
 *
 * <strong>In addition to events fired from base Zapatec.Grid class fires
 * following events:</strong>
 *
 * <b>gridEdited</b> when grid is changed. Event listener receives edited cell
 * object as argument.
 * </pre>
 *
 * @constructor
 * @extends Zapatec.Grid
 * @param {object} objArgs User configuration
 */
Zapatec.EditableGrid = function(objArgs) {
  // Call constructor of superclass
  Zapatec.EditableGrid.SUPERconstructor.call(this, objArgs);
};

// Inherit Grid
Zapatec.inherit(Zapatec.EditableGrid, Zapatec.Grid);

/**
 * Initializes object.
 *
 * @private
 * @param {object} objArgs User configuration
 */
Zapatec.EditableGrid.prototype.init = function(objArgs) {
  // Define config options
  this.config.callbackCellEdit = null;
  this.config.callbackCellReadOnly = null;
  // Call parent method
  Zapatec.EditableGrid.SUPERclass.init.call(this, objArgs);
};

/**
 * Extends parent method.
 *
 * @private
 * @param {objcet} objCell Cell object
 * @return Converted cell object
 * @type object
 */
Zapatec.EditableGrid.prototype.convertCell = function(objCell) {
  var objField = this.getFieldByCell(objCell);
  if (objField && objField.list && !objCell.values) {
    // List of values
    var arrValues = [];
    // Parse HTML fragment
    var objTempContainer = Zapatec.Transport.parseHtml(objCell.v);
    if (objTempContainer.firstChild) {
      if (objTempContainer.firstChild.nodeType == 1 &&
       objTempContainer.firstChild.nodeName.toLowerCase() == 'select') {
        // Remove old value
        var undef;
        objCell.v = undef;
        // Iterate over values
        var objOption = objTempContainer.firstChild.firstChild;
        while (objOption) {
          if (objOption.nodeType == 1 && objOption.nodeName.toLowerCase() ==
           'option') {
            // Add value
            arrValues.push(objOption.innerHTML);
            // Opera doesn't set selected property correctly
            if (objOption.selected || objOption.getAttribute('selected') ||
             typeof objCell.v == 'undefined') {
              // Set selected value
              objCell.v = objOption.innerHTML;
            }
          }
          // Next value
          objOption = objOption.nextSibling;
        }
      } else {
        // Single value
        arrValues.push(objCell.v);
      }
    }
    var objCell =
     Zapatec.EditableGrid.SUPERclass.convertCell.call(this, objCell);
    objCell.values = arrValues;
    return objCell;
  }
  return Zapatec.EditableGrid.SUPERclass.convertCell.call(this, objCell);
};

// Check if zpgrid-html.js is loaded
if (Zapatec.Grid.prototype.newFieldHtml) {

  /**
   * Extends parent method.
   *
   * @private
   * @param {object} objCell HTMLElement object
   * @return Field object
   * @type object
   */
  Zapatec.EditableGrid.prototype.newFieldHtml = function(objCell) {
    // Call parent method
    var objField =
     Zapatec.EditableGrid.SUPERclass.newFieldHtml.call(this, objCell);
    // Set noedit flag
    if (objCell.className.indexOf('zpGridTypeNoedit') >= 0) {
      objField.noedit = true;
    }
    // Set list flag
    if (objCell.className.indexOf('zpGridTypeList') >= 0) {
      objField.list = true;
    }
    return objField;
  };

}

// Check if zpgrid-xml.js is loaded
if (Zapatec.Grid.prototype.newFieldXml) {

  /**
   * Extends parent method.
   *
   * @private
   * @param {object} objCell source object
   * @return Field object
   * @type object
   */
  Zapatec.EditableGrid.prototype.newFieldXml = function(objCell) {
    // Call parent method
    var objField =
     Zapatec.EditableGrid.SUPERclass.newFieldXml.call(this, objCell);
    // Set noedit flag
    if (objCell.getAttribute('noedit') == 'true') {
      objField.noedit = true;
    }
    // Set list flag
    if (objCell.getAttribute('list') == 'true') {
      objField.list = true;
    }
    return objField;
  };

}

/**
 * Keydown event handler in editable mode.
 *
 * @private
 * @param {number} iGridId Grid id
 * @param {number} iRowId Row id
 * @param {number} iCellId Cell id
 */
Zapatec.EditableGrid.onInputKeyDown = function(iGridId, iRowId, iCellId) {
  // Get Event object
  var objEvent = window.event;
  if (!objEvent) {
    return;
  }
  // Skip Shift, Ctrl and Alt buttons because they are used only as modifiers
  if (objEvent.keyCode >= 16 && objEvent.keyCode <= 18) {
    return true;
  }
  // Get target element
  var objElement = objEvent.srcElement || objEvent.target;
  if (!objElement) {
    return;
  }
  // Get grid object
  var objGrid = Zapatec.Widget.getWidgetById(iGridId);
  if (!objGrid) {
    return;
  }
  // Get row object
  var objRow = objGrid.getRowById(iRowId);
  if (!objRow) {
    return;
  }
  // Get field object
  var objField = objGrid.getFieldById(iCellId);
  if (!objField) {
    return;
  }
  // Get cell object
  var objCell = objGrid.getCellById(iRowId, iCellId);
  if (!objCell) {
    return;
  }
  // Process key
  switch (objEvent.keyCode) {
    case 27: // Esc
      return objGrid.onInputButtonEsc(objCell, objEvent, objElement);
    case 13: // Enter
      return objGrid.onInputButtonEnter(objCell, objEvent, objElement);
    case 0: // Shift+Tab in Safari only
    case 9: // Tab
      return objGrid.onInputButtonTab(objCell, objEvent, objElement);
  }
};

/**
 * Focus lost event handler in editable mode.
 *
 * @private
 * @param {number} iGridId Grid id
 * @param {number} iRowId Row id
 * @param {number} iCellId Cell id
 */
Zapatec.EditableGrid.onInputBlur = function(iGridId, iRowId, iCellId) {
  // Get grid object
  var objGrid = Zapatec.Widget.getWidgetById(iGridId);
  if (!objGrid) {
    return;
  }
  // Get cell object
  var objCell = objGrid.getCellById(iRowId, iCellId);
  if (!objCell) {
    return;
  }
  // Make it read-only
  objGrid.readOnlyCell(objCell);
};

/**
 * Esc key handler in editable mode.
 *
 * @private
 * @param {object} objCell Cell object
 * @param {object} objEvent Event object
 * @param {object} objElement Target element
 */
Zapatec.EditableGrid.prototype.onInputButtonEsc = function(objCell, objEvent, objElement) {
  // Restore original value
  if (objElement.nodeName.toLowerCase() == 'input') {
    // Checkbox
    objElement.checked = this.getCellValueCompare(objCell) ? true : false;
  } else {
    // Selectbox or textarea
    objElement.value = this.getCellValueOriginal(objCell);
  }
  // Make cell read only
  this.readOnlyCell(objCell);
  // Stop event
  return Zapatec.Utils.stopEvent(objEvent);
};

/**
 * Enter key handler in editable mode.
 *
 * @private
 * @param {object} objCell Cell object
 * @param {object} objEvent Event object
 * @param {object} objElement Target element
 */
Zapatec.EditableGrid.prototype.onInputButtonEnter = function(objCell, objEvent, objElement) {
  // Make cell read only
  this.readOnlyCell(objCell);
  // Stop event
  return Zapatec.Utils.stopEvent(objEvent);
};

/**
 * Tab key handler in editable mode.
 *
 * @private
 * @param {object} objCell Cell object
 * @param {object} objEvent Event object
 * @param {object} objElement Target element
 */
Zapatec.EditableGrid.prototype.onInputButtonTab = function(objCell, objEvent, objElement) {
  // Save reference to current cell
  var objPrevCell = objCell;
  // Go to previous/next cell skipping not editable cells
  do {
    var objNextCell = null;
    if (objEvent.shiftKey) {
      // Get previous cell
      if (objCell.i == 0) {
        // Get visible rows
        var arrRows = this.applyPaging();
        // Get previous row
        for (var iRow = 0; iRow < arrRows.length; iRow++) {
          var objCurrRow = arrRows[iRow];
          if (objCurrRow && objCell.r == objCurrRow.i) {
            if (iRow > 0) {
              // Go to last cell of previous row
              var objPrevRow = arrRows[iRow - 1];
              if (objPrevRow && objPrevRow.cells instanceof Array) {
                objNextCell = objPrevRow.cells[objPrevRow.cells.length - 1];
              }
            } else if (this.currentPage > 0) {
              // Visual improvement: unselect row and cell before switching page
              this.unselectRow(this.getRowByCell(objPrevCell));
              this.unselectCell(objPrevCell);
              // Go to previous page
              this.gotoPage(this.currentPage - 1);
              // Get visible rows
              arrRows = this.applyPaging();
              // Go to last cell
              var objLastRow = arrRows[arrRows.length - 1];
              if (objLastRow && objLastRow.cells instanceof Array) {
                objNextCell = objLastRow.cells[objLastRow.cells.length - 1];
              }
            }
            break;
          }
        }
      } else {
        // Go to previous cell
        var objRow = this.getRowByCell(objCell);
        if (objRow && objRow.cells instanceof Array) {
          objNextCell = objRow.cells[objCell.i - 1];
        }
      }
    } else {
      // Get next cell
      var objRow = this.getRowByCell(objCell);
      if (objRow && objRow.cells instanceof Array) {
        if (objCell.i == objRow.cells.length - 1) {
          // Get visible rows
          var arrRows = this.applyPaging();
          // Get next row
          for (var iRow = 0; iRow < arrRows.length; iRow++) {
            var objCurrRow = arrRows[iRow];
            if (objCurrRow && objRow.i == objCurrRow.i) {
              if (iRow < arrRows.length - 1) {
                // Go to first cell of next row
                var objNextRow = arrRows[iRow + 1];
                if (objNextRow && objNextRow.cells instanceof Array) {
                  objNextCell = objNextRow.cells[0];
                }
              } else if (this.currentPage < this.totalPages() - 1) {
                // Visual improvement: unselect row and cell before switching
                // page
                this.unselectRow(this.getRowByCell(objPrevCell));
                this.unselectCell(objPrevCell);
                // Go to next page
                this.gotoPage(this.currentPage + 1);
                // Get visible rows
                arrRows = this.applyPaging();
                // Go to first cell
                var objFirstRow = arrRows[0];
                if (objFirstRow && objFirstRow.cells instanceof Array) {
                  objNextCell = objFirstRow.cells[0];
                }
              }
              break;
            }
          }
        } else {
          // Go to next cell
          objNextCell = objRow.cells[objCell.i + 1];
        }
      }
    }
    // Go to previous/next cell
    objCell = objNextCell;
  } while (objCell && this.fields[objCell.i].noedit);
  // Do nothing if this is first or last cell in the grid
  if (objCell) {
    // Wait until grid is refreshed
    var objGrid = this;
    setTimeout(function() {
      // Unselect previous row
      objGrid.unselectRow(objGrid.getRowByCell(objPrevCell));
      // Unselect previous cell and turn it into read only state
      objGrid.unselectCell(objPrevCell);
      // Turn cell into editable mode
      objGrid.editCell(objCell);
      // Select cell
      objGrid.selectCell(objCell);
      // Select row
      objGrid.selectRow(objGrid.getRowByCell(objCell));
    }, 0);
  }
  // Stop event
  return Zapatec.Utils.stopEvent(objEvent);
};

/**
 * Turns cell into editable state. Calls callbackCellEdit function.
 *
 * @private
 * @param {object} objCell Cell object
 */
Zapatec.EditableGrid.prototype.editCell = function(objCell) {
  // Check arguments
  if (!objCell || objCell.editing) {
    return;
  }
  // Check if editing of this column is allowed
  var objField = this.getFieldByCell(objCell);
  if (!objField || objField.noedit) {
    return;
  }
  // Edit callback
  var boolVisualize;
  if (typeof this.config.callbackCellEdit == 'function') {
    boolVisualize = this.config.callbackCellEdit(this, objCell);
  }
  // Display input field if we are responsible for visualisation
  if (this.visualize &&
   (boolVisualize || typeof boolVisualize == 'undefined')) {
    var iGridId = this.id;
    var iRowId = this.getCellRowId(objCell);
    var iCellId = this.getCellId(objCell);
    // Get table row element
    var objTr = document.getElementById('zpGrid' + iGridId + 'Row' + iRowId);
    if (objTr) {
      // Update className
      var arrClass = [];
      arrClass.push(' zpGridRowEditable zpGridRowEditable');
      arrClass.push(iRowId);
      arrClass.push(' zpGridRowEditable');
      arrClass.push(objTr.className.indexOf('zpGridRowOdd') >= 0 ?
       'Odd' : 'Even');
      if (objTr.className.indexOf('zpGridRowLast') >= 0) {
        // Last row
        arrClass.push(' zpGridRowEditableLast');
      }
      var strClass = arrClass.join('');
      objTr.className += strClass;
      // Get fixed part of the row
      objTr = document.getElementById('zpGrid' + iGridId + 'Row' + iRowId +
       'Fixed');
      if (objTr) {
        objTr.className += strClass;
      }
    }
    // Get table cell element
    var objTd = document.getElementById('zpGrid' + iGridId + 'Row' + iRowId +
     'Cell' + iCellId);
    // Can be on different page
    if (objTd && objTd.firstChild) {
      // Get number of visible columns
      var iCols = this.fields.length;
      for (var iField = 0; iField < this.fields.length; iField++) {
        var objField = this.fields[iField];
        if (!objField || objField.hidden) {
          iCols--;
        }
      }
      // Update className
      var arrClass = [];
      arrClass.push(' zpGridCellEditable zpGridCellEditable');
      arrClass.push(iCellId);
      arrClass.push(' zpGridCellEditable');
      arrClass.push(iCellId % 2 == 1 ? 'Odd' : 'Even');
      if (iCellId == iCols - 1) {
        // Last cell
        arrClass.push(' zpGridCellEditableLast');
      }
      objTd.className += arrClass.join('');
      // Display form element depending from cell data type to edit cell value
      var strCellDataType = this.getCellDataType(objCell);
      if (strCellDataType && strCellDataType.indexOf('boolean') == 0) {
        // Checkbox
        var arrHtml = [];
        arrHtml.push('<div style="position:relative;height:0px""><div \
         style="position:absolute;top:0px;left:0px;width:');
        arrHtml.push(objTd.firstChild.offsetWidth);
        arrHtml.push('px;height:');
        arrHtml.push(objTd.firstChild.offsetHeight);
        arrHtml.push('px;text-align:center"><input type="checkbox" \
         class="zpGridInput" \
         onkeydown="Zapatec.EditableGrid.onInputKeyDown(\'');
        arrHtml.push(iGridId);
        arrHtml.push("','");
        arrHtml.push(iRowId);
        arrHtml.push("','");
        arrHtml.push(iCellId);
        arrHtml.push('\')" ');
        if (this.getCellValueCompare(objCell)) {
          arrHtml.push('checked="checked" ');
        }
        arrHtml.push('onblur="Zapatec.EditableGrid.onInputBlur(\'');
        arrHtml.push(iGridId);
        arrHtml.push("','");
        arrHtml.push(iRowId);
        arrHtml.push("','");
        arrHtml.push(iCellId);
        arrHtml.push('\')"/></div></div>');
        // Original content is needed to keep cell size and position form
        // element correctly
        arrHtml.push('<div style="overflow:hidden;visibility:hidden">');
        arrHtml.push(objTd.firstChild.innerHTML);
        arrHtml.push('</div>');
        objTd.innerHTML = arrHtml.join('');
      } else if (objCell.values instanceof Array) {
        // Selectbox
        var arrHtml = [];
        arrHtml.push('<div style="position:relative;height:0px""><div \
         style="position:absolute;top:0px;left:0px"><select \
         class="zpGridSelect" \
         onkeydown="Zapatec.EditableGrid.onInputKeyDown(\'');
        arrHtml.push(iGridId);
        arrHtml.push("','");
        arrHtml.push(iRowId);
        arrHtml.push("','");
        arrHtml.push(iCellId);
        arrHtml.push('\')" onblur="Zapatec.EditableGrid.onInputBlur(\'');
        arrHtml.push(iGridId);
        arrHtml.push("','");
        arrHtml.push(iRowId);
        arrHtml.push("','");
        arrHtml.push(iCellId);
        arrHtml.push('\')" style="width:');
        arrHtml.push(objTd.firstChild.offsetWidth);
        arrHtml.push('px">');
        // Options
        var strCellOrigValue = this.getCellValueOriginal(objCell);
        for (var iOption = 0; iOption < objCell.values.length; iOption++) {
          var strValue = objCell.values[iOption];
          arrHtml.push('<option');
          if (strValue == strCellOrigValue) {
            arrHtml.push(' selected');
          }
          arrHtml.push(' value="');
          // Escape quotes
          arrHtml.push(strValue.replace(/"/g, '&#34;'));
          arrHtml.push('">');
          arrHtml.push(strValue);
          arrHtml.push('</option>');
        }
        arrHtml.push('</select></div></div>');
        // Original content is needed to keep cell size and position form
        // element correctly
        arrHtml.push('<div style="overflow:hidden;visibility:hidden">');
        arrHtml.push(objTd.firstChild.innerHTML);
        arrHtml.push('</div>');
        objTd.innerHTML = arrHtml.join('');
      } else {
        // Textarea
        var arrHtml = [];
        arrHtml.push('<div style="position:relative;height:0px"><div \
         style="position:absolute;top:0px;left:0px"><textarea \
         class="zpGridTextarea" \
         onkeydown="Zapatec.EditableGrid.onInputKeyDown(\'');
        arrHtml.push(iGridId);
        arrHtml.push("','");
        arrHtml.push(iRowId);
        arrHtml.push("','");
        arrHtml.push(iCellId);
        arrHtml.push('\')" \
         onkeyup="this.style.height=this.scrollHeight+\'px\'; \
         this.style.width=this.scrollWidth+\'px\'" \
         onblur="Zapatec.EditableGrid.onInputBlur(\'');
        arrHtml.push(iGridId);
        arrHtml.push("','");
        arrHtml.push(iRowId);
        arrHtml.push("','");
        arrHtml.push(iCellId);
        arrHtml.push('\')" style="width:');
        arrHtml.push(objTd.firstChild.offsetWidth);
        arrHtml.push('px;height:');
        arrHtml.push(objTd.firstChild.offsetHeight);
        arrHtml.push('px"></textarea></div></div>');
        // Original content is needed to keep cell size and position form
        // element correctly
        arrHtml.push('<div style="overflow:hidden;visibility:hidden">');
        arrHtml.push(objTd.firstChild.innerHTML);
        arrHtml.push('</div>');
        objTd.innerHTML = arrHtml.join('');
        // Edit original value
        objTd.firstChild.firstChild.firstChild.innerHTML =
         this.getCellValueOriginal(objCell);
      }
      var objInput = objTd.firstChild.firstChild.firstChild;
      // IE needs small delay
      setTimeout(function() {
        if (objInput.tagName.toLowerCase() == 'textarea') {
          objInput.style.height = objInput.scrollHeight + 'px';
          objInput.style.width = objInput.scrollWidth + 'px';
          // IE needs focus twice for large grids
          objInput.focus();
        }
        objInput.focus();
        // Prevent possible memory leak in IE
        objInput = null;
      }, 0);
    }
  }
  // Mark cell as editable
  objCell.editing = true;
  // Save reference
  this.editingCell = objCell;
};

/**
 * Turns cell into read-only state and assigns new value.
 *
 * @private
 * @param {object} objCell Cell object
 */
Zapatec.EditableGrid.prototype.readOnlyCell = function(objCell) {
  // Check arguments
  if (!objCell || !objCell.editing) {
    return;
  }
  // Get new value if we are responsible for visualisation
  var value;
  if (this.visualize) {
    // Get table cell element
    var objTd = document.getElementById('zpGrid' + this.id + 'Row' +
     this.getRowId(this.getRowByCell(objCell)) + 'Cell' +
     this.getCellId(objCell));
    // Can be on different page
    if (objTd && objTd.firstChild && objTd.firstChild.firstChild &&
     objTd.firstChild.firstChild.firstChild &&
     typeof objTd.firstChild.firstChild.firstChild.value != 'undefined') {
      // Get cell value from input element
      var strCellDataType = this.getCellDataType(objCell);
      if (strCellDataType && strCellDataType.indexOf('boolean') == 0) {
        // Checkbox
        value = objTd.firstChild.firstChild.firstChild.checked;
      } else {
        // Textarea or selectbox
        value = objTd.firstChild.firstChild.firstChild.value;
      }
    }
  }
  // Turn cell into read-only state
  this.setCellReadOnly(objCell, value);
};

/**
 * Turns cell into read-only state and assigns new value. If new value is
 * undefined, cell value is not changed. Calls callbackCellReadOnly function.
 *
 * @param {object} objCell Cell object
 * @param {any} value Optional. New cell value
 */
Zapatec.EditableGrid.prototype.setCellReadOnly = function(objCell, value) {
  // Check arguments
  if (!objCell) {
    return;
  }
  // Mark cell as read-only
  objCell.editing = false;
  // Remove reference
  this.editingCell = null;
  // Set new value
  if (typeof value != 'undefined') {
    this.setCellValue(objCell, value);
  }
  // Display updates if we are responsible for visualisation
  if (this.visualizeCellReadOnly && this.visualize) {
    this.visualizeCellReadOnly(objCell);
  }
  // Redraw totals
  if (this.redrawTotals) {
    this.redrawTotals({
      column: this.getCellId(objCell)
    });
  }
  // Read-only callback
  if (typeof this.config.callbackCellReadOnly == 'function') {
    this.config.callbackCellReadOnly(this, objCell);
  }
  // Fire event
  this.fireEvent('gridEdited', objCell);
};

/**
 * Extends parent method. 
 *
 * @private
 * @param {object} objCell Cell object
 */
Zapatec.EditableGrid.prototype.unselectCell = function(objCell) {
  // Editable cell is always selected as well. When cell is unselected, it must
  // be turned into read-only state.
  this.readOnlyCell(objCell);
  // Call parent method
  Zapatec.EditableGrid.SUPERclass.unselectCell.call(this, objCell);
};

/**
 * Extends parent method.
 * @private
 */
Zapatec.EditableGrid.prototype.refresh = function() {
  // If there is editable cell, its value must be updated. Otherwise changes
  // will be lost.
  this.readOnlyCell(this.editingCell);
  // Call parent method
  Zapatec.EditableGrid.SUPERclass.refresh.call(this);
};

/**
 * Extends parent method.
 *
 * @private
 * @param {number} iRowId Id of row that was clicked
 * @param {number} iCellId Id of cell that was clicked
 */
Zapatec.EditableGrid.prototype.rowOnDblClick = function(iRowId, iCellId) {
  // Call parent method
  Zapatec.EditableGrid.SUPERclass.rowOnDblClick.call(this, iRowId, iCellId);
  // Turn cell that is currently edited into read-only state
  if (this.editingCell) {
    this.readOnlyCell(this.editingCell);
  }
  // Turn cell into editable state
  this.editCell(this.getCellById(iRowId, iCellId));
};
