Ajax: Excel风格的HTML Table输入控件

来源:互联网 发布:外汇画线分析软件 编辑:程序博客网 时间:2024/06/05 04:19

[一]:内部表格的区域划分与区域标识

实际效果: http://www.weiqihome.com/scotttable.jsp 
 
首先需要说明的是,开发过程采用了许多Prototype与scriptaculous的方法和组件。
 
要实现上下行、左右列均可固定,只有中间行列可滚动的表格,需要将整个表格划分成16个部分,具体如下:  # left fixed columns   scrollable columns  right fixed columns  1  top fixed rows      2  scrollable rows      3  bottom fixed rows    
 
这16个单元格内又放置16个表格。其中又有8个单元格又要分别放在8个可滚动的DIV内。
 buildFixedTable: function() {
  return Builder.node("TABLE", {cellSpacing:'0', cellPadding:'0', style:'table-layout:fixed;border-collapse:collapse;margin:0;'});
 },
 buildRelativeDiv: function() {
  return Builder.node("DIV", {style:'position:relative; overflow:hidden;'});
 },
第一个function运用builder创建一个表格,注意table-layou:fixed非常重要。
第二个function运用builder创建一个可滚动的DIV,注意overflow:hidden,因为我们需要提供单独的垂直滚动条,统一负责四个垂直方向的DIV的滚动。也需要提供单独的水平滚动条,统一负责四个水平方向的DIV的滚动。
 
另外,我们需要给每个区域标注一下。首先,四个列区域分别为no,left,center,right;四个行区域分别为header,top,buffer,footer。这样行列组合成每个区域的标识。例如top行的四个区域的标识分别为:no-top,left-top,center-top,right-top。同时为每个行赋予相应区域的标识。
 
例如如果要取任一单元格所在行的数据,只需要提供区域名和所在区域的行号即可。实现如下:
 getRowData: function(rowIndex, area, rowId) {
  var data = new Array(0);
  if (rowId == "inserted") {
   data[0] = null; //important, can't be ""
  } else
   data[0] = rowId;
  if (area == "footer") {
   return data;
  }
  var getFunction = null;
  if (this.leftFixedColumns > 0) {
   getFunction = ("_get-left-" + area + "-row-data").camelize();
   eval("this." + getFunction + "(rowIndex, data);");
  }
  getFunction = ("_get-center-" + area + "-row-data").camelize();
  eval("this." + getFunction + "(rowIndex, data);");
  if (this.rightFixedColumns > 0) {
   getFunction = ("_get-right-" + area + "-row-data").camelize();
   eval("this." + getFunction + "(rowIndex, data);");
  }
  return data;
 },
这里利用javascript的反射机制调用相应的方法,例如_getLeftTopRowData,_getCenterTopRowData,_getRightTopRowData,_getLeftBufferRowData,_getCenterBufferRowData,_getRightBufferRowData。
 
同样的方法有很多,如getColCells(取得任一单元格所在列的数据)、getNoCell(取得任一单元格所在行的序号单元格)、getAdjustedColIndex(取得任一单元格所在列的全局列号)、getAdjustedRowIndex(取得任一单元格所在行的全局行号)、setRowCssClass(设置任一单元格所在行的css样式)等。
[二]:外部表格与水平垂直滚动条
外部表格的作用是提供水平垂直滚动条,它们分别与多个可滚动的DIV联系,并且滚动条的大小随着滚动区域的内容动态改变。
 
先介绍水平垂直滚动区域。前一节介绍了共有7个可滚动区域,它们分别是:centerHeaderBox,centerTopBox,noBufferBox,leftBufferBox,centerBufferBox,rightBufferBox,centerFooterBox;它们分别容纳着7个内部表格:centerHeaderTable,centerTopTable,noBufferTablex,leftBufferTable,centerBufferTable,rightBufferTable,centerFooterTable。
 
