// GridEdit.js
//
// Editable grid implementation
//
// Boris Kabisher
// Copyright (c) SAP AG 2005. All rights reserved.
//

#DEPENDENCIES[
   lib:Basic.js
   lib:EnumMgr.js
   lib:EventsMgr.js
   lib:InputMgr.js
   lib:PopupMgr.js
   lib~skin:styles.GridEdit.css
]

#INCLUDE[env:global_hotkeys.inc]

//
// The idea of using this to solve eventHandler 'this' problem is taken from here:
// http://www.deepwood.net/writing/method-references.html.utf8
//
Function.prototype.bindAsEventListener = function (object) {
   var method = this;
   var preappliedArguments = arguments;
   return function (event) {
      //method.call(object, event || window.event);
      method.apply(object, concatArguments([event || window.event], preappliedArguments));
   };

   // Some browsers don't like expressions such as foo.concat(arguments)
   // or arguments.concat(foo) --- presumably because argument objects are
   // not really considered to be arrays --- so we'll just concatenate
   // pseudoarrays manually.  Thanks to Chih-Chao Lam for pointing this out.
   function concatArguments() {
      var result = [];

      for (var i = 0; i < arguments.length; i++)
         for (var j = 0; j < arguments[i].length; j++)
            result.push(arguments[i][j]);

      return result;
   }
}

function GridEdit() {
   this._ready       = false;
   this._glyph       = 'GRID';
   this._showHeader  = true;

   this._rowHeight      = 15;
   this._borderSize  = 1;
   this._headerHeight   = 17;
   this._initialHeight  = 2 * this._borderSize + this._headerHeight + 4 * this._rowHeight;

   this._colDefs  = null;  // column definitions
   this._gridWidth = 0; // total columns width
   this._gridRows = null;  // grid rows collection
   this._numRows  = 0;  // number of non-blank rows in the gridview (rows that have Id and contain data)
   this._numCols  = 0;  // number of columns in the gridview
   this._blankRow = null;  // blank row template
   this._currRow  = null;  // current row
   this._currCell = null;  // current cell

   this._canEdit    = true;
   this._canAdd     = true;
   this._canDelete  = true;
   this._canMove    = true;
   this._canSort    = false;
   this._canResize  = true;
   this._isReadonly = false;

   this._headTbl = null;
   this._gridTbl = null;
   this._headBox = null;
   this._gridBox = null;

   this._gripper     = null;

   this._showToolbar   = false;
   this._toolbarAdd    = null;
   this._toolbarDelete = null;
   this._toolbarMoveUpward = null;
   this._toolbarMoveDownward = null;

   this._eventHandler  = null;

   this._parentElem  = null;
   this._doc = null;
   this._elemDef = null;
}

function GridEdit.prototype.getCurrRow() {
   return this._ready && this._currRow && !this._currRow.hide && this._currRow.id || null;
}

function GridEdit.prototype.getCurrCol() {
   return this._ready && this._currRow && this._currCell && this._currCell.id || null;
}

function GridEdit.prototype.setShowHeader(val) {
   this._showHeader = BOOL(val);
   this.resize();
}

//
// Calls eventListener,
// returns true, if eventHandler processed the operation,
// returns false, if operation wasn't processed, so GridEdit should handle it by itself
//
function GridEdit.prototype.onEvent(eventName, eventArgs, eventObj) {
   if (this._eventHandler == null) return false;
   return this._eventHandler(eventName, eventArgs, eventObj);
}