外部表格的构建如下:
 buildOuterTable: function() {
  this.verticalBar = Builder.node("DIV", {style:'position:relative; overflow:scroll;width:19px;'});
  this.heightDiv = document.createElement("div");
  this.heightDiv.style.width  = "1px";
  this.verticalBar.style.height=this.visibleHeight + 18;
  this.verticalBar.appendChild(this.heightDiv);
  this.verticalBar.onscroll = this.verticalScroll.bindAsEventListener(this);
  this.verticalBarBox = Builder.node("DIV", {style:'position:relative; overflow:hidden;width:20px;'});
  this.verticalBarBox.style.height=this.visibleHeight;
  this.verticalBarBox.appendChild(this.verticalBar);
  this.horizonBar = Builder.node("DIV", {style:'position:relative; overflow:scroll;height:19px;'});
  this.horizonBar.style.width = this.visibleWidth + 18;
  this.horizonBar.appendChild(this.widthDiv);
  this.horizonBar.onscroll = this.horizonScroll.bindAsEventListener(this);
  this.horizonBarBox = Builder.node("DIV", {style:'position:relative; overflow:hidden;width:20px;'});
  this.horizonBarBox.style.width=this.visibleWidth + 2
  this.horizonBarBox.appendChild(this.horizonBar);
  this.outerTable = this.buildFixedTable();
  var row = this.outerTable.insertRow();
  var cell = row.insertCell();
  cell.style.width = this.visibleWidth + 3;
  cell.appendChild(this.innerTable);
  cell = row.insertCell();
  cell.style.width = "19px";
  cell.appendChild(this.verticalBarBox);
  row = this.outerTable.insertRow();
  var cell1 = row.insertCell();
  var cell2 = row.insertCell();
  row.removeChild(cell1);
  cell2.colSpan = "2";
  cell2.appendChild(this.horizonBarBox);
 },
 
可以看出,水平垂直滚动条都有三层:外部Box、中间显示滚动条的DIV、最里面代表实际大小的DIV(另外水平部分的widthDiv在外面创建)。外部Box的style是:overflow:hidden,这样可不显示滚动条多余的部分。当滚动区域的内容即行数或列宽度改变时,只需要改变最里面的DIV的style的width或height即可。
另外请注意,水平垂直滚动条的style是overflow:scroll,这样保证即使不需要滚动时,滚动条依然在。
另外在创建内部表格的同时,也将这另外几个可滚动DIV的onscroll事件与其Scroll方法关联起来,这样任一DIV的滚动导致其他相应DIV同时一致滚动。
 
滚动实现如下:
 horizonScroll: function() {
  this.centerBufferBox.scrollLeft = this.horizonBar.scrollLeft;
  this.centerHeaderBox.scrollLeft = this.horizonBar.scrollLeft;
  if (this.topFixedRows > 0) {
   this.centerTopBox.scrollLeft = this.horizonBar.scrollLeft;
  }
  
  if (this.bottomFixedRows > 0) {
   this.centerFooterBox.scrollLeft = this.horizonBar.scrollLeft;
  }
  
 },
 verticalScroll: function() {
  this.centerBufferBox.scrollTop = this.verticalBar.scrollTop;
  this.noBufferBox.scrollTop = this.verticalBar.scrollTop;
  if (this.leftBufferBox) {
   this.leftBufferBox.scrollTop = this.verticalBar.scrollTop;
  }
  if (this.rightBufferBox) {
   this.rightBufferBox.scrollTop = this.verticalBar.scrollTop;
  }
 },
 
 centerBufferScroll: function() {
  this.verticalBar.scrollTop = this.centerBufferBox.scrollTop;
  if (this.leftBufferBox) {
   this.leftBufferBox.scrollTop = this.verticalBar.scrollTop;
  }
  if (this.rightBufferBox) {
   this.rightBufferBox.scrollTop = this.verticalBar.scrollTop;
  }
 },
 leftBufferScroll: function() {
  this.verticalBar.scrollTop = this.leftBufferBox.scrollTop;
  this.centerBufferBox.scrollTop = this.verticalBar.scrollTop;
  if (this.rightBufferBox) {
   this.rightBufferBox.scrollTop = this.verticalBar.scrollTop;
  }
 },
 rightBufferScroll: function() {
  this.verticalBar.scrollTop = this.rightBufferBox.scrollTop;
  this.centerBufferBox.scrollTop = this.verticalBar.scrollTop;
  if (this.leftBufferBox) {
   this.leftBufferBox.scrollTop = this.verticalBar.scrollTop;
  }
 }