//
// parentElem - required; HTML elem, used as a container for GridEdit
// def - required; Column definitions structure in format of GRID_DEF
// eventHandler - optional; function(eventName, eventArgs, eventObj).
//                 should return: true - to say that the operation was handled
//                                false - to ask GridEdit to handle the operation
//                 events supported:
//                 onRowAdd(rowId, rowIndex)
//                 onRowDelete(rowId, rowIndex, lParam)
//                 onCellChange(rowId, rowIndex, colId, newValue, lParam)
//
// showToolbar - optional; default: false
// initialHeight - optional; height in pixels; default equals to the size of 4 rows
//
function GridEdit.prototype.buildGrid(parentElem, def, showToolbar, eventHandler,initialHeight, elemDef) {
   this._elemDef = elemDef ;
   this._colDefs = def || null;
   if (!this._colDefs) {
      this.eraseGrid();
      return;
   }

   if (showToolbar != null) this._showToolbar = showToolbar;

   if (initialHeight) {
      this._initialHeight = initialHeight;
   }else {
      if (!this._showHeader) this._initialHeight -= 17;

   }

   // build header row
   var HD=[], RW=[], CG=[], GP=[], ids={}, ro=!this._canEdit;
   this._gridWidth = 0;
   for (var i=0, len=this._colDefs.length; i<len; i++) {
      var cdef = this._colDefs[i];
      var cid  = cdef.id || ('COL'+i);
      if (cid in ids) {
         PROMPT('Duplicate column identifier "'+cid+'"', {icon:'WARNING'});
         continue;
      }
      ids[cid] = true;
      if (ro || cdef.id == 'id') cdef.readonly = true;

      var enumData = cdef['enum'];
      if (enumData && enumData.indexOf(';') >= 0 ) {
         var enumId=ALLOC_ENUM();
         SET_ENUM(enumId, STR2ENUM(enumData));
         cdef['enum'] = enumId;
      }

      var attrs = ' id="'+cid+'"'+(cdef.notify ? ' notify="1"' : '')+(cdef.style ? ' style="'+cdef.style+'"' : '')+(cdef.charset ? ' charset="'+cdef.charset+'"' : '')+(cdef.format ? ' format="'+ESCAPE(cdef.format)+'"' : '');
      if (cdef.maxlength) attrs += ' maxlength="'+cdef.maxlength+'"';
      var fld = BUILD_INPUT(cdef, this._glyph, attrs);
      if (!fld) {
         PROMPT('Invalid column definition "'+cid+'"', {icon:'WARNING'});
         continue;
      }

      var w = (cdef.width == '*' ? 10 : cdef.width);
      cdef.userW = POS(cdef.userW || w, 75);
      this._gridWidth += cdef.userW;
      var header = (cdef.icon) ? '<IMG  src="'+cdef.icon+'"   width="11px" height="11px" align=absmiddle >' :
                  (cdef.caption||cdef.id) ;

      CG.push('<COL id="'+cid+'" style="width:'+cdef.userW+';display:'+(cdef.hide ? 'none' : 'inline')+'"/>');
      HD.push('<TD id="'+cid+'" class="'+this._glyph+'-HEAD"'+(cdef.hint ? ' title="'+cdef.hint+'"' : '')+'><span style="height:100%; width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">'+header +'</span></TD>');
      RW.push('<TD id="'+cid+'" class="'+this._glyph+'-CELL">'+fld+'</TD>');
      if (this._canResize) {
         GP.push('<SPAN id="GRIP" column="'+i+'" class="'+this._glyph+'-GRIPPER"></SPAN>');
      }
   }
   CG.push('<COL/>');
   HD.push('<TD class="'+this._glyph+'-HEAD">&nbsp;</TD>');
   RW.push('<TD class="'+this._glyph+'-CELL">&nbsp;</TD>');

   var A=[], B=[];

   var key = parentElem.document.uniqueID;
   A.push('<TABLE id="'+key+'_HEAD_TBL" class="'+this._glyph+'" style="margin-right:16" cellspacing=0 cellpadding=0>');
   A.push('<COLGROUP>'+CG.join('')+'</COLGROUP>');
   A.push('<TR class="'+this._glyph+'-ROW">'+HD.join('')+'</TR>');
   A.push('</TABLE>');

   B.push('<TABLE id="'+key+'_GRID_TBL" class="'+this._glyph+'" cellspacing=0 cellpadding=0>');
   B.push('<COLGROUP>'+CG.join('')+'</COLGROUP>');
   B.push('<TR class="'+this._glyph+'-ROW">'+RW.join('')+'</TR>');
   B.push('</TABLE>');

   A = '<DIV id="'+key+'_HEAD_BOX" class="'+this._glyph+'-HEAD-BOX">'+A.join('')+GP.join('')+'</DIV>';

   B = '<DIV id="'+key+'_GRID_BOX" class="'+this._glyph+'-INNER-BOX">'+B.join('')+'</DIV>';
   var toolBar = '';
   var details = '';
   if (this._showToolbar) {
         toolBar =  '<DIV  id="' + key + '_TOOLBAR" class="'+this._glyph+'-TOOLBAR"  style="height: 22px;width: 100%" border=1>';
      if(this._elemDef.addItem)
            toolBar += '<SPAN id="' + key + '_TOOLBAR_ADD"  class="TOOLBTN" ><img src="#URL[dev~skin:icons.plus.gif]" align=absmiddle></span>';
      if(this._elemDef.removeItem)
         toolBar += '<SPAN id="' + key + '_TOOLBAR_DELETE"  class="TOOLBTN" ><img src="#URL[dev~skin:icons.delete.gif]" align=absmiddle></span>';
      if(this._elemDef.detailedView)
         toolBar += '<SPAN id="' + key + '_TOOLBAR_DETAILS"  class="TOOLBTN" ><img src="#URL[dev~skin:icons.inspect.gif]" align=absmiddle ></span>';
      if(this._elemDef.moveUpward)
         toolBar += '<SPAN id="' + key + '_TOOLBAR_MOVE_UPWARD"  class="TOOLBTN" ><img src="#URL[dev~skin:icons.up.gif]" align=absmiddle></span>';
      if(this._elemDef.moveDownward)
         toolBar += '<SPAN id="' + key + '_TOOLBAR_MOVE_DOWNWARD"  class="TOOLBTN" ><img src="#URL[dev~skin:icons.down.gif]" align=absmiddle></span>';
      toolBar += '</DIV>';
   }

    parentElem.innerHTML = toolBar + '<DIV id="' + key + '_OUTER_BOX" class="' + this._glyph + '-OUTER-BOX" style="height: 100%;">'+ A + B + '</DIV>';
   this._doc = parentElem.document;
   this._toolbar = parentElem.document.getElementById(key+'_TOOLBAR');
   this._outerbox = parentElem.document.getElementById(key+'_OUTER_BOX');


   this._headTbl = parentElem.document.getElementById(key+'_HEAD_TBL');
   this._gridTbl = parentElem.document.getElementById(key+'_GRID_TBL');
   this._headBox = parentElem.document.getElementById(key+'_HEAD_BOX');
   this._gridBox = parentElem.document.getElementById(key+'_GRID_BOX');

   this._gridRows   = this._gridTbl.rows;
   this._numRows    = 0;
   this._numCols    = this._colDefs.length;
   this._blankRow   = this._gridRows.item(0).cloneNode(true);
   this._currRow    = null;
   this._currCell   = null;

   this._gridRows[0].style.display = 'none';
   this._gridRows[0].id = parentElem.document.uniqueID;

   if (this._showToolbar) {
      this._toolbarAdd    = parentElem.document.getElementById(key+'_TOOLBAR_ADD');
      this._toolbarDelete = parentElem.document.getElementById(key+'_TOOLBAR_DELETE');
      this._toolbarDetatils = parentElem.document.getElementById(key+'_TOOLBAR_DETAILS');
      this._toolbarMoveUpward = parentElem.document.getElementById(key+'_TOOLBAR_MOVE_UPWARD');
      this._toolbarMoveDownward = parentElem.document.getElementById(key+'_TOOLBAR_MOVE_DOWNWARD');
     if(this._toolbarAdd)
         this._toolbarAdd.onclick    = this.onToolbarAdd.bindAsEventListener(this)|| null;
      if(this._toolbarDelete)
            this._toolbarDelete.onclick = this._toolbarDelete && this.onToolbarDelete.bindAsEventListener(this) ;
      if(this._toolbarDetatils)
            this._toolbarDetatils.onclick = this._toolbarDetatils && this.onToolbarDetails.bindAsEventListener(this);
      if(this._toolbarMoveUpward)
         this._toolbarMoveUpward.onclick = this._toolbarMoveUpward && this.onToolbarMoveUpward.bindAsEventListener(this);
      if(this._toolbarMoveDownward)
         this._toolbarMoveDownward.onclick = this._toolbarMoveDownward && this.onToolbarMoveDownward.bindAsEventListener(this);
   }

   this._headBox.onmousedown = this.gripperDown.bindAsEventListener(this);
   this._headBox.ondblclick  = this.gripperDblClick.bindAsEventListener(this);
   this._gridBox.onscroll    = this.alignTables.bindAsEventListener(this);

   this._gridBox.onInputChange      = this.onInputChange.bindAsEventListener(this);
   this._gridBox.onFieldFirstClick  = this.onFieldFirstClick.bindAsEventListener(this);
   this._gridBox.onFieldKey      = this.onFieldKey.bindAsEventListener(this);
   this._gridBox.onFieldExit     = this.onFieldExit.bindAsEventListener(this);
   this._gridBox.onFieldEnter    = this.onFieldEnter.bindAsEventListener(this);

   this._unselectable  = true;
   this._gridBox.unselectable = true;
   for (var i=0, A=this._headBox.all, len=A.length; i<len; i++) {
      A(i).unselectable = true;
   }

   this._parentElem    = parentElem;
   this._eventHandler  = eventHandler;

   this._ready = true;

   this.resize();
}