[三]:线条与滚动条的精确控制
第一节介绍了内部表格,共有16个区域,每个区域内都是单独的表格。如果每个表格都设置同样的线条,那么表格相邻的两条线会重叠形成粗线条,让人感觉不好。而表格之间必须要有线条指簦蚁咛豸于哪个表格,运行效果也不同?lt;/div>
 
例如,水平滚动时,固定区域与可滚动区域之间有一根线条,如果这根线条属于滚动区域,那么滚动时,此线条可能不在可视区内,导致固定区域与滚动区域之间没有线条。所以此线条应该属于固定区域。
 
现在我们应该清楚,整个表格控件,其表格层次又有三层:外部表格、内部表格、区域表格。除外部没有线条外,如何协调好内部表格与区域表格的线条,对整个表格的可视效果非常重要。
 
因为整个控件外部肯定要有个线条,所以内部表格外面由一矩形线条包围,这样区域表格与内部表格相邻的部分不能有线条。固定区域与滚动区域的线条属于固定区域,滚动区域内的区域表格内部依然要有线条。当你在滚动区域内增加一行到末尾时,要补上上一行的下部线条,同时去掉最后一行的下部线条;同理当删除最后一行时,要去掉前一行的下部线条。如果表格行很少时,还要保留该线条。
 
对于水平垂直滚动条的宽度和高度,也要将线条考虑进去。否则导致滚动条的滚动范围与实际滚动区域的宽度或高度不一致。
 
在每增加一行或删除一行时:
this.heightDiv.style.height = this.visibleHeight + (this.centerBufferTable.rows.length) * (this.rowHeight+1) - this.centerBufferBox.offsetHeight;
[四]:事件与单元格的遍历
本节介绍表格控件的事件处理,以及如何用键盘和鼠标遍历单元格,以及如何编辑、取消单元格的编辑。
 
1. 鼠标事件
首选取得当前单元格:
this.lastSelectedCell = Event.findElement(ev, "TD");
this.lastSelectedRow = this.lastSelectedCell.parentNode;
 
对每一个区域表格都注册鼠标单击事件与双击事件:
  this.centerBufferTable.onclick = this.onClick.bindAsEventListener(this);
  this.centerBufferTable.ondblclick = this.editCell.bindAsEventListener(this);
这里注册当双击时,就编辑单元格。如何编辑单元格将在第五节介绍。
当单击时,就选中当前单元格,同时不选中原来选中的单元格:
Element.removeClassName(this.lastSelectedCell, "cellselected");
Element.addClassName(this.lastSelectedCell, "cellselected");
同时,设置
 
另外还要考虑,如果是checkbox或radio风格的单元格,当单击时就改变其状态。
 
2. 键盘事件
Event.observe(document, 'keypress', this.onKeyPress.bindAsEventListener(this));
这里统一设置键盘事件,而不是每个区域表格都注册。
 
首先处理键盘事件时,使事件停止,不致再往下传播。
Event.stop(ev);很重要,不然当编辑单元格时,事件会传播到编辑控件。
 
同鼠标事件的处理,当前后左右遍历单元格时,就选中当前单元格,同时不选中原来选中的单元格。具体算法:
  //UP & DOWN
  if(ev.keyCode==40 || ev.keyCode==38){
   if (ev.keyCode==38) {
    if (this.lastSelectedRow.rowIndex > 0) {
     Element.removeClassName(this.lastSelectedCell, "cellselected");
     this.lastSelectedRow = this.lastSelectedRow.parentNode.rows[this.lastSelectedRow.rowIndex-1];
     this.lastSelectedCell = this.lastSelectedRow.cells[this.lastSelectedCell.cellIndex];
     Element.addClassName(this.lastSelectedCell, "cellselected");
    }
   }
   if (ev.keyCode==40) {
    if (this.lastSelectedRow.rowIndex < this.lastSelectedRow.parentNode.rows.length-1) {
     Element.removeClassName(this.lastSelectedCell, "cellselected");
     this.lastSelectedRow = this.lastSelectedRow.parentNode.rows[this.lastSelectedRow.rowIndex+1];
     this.lastSelectedCell = this.lastSelectedRow.cells[this.lastSelectedCell.cellIndex];
     Element.addClassName(this.lastSelectedCell, "cellselected");
    }
   }
  }