// Calculates the outer margin of a given element definition
function GridEdit.prototype.outerMargin(elemDef, parent) {
   return POS(elemDef.outer)*18;
}

function GridEdit.prototype.createTag(tag, attrs) {
   var e = this._doc.createElement(tag);
   if (!attrs) return e;
   for (var k in attrs) {
      try { if (k.charAt(0) != '$') e[k] = attrs[k]; else e.style[k.slice(1)] = attrs[k]; } catch (e) {}
   }
   return e;
}

function GridEdit.prototype.eraseGrid() {
   this._parentElem.innerHTML = '';
   this._ready = false;
   this._colDefs = [];
}

function GridEdit.prototype.clearGrid() {
   //
   // BORKA: 2-May-05. VC04-taken implementation didn't remove first node:
   // for (var i=_gridRows.length-1; i>0; i--) {
   //
   for (var i=this._gridRows.length-1; i>=0; i--) {
      this._gridRows[i].removeNode(true);
   }
   //
   // BORKA: 2-May-05. VC04-taken implementation didn't set _numRows to 0
   //
   this._numRows = 0;
}

function GridEdit.prototype.getGridData() {
   var data=[];
   if (!this._ready) return data;
   for (var i=0; i<this._numRows; i++) {
      data.push(this.getRowData(this._gridRows[i]));
   }
   return data;
}


function GridEdit.prototype.getNumRows() {
   return this._ready && this._numRows || 0;
}

function GridEdit.prototype.getGridAsString() {
   var D=[];
   if (!this._ready) return D;
   for (var i=0; i<this._numRows; i++) {
      var row=this.getRowObj(this._gridRows[i]);
      if (!row) continue;
      for (var j=0, R=[], C=row.cells, numCols=C.length; j<numCols; j++) {
         var cell=C[j];
         if (cell.id) R.push(cell.id + ':\'' + (GET_INPUT(cell.firstChild)+'').replace(/\'/g,'\\\'') + '\'');
      }
      D.push('{' + R.join(',') + '}');
   }
   return '[' + D.join(',') + ']';
}

function GridEdit.prototype.setGridData(data) {
   if (!this._ready) return;

   var tbody=this._gridTbl.tBodies[0];
   var len1=this._gridRows.length, len2=data && data.length || 0, diff=len1-len2;
   if (diff < 0) {
      for (var i=0; i<-diff; i++) {
         var row=tbody.appendChild(this._blankRow.cloneNode(true));
         row.id = this._parentElem.document.uniqueID
      }
   }

   if (diff > 0) {
      for (var i=len2; i<len1; i++) {
         var row=tbody.rows(i);
         if (row.hide) break;
         row.hide = true;
         row.style.display = 'none';
      }
   }

   this._numRows = len2;
   for (var i=0; i<len2; i++) {
      this.setRowData(tbody.rows(i), data[i]);
   }

   this._gridBox.scrollTop = 0;
   this.selectRow(null, 'data');
}

function GridEdit.prototype.setGridFromString(str) {
   var data=null;
   if (str) try { eval('data = '+str+';'); } catch(e) { data=null; }
   this.setGridData(data);
}

function GridEdit.prototype.clearGridData() {
   setGridData();
}

function GridEdit.prototype.getIndexedGridData(key) {
   var data={};
   if (!this._ready) return data;
   for (var i=0; i<this._numRows; i++) {
      var row=this.getRowData(this._gridRows[i]), kval=row[key];
      if (kval) data[kval] = row;
   }
   return data;
}

function GridEdit.prototype.getRowData(row) {
   var obj={}, row=this.getRowObj(row);
   if (!row) return obj;
   for (var i=0, C=row.cells, len=C.length; i<len; i++) {
      var cell = C[i];
      obj[cell.id] = GET_INPUT(cell.firstChild);
   }
   return obj;
}

function GridEdit.prototype.setRowData(row, values) {
   var row = this.getRowObj(row);
   if (!row) return;
   row.hide = false;
   if (!values) values = {};
   values._row = row.id.replace('ms__','');
   for (var i=0, C=row.cells, len=C.length; i<len; i++) {
      var cell=C[i], cid=cell.id, fld=cell.firstChild;
      if (!cid || !fld || !fld.dt) continue;
      SET_INPUT(fld, values[cid] || null, false);
      if ('$color' in values) fld.style.color = values.$color;
      fld.runtimeStyle.backgroundColor = '';
   }
   row.style.display = 'block';
}

function GridEdit.prototype.clearRowData(row) {
   var row = this.getRowObj(row);
   if (!row || row.hide) return;
   row.hide = true;
   row.style.display = 'none';
}

function GridEdit.prototype.clearSelection() {
   if (!this._currRow) return;
   this._currRow.className = this._glyph+'-ROW';
   this._currRow  = null;
   this._currCell = null;

   var evt = event ? this._parentElem.document.createEventObject(event) : this._parentElem.document.createEventObject();
   evt.row = '';
   evt.opcode = '';
   this._parentElem.fireEvent('onRowSelect', evt);
}

function GridEdit.prototype.selectRow(row, opcode) {
   var row = this.getRowObj(row);
   if (this._currRow) this._currRow.className = this._glyph+'-ROW';
   this._currRow  = row;
   this._currCell = null;
   if (!this._currRow) return;

   this._currRow.className = this._glyph+(this._canEdit ? '-ROW-SEL' : '-ROW-SEL2');
   var R=this._gridBox.getBoundingClientRect(), H=this._gridBox.clientHeight;
   var r=this._currRow.getBoundingClientRect();
   this._gridBox.scrollTop += MIN(0,r.top-R.top) + MAX(0,r.bottom-R.top-H);

   var evt = event ? this._parentElem.document.createEventObject(event) : this._parentElem.document.createEventObject();
   evt.row = this._currRow.id;
   evt.rowIndex = this._currRow.rowIndex;
   evt.opcode = opcode;
   this._parentElem.fireEvent('onrowenter', evt);
}

function GridEdit.prototype.selectNext() {
   if (!this._currRow) return;
   var row=this._currRow.nextSibling;
   if (row && row.style.display == 'block') this.selectRow(row, 'move');
}