当回车事件时,如果原来是编辑状态,则停止编辑,否则编辑当前单元格。
  //ENTER
  else if(ev.keyCode==13) {
   if (this.editor != null)
    this.editStop(true);
   else
    this.editCell();
  }
TAB键和左右是相关的:
  //RIGHT LEFT
  else if(ev.keyCode==37 || ev.keyCode==39){
   if (ev.keyCode==37) {
    navLeft = true;
   }
   if (ev.keyCode==39) {
    navRight = true;
   }
  }
  //TAB
  else if(ev.keyCode==9  && !ev.shiftKey) {
   navRight = true;
   if (lastEditing) {
    nextEditing = true;
   }
  }
  else if(ev.keyCode==9  && ev.shiftKey) {
   navLeft = true;
   if (lastEditing) {
    nextEditing = true;
   }
  }
F2键编辑当前单元格:
  //F2
  else if(ev.keyCode==113) {
   this.editCell();
  }
ESC键取消编辑:
  //ESC
  else if(ev.keyCode==27) {
   this.editStop(false);
  }
SPACE键也是编辑当前单元格:
  //SPACE
  else if(ev.keyCode==32) {
   if (this.editor == null) {
    this.editCell();
   }
  }

3. 使当前单元格始终在可视区
当用键盘或鼠标遍历单元格时,如果当前单元格部分或全部不在可视区时,必须滚动使其在可视区内,同时协调相应的其他的区域滚动。
  var element = this.lastSelectedCell;
  var x = element.x ? element.x : element.offsetLeft;
        var y = element.y ? element.y : element.offsetTop;
  var divParent = this.lastSelectedRow.parentNode.parentNode.parentNode;
  if (divParent.scrollTop > y) {
   this.verticalBar.scrollTop = y;
   this.verticalScroll();
  }
  if (divParent.scrollTop + divParent.offsetHeight < y + element.offsetHeight) {
   this.verticalBar.scrollTop = y + element.offsetHeight - divParent.offsetHeight;
   this.verticalScroll();
  }
  if (divParent.scrollLeft > x) {
   this.horizonBar.scrollLeft = x;
   this.horizonScroll();
  }
  if (divParent.scrollLeft + divParent.offsetWidth < x + element.offsetWidth) {
   this.horizonBar.scrollLeft = x + element.offsetWidth - divParent.offsetWidth;
   this.horizonScroll();
  }
[五]:单元格的编辑
从本节开始将重点介绍单元格的编辑、显示、数据的修改及保存到服务器端。
 
首先要说明的是,单元格的实际值、编辑、显示是不同的。例如checkbox和radio的编辑和显示是一样的;price的编辑和显示是不一样的,因为显示时可能还带币种。
实际值:cell.value
编辑类:Editor_xx
显示类:Render_xx
 
前一节介绍当回车、F2、SPACE、双击时均可导致编辑单元格。
 editCell: function(fl) {
  if (this.editor != null) this.editStop(true);
  var c = this.lastSelectedCell;
  eval("this.editor = new Editor_" + this.editTypes[this.getAdjustedColIndex(c)] + "(c, this);");
  if (!fl && this.editor.changeState()) {
   this.cellUpdated();
   this.editor=null;
   this.renderCell(this.lastSelectedCell);
  } else {
   Element.removeClassName(c, "editable");
   this.editor.edit();
  }
 },
 editStop: function(fl) {
  if(this.editor!=null){
   Element.removeClassName(this.lastSelectedCell, "editable");
   this.editor.detach(fl);
   this.editor=null;
  }
  this.renderCell(this.lastSelectedCell);
 },
这里同样利用反射机制,自动实例化不同的编辑类。
 