function GridEdit.prototype.selectPrev() {
   if (!this._currRow) return;
   var row=this._currRow.previousSibling;
   if (row) this.selectRow(row, 'move');
}

function GridEdit.prototype.isFirstRow() {
   var row=this._currRow && this._currRow.previousSibling || null;
   while (row) {
      if (row.currentStyle.display == 'block') return false;
      row = row.previousSibling;
   }
   return true;
}

function GridEdit.prototype.isLastRow() {
   var row=this._currRow && this._currRow.nextSibling || null;
   while (row) {
      if (row.currentStyle.display == 'block') return false;
      row = row.nextSibling;
   }
   return true;
}

function GridEdit.prototype.addRow(values, lParam) {
   if (!this._canAdd) return null;

   var newrow, cid=this.getCurrCol();
   if (this._numRows < this._gridRows.length) {
      newrow = this._gridRows[this._numRows];
      newrow.removeNode(true);
   } else {
      newrow = this._blankRow.cloneNode(true);
   }

   if (this._currRow) {
      this._gridTbl.tBodies[0].insertBefore(newrow, this._currRow.nextSibling || null);
   } else {
      this._gridTbl.tBodies[0].insertBefore(newrow, this._gridRows[this._numRows] || null);
   }

   this._numRows++;
   newrow.id = this._parentElem.document.uniqueID;
   if (lParam || lParam == 0) newrow.lParam = lParam;
   this.setRowData(newrow, values);

   if (cid) {
      FOCUS(newrow.cells.item(cid).firstChild);
   } else {
      this.selectRow(newrow, 'add');
   }

   return newrow.id;
}

function GridEdit.prototype.deleteRow() {
   if (!this._currRow || !this._canDelete) return;
   var row=this._currRow, cid=this.getCurrCol(), nearby;
   if (row.rowIndex < this._numRows-1) {
      nearby = row.nextSibling;
   } else {
      nearby = row.previousSibling;
   }

   this._numRows--;
   row.removeNode(true);
   this._gridTbl.tBodies[0].appendChild(row);
   this.clearRowData(row);

   this.selectRow(nearby||null, 'delete');
   if (cid && nearby) {
      var cell=nearby.cells.item(cid).firstChild;
      setTimeout(function () { FOCUS(cell); });
   }
}

function GridEdit.prototype.moveRow(where) {
   if (!this._currRow || !this_canMove) return;
   if (this._numRows <= 1) return;
   var row=this._currRow;
   switch (UPPER(where)) {
      case 'PREV':
         if (row.rowIndex == 0) break;
         var row2 = row.previousSibling;
         if (row2 && !row2.hide) row.swapNode(row2)
         break;

      case 'NEXT':
         if (row.rowIndex == this._numRows-1) break;
         var row2 = row.nextSibling;
         if (row2 && !row2.hide) row.swapNode(row2)
         break;

      case 'FIRST':
         if (row.rowIndex == 0) break;
         var row2=this._gridRows[0] || null;
         row.removeNode(true);
         this._gridTbl.tBodies[0].insertBefore(row, row2);
         break;

      case 'LAST':
         if (row.rowIndex == this._numRows-1) break;
         var row2=this._gridRows[this._numRows] || null;
         row.removeNode(true);
         this._gridTbl.tBodies[0].insertBefore(row, row2);
         break;
   }
   if (this._currCell) {
      var fld=this._currCell.firstChild;
      if (fld) setTimeout(function() { FOCUS(fld); });
   }

   var evt=createEventObject()
   evt.row = row.id;
   this._parentElem.fireEvent('onRowMove', evt);
}

function GridEdit.prototype.getColCaption(col) {
   try { return (this._headTbl.rows(0).cells(col).firstChild.innerHTML); } catch(e) { return ''; }
}

function GridEdit.prototype.setColCaption(col, caption) {
   try {
	   var CH = this._headTbl.rows(0).cells(col).firstChild;
	   CH.innerHTML = caption;
	   if (CH.firstChild.tagName) { //caption is an html element
		   var st = CH.firstChild.style;
		   st.whiteSpace = 'nowrap'; st.overflow = 'hidden'; st.textOverflow = 'ellipsis'; st.width = '100%';
	   } else { //caption is a simple text
		   CH.innerHTML = '<span style="height:100%; width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" align=absmiddle>' + caption + '</span>';
	   }
   } catch(e) {
      #LOG[2, 'EXCEPTION: file=GridView.js, function=setColCaption(), e='+e.description]
   }
}

function GridEdit.prototype.getColData(col) {
   for (var i=0, A=[]; i<this._numRows; i++) {
      var cell = this.getCellObj(i, col), value=null;
      if (cell) value = GET_INPUT(cell.firstChild);
      A.push(value);
   }
   return A;
}

function GridEdit.prototype.setColData(col, values) {
   var setFixedVal = false, fixedVal = null;
   if (typeof(values) != 'object') {
      setFixedVal = true;
      fixedVal = values;
   }
   if (!values) values=[];
   for (var i=0; i<this._numRows; i++) {
      var cell = this.getCellObj(i, col);
      var v;
      if (setFixedVal) {
         v = values;
      }
      else {
         v = values[i];
      }
      if (cell) SET_INPUT(cell.firstChild, v, false);
   }
}

function GridEdit.prototype.clearColData(col) {
   if (!values) values=[];
   for (var i=0; i<this._numRows; i++) {
      var cell = this.getCellObj(i, col);
      if (cell) CLEAR_INPUT(cell.firstChild, false);
   }
}

function GridEdit.prototype.searchCol(col, test) {
   var re=new RegExp(test.replace(/\?/g, '.').replace(/\*/g, '.*'), 'i');
   for (var i=0, A=[]; i<this._numRows; i++) {
      var cell = this.getCellObj(i, col), value=null;
      if (cell) value = GET_INPUT(cell.firstChild);
      if (value && re.test(value+'')) A.push(this._gridRows[i].id);
   }
   return A;
}

function GridEdit.prototype.sortCol(col, descending) {
   if (!col) col=this.getCurrCol();
   if (!col) return;
   PROMPT('This feature is not yet implemented');
}

function GridEdit.prototype.hideCol(col, hide) {
   if (hide !== false) hide=true;
   if (this._colDefs[col].hidden == hide) return;
   this._colDefs[col].hidden = hide;

   var flag = (hide ? 'none' : 'inline');
   var c  = this.getHeadCol(col);
   if (c) c.style.display = flag;
   var c  = this.getGridCol(col);
   if (c) c.style.display = flag;
   this.resizeColumns();
}


function GridEdit.prototype.getCellData(row, col) {
   var row  = this.getRowObj(row);
   var cell = this.getCellObj(row, col);
   return cell ? GET_INPUT(cell.firstChild) : null;
}

function GridEdit.prototype.setCellData(row, col, value) {
   var row  = this.getRowObj(row);
   var cell = this.getCellObj(row, col);
   if (cell) SET_INPUT(cell.firstChild, value, false);
}

function GridEdit.prototype.clearCellData(row, col) {
   var row  = this.getRowObj(row);
   var cell = this.getCellObj(row, col);
   if (cell) CLEAR_INPUT(cell.firstChild, false);
}

function GridEdit.prototype.editCell(row, col) {
   var row  = this.getRowObj(row);
   var cell = this.getCellObj(row, col);
   if (cell) EDIT_INPUT(cell.firstChild);
}

function GridEdit.prototype.focusCell(row, col) {
   var row  = this.getRowObj(row);
   var cell = this.getCellObj(row, col);
   if (cell) FOCUS(cell.firstChild);
}

function GridEdit.prototype.enableCell(row, col, enable) {
   var row  = this.getRowObj(row);
   var cell = this.getCellObj(row, col);
   if (!cell) return;
   cell.firstChild.readOnly = !enable;
}


//////////////////////////////////////////////////////////////////////////////////////
// INTERNAL: Utilities

function GridEdit.prototype.getRowObj(row) {
   if (row === null || row == undefined) return this._currRow;
   if (typeof row != 'object') {
      row = this._gridRows.item(row);
   } else if (row.tagName == 'TR') {
      return row;
   }
   return (row && !row.hide) ? row : null;
}

function GridEdit.prototype.getRowNum(row) {
   var rowobj = this.getRowObj(row);
   return rowobj ? rowobj.rowIndex : -1;
}

function GridEdit.prototype.getCellObj(row, col) {
   if (row === undefined || row === null) return null;
   if (col === undefined || col === null) return null;
   if (typeof row != 'object') row = this._gridRows.item(row);
   if (!row || row.hide) return null;
   var cell = row.cells.item(col) || null;
   if (!cell) return null;
   var fld = cell.firstChild;
   return (fld && fld.dt ? cell : null);
}

function GridEdit.prototype.getHeadCol(col) {
   return this._headTbl.firstChild.children.item(col) || null;
}

function GridEdit.prototype.getGridCol(col) {
   return this._gridTbl.firstChild.children.item(col) || null;
}

//////////////////////////////////////////////////////////////////////////////////////
// INTERNAL: Layout

function GridEdit.prototype.resize() {
   if (!this._ready) return;

   var W=this._parentElem.clientWidth, H=this._parentElem.clientHeight, h=(this._showHeader ? 17 : 0);
   var toolbarHeight = this._showToolbar ? 22 : 0 ;
   //
   // When parent is not yet rendered, clientWidth is 0 - use default in such case
   //
   if (W == 0) W = '100%';
   if (H == 0) H = this._initialHeight;

   var st=this._headBox.style; st.left=0; st.top=0; st.width=W; st.height=h; st.visibility=(this._showHeader ? 'inherit' : 'hidden')
   var st=this._gridBox.style; st.left=0; st.top=h; st.width=W; st.height=POS(H-h-toolbarHeight);
   this.resizeColumns();
   this.alignTables();
}