首先看看基类的定义:
Editor_base.prototype = {
 detach: function(fl) {
  if (this.obj && this.obj.parentNode) {
   this.obj.parentNode.removeChild(this.obj);
  }
 },
 
 setValue: function(val) {
  if (val != this.cell.value) {
   this.cell.value = val;
   this.grid.cellUpdated();
  }
 },
 
 onKeyPress: function(e){
  (e||event).cancelBubble = true;
  if ((e||event).keyCode==13) {
   this.grid.editStop(true);
  } else if(e.keyCode==27) {
   this.grid.editStop(false);
  } else if(e.keyCode==9) {
   this.grid.onKeyPress(e);
  }
 },
 
 getValue: function() {
  return this.cell.value;
 },
 changeState: function() {
  return false;
 }
}
detach方法使去除编辑控件,使单元格重新处于显示状态。
setValue判断值是否改变,如果改变激发cellUpdated事件。
onKeyPress监听编辑控件的事件,如果回车、ESC或TAB时,则停止编辑,且TAB时,自动使下一个单元格处于编辑状态。注意textarea需要重新定义该方法,因为其回车是换行,只有同时shift或ctrl处于按下状态,才编辑停止。
getValue则是取得单元格的实际值。
changeState则可以直接改变单元格的状态。
 