function GridEdit.prototype.resizeColumns() {
   if (!this._ready || !this._colDefs || !this._canResize) return;

   var HC=this._headTbl.firstChild.children;
   var GC=this._gridTbl.firstChild.children;
   var GP=this._headBox.children.item('GRIP');

   var totalW=0, freeW=0, freeN=0;
   for (var i=0, len=this._colDefs.length; i<len; i++) {
   var cdef=this._colDefs[i];
      if (cdef.hidden) continue;
      if (cdef.width != '*') totalW += POS(cdef.userW); else freeN++;
   }
   if (freeN > 0) freeW = (POS(this._gridBox.clientWidth+(this._gridBox.scrollHeight>this._gridBox.clientHeight ? -16 : 0)-totalW-1) || 75)/freeN;

   for (var i=0, x=0, len=this._colDefs.length; i<len; i++) {
      var cdef=this._colDefs[i];
      var w = !cdef.hidden && (cdef.width=='*' ? freeW : cdef.userW) || 0;

      if (w > 0) {
         x += w;
         HC[i].style.width   = w;
         GC[i].style.width   = w;
         GP[i].style.left    = x-5;
         GP[i].style.display = 'block';
      } else {
         GP[i].style.display = 'none';
      }
   }
}

function GridEdit.prototype.resetColumn(n) {
   if (!this._ready || !this._colDefs || !this._canResize) return;
   var cdef=this._colDefs[n];
   if (!cdef) return;
   cdef.userW = POS(cdef.width, 75);
   this.resizeColumns();
   this.alignTables();
}

function GridEdit.prototype.autoFitColumn(n) {
   if (!this._ready || !this._colDefs || !this._canResize) return;
   var cdef=this._colDefs[n];
   if (!cdef) return;

   var hcol = this._headTbl.firstChild.children[n];
   var gcol = this._gridTbl.firstChild.children[n];
   hcol.style.width = 10;  // reduce column width so that the fields widths can be calculated
   gcol.style.width = 10;

   var hlbl = this._headTbl.rows(0).cells(n).firstChild;
   var lblW = hlbl ? hlbl.scrollWidth+6 : 10;
   var fldW = 0;

   for (var i=0; i<this._numRows; i++) {
      var cell=this._gridRows[i].cells(n), fld=cell.firstChild;
      if (!fld || !fld.dt) continue;
      fldW = MAX(fldW, fld.scrollWidth);
   }
   if (cdef.type != 'str') fldW += 16; // compensate for dropdown arrow

   cdef.userW = MAX(lblW, fldW);
   this.resizeColumns();
   this.alignTables();
}

function GridEdit.prototype.alignTables() {
   if (!this._ready) return;
   this._headBox.scrollLeft = this._gridBox.scrollLeft;
}

function GridEdit.prototype.gripperDown() {
   var obj = event.srcElement;
   if (obj.id !='GRIP' || this._gripper) return;
   var col = POS(obj.column);
   if (isNaN(col)) return;
   var off = event.screenX-this._colDefs[col].userW;
   if (isNaN(off)) return;
   var hcol = this._headTbl.firstChild.children[col];
   var gcol = this._gridTbl.firstChild.children[col];
   this._gripper = {obj:obj, col:col, off:off, hcol:hcol, gcol:gcol}
   obj.onmousemove = this.gripperMove.bindAsEventListener(this);
   obj.onmouseup   = this.gripperUp.bindAsEventListener(this);
   obj.onlosecapture = this.gripperUp.bindAsEventListener(this);
   obj.setCapture();
}

function GridEdit.prototype.gripperMove() {
   if (!this._gripper) return;
   var w = POS(event.screenX-this._gripper.off);
   this._colDefs[this._gripper.col].userW = w;
   this._gripper.hcol.style.width = w;
   this._gripper.gcol.style.width = w;
}

function GridEdit.prototype.gripperUp() {
   if (!this._gripper) return;
   this._gripper.obj.releaseCapture();
   this._gripper = null;
   this.resizeColumns();
   this.alignTables();
}

function GridEdit.prototype.gripperDblClick() {
   var obj = event.srcElement;
   if (obj.id !='GRIP' || this._gripper) return;
   this.autoFitColumn(POS(obj.column));
}

function GridEdit.prototype.onInputChange(fld) {
   var td=fld.parentElement, tr=td.parentElement;

   var evt = {};
   evt.row = tr.id;
   evt.rowIndex = tr.rowIndex;
   evt.col = td.id;
   evt.field = GET_INPUT(fld); // NOTE: change fld to GET_INPUT(fld) in all other places!!!
   if (tr.lParam || tr.lParam == 0 ) evt.lParam = tr.lParam;

   this.onEvent("onCellChange", evt, event);
}

function GridEdit.prototype.onFieldEnter(fld) {
   var td=fld.parentElement, tr=td.parentElement;
   this.selectRow(tr.id, 'focus');
   this._currCell = td;
   //
   // There's no onCellFocus event in IE DOM, so we use regular callback
   //
   if (this._parentElem.onFieldEnter) {
      var evt = this._parentElem.document.createEventObject();
      evt.row = tr.id;
      evt.rowIndex = tr.rowIndex;
      evt.col = td.id;
      evt.field = fld;
      this._parentElem.onFieldEnter(evt);
   }
}

function GridEdit.prototype.onFieldExit(fld) {
   this._currCell = null;
}

function GridEdit.prototype.onFieldKey(fld) {
   if (!this._ready) return false;
   var fld2=null;

   var code=event.keyCode, key=code+'';
   if (event.shiftKey) key = 'S'+key;
   if (event.ctrlKey)  key = 'C'+key;
   if (event.altKey)   key = 'A'+key;
   if (key == ''+code && (code >= 48 && code < 112 || code > 123) ) return false;

   switch (key) {
      case #[S_CtrlENTER]:    // Ctrl+ENTER
         this.addRow();
         return true;

      case #[S_CtrlEND]:    // Ctrl+END
         if (this._numRows>0) {
            var row=this._gridRows[this._numRows-1], cid=this.getCurrCol();
            if (cid) this.focusCell(row, cid); else this.selectRow(row, 'focus');
         }
         return true;

      case #[S_CtrlShftEND]:   // Ctrl+Shift+END
         this.moveRow('LAST');
         return true;

      case #[S_CtrlHOME]:    // Ctrl+HOME
         if (this._numRows>0) {
            var row=this._gridRows[0], cid=this.getCurrCol();
            if (cid) this.focusCell(row, cid); else this.selectRow(row, 'focus');
         }
         return true;

      case #[S_CtrlShftHOME]:   // Ctrl+Shift+HOME
         this.moveRow('FIRST');
         return true;

      case #[S_UP]:     // UP
         var fld2 = nearRow(fld,-1);
         if (fld2) FOCUS(fld2);
         HIDE_POPUP();
         return true;

      case #[S_CtrlShftUP]:   // Ctrl+Shift+UP
         this.moveRow('PREV');
         HIDE_POPUP();
         return true;

      case #[S_DOWN]:     // DOWN
         fld2 = nearRow(fld,+1);
         if (fld2) FOCUS(fld2);
         HIDE_POPUP();
         return true;

      case #[S_CtrlShftDOWN]:   // Ctrl+Shift+DOWN
         this.moveRow('NEXT');
         HIDE_POPUP();
         return true;

      case #[S_CtrlShftINS]:   // Ctrl+Shift+INS
         this.addRow();
         return true;

      case #[S_CtrlShftDEL]:   // Ctrl+Shift+DEL
         this.deleteRow();
         return true;
   }
   return false;

   function nearRow(obj, off) {
      var td=obj;
      if (td && td.tagName != 'TD') td = td.parentElement;
      if (!td || td.tagName != 'TD') return null;
      var tr=td.parentElement, tbl=tr.parentElement;
      var tdx=td.cellIndex, trx=tr.rowIndex;
      if (trx+off < 0 || trx+off >= tbl.rows.length) return null;
      tr = tbl.rows(trx+off);
      if (!tr) return null;
      td = tr.cells(tdx);
      if (!td) return null;
      var fld = td.firstChild;
      if (fld && fld.dt) return fld;
      return null;
   }

}

function GridEdit.prototype.onFieldFirstClick(fld) {
   return true; // disable dropdown on first click in field
}

function GridEdit.prototype.onToolbarAdd() {
   if (this.onEvent("onRowAdd", null, null) == true) return;
   //
   // Add empty row
   //
    //this.addRow();
}

function GridEdit.prototype.onToolbarDelete() {
   var row = this._currRow;
   if (!row) return;

   var evt = {};
   evt.row = row.id;
   evt.rowIndex = row.rowIndex;
   if (row.lParam || row.lParam == 0 ) evt.lParam = row.lParam;

   if (this.onEvent("onRowDelete", evt, null) == true) return;
   //
   // Delete current row
   //
   this.deleteRow();
}

function GridEdit.prototype.onToolbarDetails(){
	var row = this._currRow;
	var evt = {};
	if (row) {
		evt.row = row.id;
		evt.rowIndex = row.rowIndex;
		if (row.lParam || row.lParam == 0 ) evt.lParam = row.lParam;
	}

  if (this.onEvent("onDetailedView", evt, null) == true) return;
}


function GridEdit.prototype.onToolbarMoveUpward() {
    if (this.onEvent("onMoveUpward", null, null) == true) return;
}


function GridEdit.prototype.onToolbarMoveDownward() {
    if (this.onEvent("onMoveDownward", null, null) == true) return;
}