另外每个控件都有edit方法,因为每个控件的编辑控件不一样。
1. 只读列的edit
Editor_read = Class.create();
Object.extend(Object.extend(Editor_read.prototype, Editor_base.prototype), {
 initialize: function(cell, grid) {
  this.cell = cell;
  this.grid = grid;
 },
 edit: function() {}
});
2. 简单文本编辑
Editor_text = Class.create();
Object.extend(Object.extend(Editor_text.prototype, Editor_base.prototype), {
 initialize: function(cell, grid) {
  this.cell = cell;
  this.grid = grid;
 },
 edit: function() {
  this.val = this.getValue();
  this.obj = document.createElement("INPUT");
  this.obj.style.height = (this.cell.offsetHeight-7)+"px";
  this.obj.style.width = (this.cell.offsetWidth-7)+"px";
  this.obj.className="dhx_combo_edit";
  this.obj.wrap = "soft";
  this.obj.style.textAlign = "left";
  this.obj.onclick = function(e){(e||event).cancelBubble = true}
  this.obj.onkeydown = this.onKeyPress.bindAsEventListener(this);
  this.obj.value = this.val;
  this.cell.innerHTML = "";
  this.cell.appendChild(this.obj);
  this.obj.onselectstart=function(e){  if (!e) e=event; e.cancelBubble=true; return true;  };
  this.obj.focus();this.obj.focus();
 },
 detach: function(fl) {
  if (fl) {
   this.setValue(this.obj.value);
  }
  if (this.obj.parentNode) {
   this.obj.parentNode.removeChild(this.obj);
  }
 }
});
这里是直接使编辑控件占用单元格的空间。
3. 多行textarea的编辑
Editor_textarea = Class.create();
Object.extend(Object.extend(Editor_textarea.prototype, Editor_base.prototype), {
 initialize: function(cell, grid) {
  this.cell = cell;
  this.grid = grid;
 },
 edit: function() {
  this.val = this.getValue();
  this.obj = document.createElement("TEXTAREA");
  this.obj.className="dhx_textarea";
  var arPos = Util.getPosition(this.cell);
  //this.obj.value = this.cell.innerHTML.replace(/<br[^>]*>/gi,"/n");
  document.body.appendChild(this.obj);
  this.obj.style.left = arPos[0]-Position.realOffset(this.cell)[0]+"px";
  this.obj.style.top = arPos[1]+this.cell.offsetHeight-Position.realOffset(this.cell)[1]+"px";
  if(this.cell.scrollWidth<200)
   this.obj.style.width = "200px";
  else
   this.obj.style.width = this.cell.scrollWidth+"px";
  this.obj.style.display = "";
  this.obj.focus();
  this.obj.onclick = function(e){(e||event).cancelBubble = true}
  this.obj.onkeydown = this.onKeyPress.bindAsEventListener(this);
  this.obj.value = this.val;
  //this.cell.appendChild(this.obj);
  this.obj.onselectstart=function(e){  if (!e) e=event; e.cancelBubble=true; return true;  };
  this.obj.focus();this.obj.focus();
  this.obj.value = this.val;
 },
 onKeyPress: function(e){
  (e||event).cancelBubble = true;
  if ((e||event).keyCode==13 && (e.altKey || e.shiftKey)) {
   this.grid.editStop(true);
  } else if(e.keyCode==27) {
   this.grid.editStop(false);
  } else if(e.keyCode==9) {
   this.grid.onKeyPress(e);
  }
 },
 
 detach: function(fl) {
  if (fl) {
   var val = this.obj.value.replace(//n/g,"<br/>");
   this.setValue(val);
  }
  document.body.removeChild(this.obj);
 },
 getValue: function() {
  return this.cell.value.replace(/<br[^>]*>/gi,"/n")
 }
 
});
注册显示textarea的位置,必须先确定当前单元格的位置。
 getPosition: function(oNode,pNode) {
  if(!pNode)
   var pNode = document.body
  
  var oCurrentNode=oNode;
  var iLeft=0;
  var iTop=0;
  while ((oCurrentNode)&&(oCurrentNode!=pNode)) {
   iLeft+=oCurrentNode.offsetLeft;
   iTop+=oCurrentNode.offsetTop;
   oCurrentNode=oCurrentNode.offsetParent;//isIE()?:oCurrentNode.parentNode;
  }
  return new Array(iLeft,iTop);
 }

 
4. checkbox和radio的编辑则是直接改变状态
对checkbox:
 changeState: function() {
  if (this.cell.value > "0") {
   this.cell.value = "0";
  } else
   this.cell.value = "1";
  return true;
 }
对radio:
 changeState: function() {
  if (this.cell.value <= "0") {
   this.cell.value = "1";
   return true;
  }
  return false;
 }
注意,如果radio单元格处于选中状态,只有选择同列的其他单元格,才能改变其值。
这个是在cellUpdated方法中单独处理的:
  if (editType == "radio" && this.radioUnique) {
   if (cv > 0) {
    var cellIndex = c.cellIndex;
    var cells = this.getColCells(cellIndex, r.area.split("-")[0]);
    for (var i=0;i<cells.length;i++) {
     var tc = cells[i];
     if (tc != c) {
      tc.value = uv;
      this.renderCell(tc);
     }
    }
   }
  }
 
5. 最后是combo或select的编辑
Editor_combo = Class.create();
Object.extend(Object.extend(Editor_combo.prototype, Editor_base.prototype), {
 initialize: function(cell, grid) {
  this.cell = cell;
  this.grid = grid;
  this.combo = this.grid.getCombo(this.cell.cellIndex);
  this.editable = true
 },
 
 edit: function() {
  this.val = this.getValue();
  var arPos = Util.getPosition(this.cell)//,this.grid.objBox)
  this.obj = document.createElement("INPUT");
  this.obj.className="dhx_combo_edit";
  this.obj.style.height = (this.cell.offsetHeight-7)+"px";
  this.obj.style.width = (this.cell.offsetWidth-7)+"px";
  this.obj.wrap = "soft";
  this.obj.style.textAlign = this.cell.align;
  this.obj.onclick = function(e){(e||event).cancelBubble = true}
  this.obj.value = this.val
  this.list =  document.createElement("SELECT");
  this.list.editor_obj = this;
  this.list.className='dhx_combo_select';
  document.body.appendChild(this.list)//nb:this.grid.objBox.appendChild(this.listBox);
  this.list.style.width=this.cell.offsetWidth+"px";
  this.list.style.left = arPos[0]-Position.realOffset(this.cell)[0]+"px";//arPos[0]
  this.list.style.top = arPos[1]+this.cell.offsetHeight-Position.realOffset(this.cell)[1]-4+"px";//arPos[1]+this.cell.offsetHeight;
  this.list.size="6";
  this.list.onclick = function(e){
   var ev = e||window.event;
   var cell = ev.target||ev.srcElement
   //tbl.editor_obj.val=cell.combo_val;
   if (cell.tagName=="OPTION") cell=cell.parentNode;
   cell.editor_obj.setValue(cell.value);
   cell.editor_obj.editable=false;
   cell.editor_obj.detach();
  }
  var comboKeys = this.combo.getKeys();
  var fl=false
  var selOptId=0;
  for(var i=0;i<comboKeys.length;i++){
   var val = this.combo.get(comboKeys[i])
   this.list.options[this.list.options.length]=new Option(val,comboKeys[i]);
   if(comboKeys[i]==this.val){
    selOptId=this.list.options.length-1;
    fl=true;
   }
  }
  if(fl==false) {//if no such value in combo list
   this.list.options[this.list.options.length]=new Option(this.val,this.val===null?"":this.val);
      selOptId=this.list.options.length-1;
  }
  this.cstate=1;
  if(this.editable){
   this.cell.innerHTML = "";
  } else {
   this.obj.style.width="1px";
   this.obj.style.height="1px";
  }
  this.obj.onkeydown = this.onKeyPress.bindAsEventListener(this);
  this.cell.appendChild(this.obj);
  this.list.options[selOptId].selected=true;
  this.obj.focus();
  this.obj.focus();
  if (!this.editable) {
   this.obj.style.visibility="hidden";
   this.list.focus();
   this.list.onkeydown = this.onKeyPress.bindAsEventListener(this);
  }
 },
 onKeyPress: function(e){
  (e||event).cancelBubble = true;
  if ((e||event).keyCode==13) {
   this.grid.editStop(true);
  }
  else if ((e||event).keyCode==40) {
   if (this.list.selectedIndex < this.list.options.length - 1) {
    this.list.selectedIndex = this.list.selectedIndex + 1;
   }
   this.obj.value = this.list.options[this.list.selectedIndex].value;
  }
  else if ((e||event).keyCode==38) {
   if (this.list.selectedIndex > 0) {
    this.list.selectedIndex = this.list.selectedIndex - 1;
    this.obj.value = this.list.options[this.list.selectedIndex].value;
   }
  } else if(e.keyCode==27) {
   this.grid.editStop(false);
  } else if(e.keyCode==9) {
   this.grid.onKeyPress(e);
  }
 },
 detach: function(fl) {
  if (fl) {
   var val = null
   if(this.list.parentNode!=null){
    if (this.editable)
     if(this.obj.value!=this.text) {
      val = this.obj.value;
     } else {
      val = this.val;
     }
    else
     val = this.list.value;
   }
   this.setValue(val);
  }
  if(this.list.parentNode)
   this.list.parentNode.removeChild(this.list);
  if(this.obj.parentNode)
   this.obj.parentNode.removeChild(this.obj);
 }
});
Editor_list = Class.create();
Object.extend(Object.extend(Editor_list.prototype, Editor_combo.prototype), {
 initialize: function(cell, grid) {
  this.cell = cell;
  this.grid = grid;
  this.combo = this.grid.getCombo(this.cell.cellIndex);
  this.editable = false
 }
});
这里结合了前面input和textarea的定位方法。
[六]、单元格的显示
本节介绍如何显示单元格,包括如何以text,checkbox,radio来显示单元格。
 
Render_base = Class.create();
Render_base.prototype = {
 initialize: function(grid) {
  this.grid = grid;
 },
 getRenderHTML: function(val) {}
}
Render_text = Class.create();
Object.extend(Object.extend(Render_text.prototype, Render_base.prototype), {
 getRenderHTML: function(val) {
  if (val == null || val == "") {
   return "&nbsp;";
  }
  return val;
 }
});
Render_check = Class.create();
Object.extend(Object.extend(Render_check.prototype, Render_base.prototype), {
 getRenderHTML: function(val) {
  if (val == "") {
   val = "0";
  }
  if (val == "1" || val == "0") {
   return "<img src='"+this.grid.imgURL+"item_chk"+val+".gif'>";
  } else if (val == null) {
   return "&nbsp;";
  } else
   return val;
 }
});
Render_radio = Class.create();
Object.extend(Object.extend(Render_radio.prototype, Render_base.prototype), {
 getRenderHTML: function(val) {
  if (val == "") {
   val = "0";
  }
  if (val == "1" || val == "0") {
   return "<img src='"+this.grid.imgURL+"radio_chk"+val+".gif'>";
  } else if (val == null) {
   return "&nbsp;";
  } else
   return val;
 }
});

可见,只要实现不同的getRenderHTML就可以了。
可以很方便的扩展其他显示类,如price,color等。

 

原创粉丝点击