:: Forum >> Version 2 >>

Possible Solution: Grid with combo editable cell as in attached example

I was looking for a method to edit columns with combo elements.
I finally "emulated" one as shown below.

It works fine on FF (1.5) and on IE 6, IE 7 (had multiple IEs installed) acceptable.

I need comments on this post in order to see what can be improved.

var myData = [
    [
"The Godfather",    1,    1,    1972,    "USA"],
    [
"Pulp Fiction",    2,    2,    1994,    "USA"],
    [
"American Beauty"3,    3,    1997,    "USA"],
    [
"Vertigo",            4,    4,    1958,    "USA"],
    [
"Apocalypse Now",    1,    5,    1979,    "USA"]   // no comma on the last line!
];

var 
myHeaders = ["Title""Director""Genre""Year""Country"];

 
<!-- 
create controls -->
var 
itemText1 = ["Francis Ford Coppola","Quentin Tarantino","Sam Mendes","Alfred Hitchcock"];
var 
itemValues1 = [1,2,3,4];

var 
itemText2 = ["Crime/Drama","Action/Crime/Drama","Drama","Mystery/Thriller/Romance","Adventure/Drama/War"];
var 
itemValues2 = [1,2,3,4,5];

var 
myFooters1 = [""""""""];
var 
myFooters2 = [""""""""];

    var 
list1 = new AW.UI.List;
    
list1.setItemText(itemText1);
    
list1.setItemValue(itemValues1);
    
list1.setItemCount(itemText1.length);

    var 
list2 = new AW.UI.List;
    
list2.setItemText(itemText2);
    
list2.setItemValue(itemValues2);
    
list2.setItemCount(itemText2.length);

    var 
obj = new AW.Grid.Extended;

    
obj.setCellData(myData);
    
obj.setCellText(function(c,r){if (c==1) return itemText1[r];if (c==2) return itemText2[r];return myData[r][c]});
    
    
obj.setHeaderText(myHeaders);
    
    
obj.setColumnCount(4);
    
obj.setRowCount(5);
    
    
obj.setFooterCount(2);
    
obj.setFooterVisible(true);
    
    
//I define a "track" of the cellTemplate 
    
obj.oldTemplate = [null,null,null,null];
    
//I define an array that holds the current cell being edited as "combo"
    
obj.currentCellEdited = [null,null];
    
    
obj.setCellEditable(true);
    
//I intend to edit cols 1,2 as combos - I disable cell Edit
    
obj.setCellEditable(false,1); 
    
obj.setCellEditable(false,2); 
    
    
/*
    Next I define the handlers that would allow me to add combo-edit functionality ...
    
    */
    
    
obj.onCellEditStarted cellEditStarted;
    
obj.onCellClicked cellClicked;
    
obj.onCellDoubleClicked cellDblClicked
    
obj.onKeyPress  gridKeyPress;
    
obj.onKeyF2 gridKeyF2;
    
obj.onKeyTab gridKeyTab;
    
obj.onKeyEnter gridKeyEnter;
    
/*
  when tapping Tab on cell it should "exit" edit mode for combo object
*/
function gridKeyTab(event)
{
 var 
this.currentCellEdited[0];
 var 
this.currentCellEdited[1];  
 var 
column this.getCurrentColumn();
 var 
row this.getCurrentRow();
 
// try "exiting" edit mode 
 
if (column == && row == r)
 {
   
this.getCellTemplate(cr).hidePopup();
   
this.setCellTemplate(this.oldTemplate[c],c,r);
   
this.getCellTemplate(c,r).refresh();
   
this.currentCellEdited = [nullnull];  
 }
}
/*
  when tapping Enter on cell it should "enter" or "exit" edit mode for combo object
*/
function gridKeyEnter(event)
{
 var 
this.currentCellEdited[0];
 var 
this.currentCellEdited[1];  
 var 
column this.getCurrentColumn();
 var 
row this.getCurrentRow();
 
 
// try "exiting" edit mode
 
if (column == && row == r)
 {
   
this.getCellTemplate(cr).hidePopup();
   
this.setCellTemplate(this.oldTemplate[c],c,r);
   
this.getCellTemplate(c,r).refresh();
   
this.currentCellEdited = [nullnull];  
 }  
 
// try "entering" edit mode
  
if (=== null && === null)
 {
    
this.onCellEditStarted(this.getCellText(columnrow), columnrow);     
 }
}

/*
  when tapping any "alphanumeric" key on cell it should "enter" edit mode for combo object
  (like editing normal cell)
*/
function gridKeyPress(event)
{
 var 
this.currentCellEdited[0];
 var 
this.currentCellEdited[1];  
 var 
column this.getCurrentColumn();
 var 
row this.getCurrentRow();
 
// try "entering" edit mode
 
if (=== null && === null)
 {
    
this.onCellEditStarted(this.getCellText(columnrow), columnrow);     
 }   
}
/*
  when tapping F2 key on cell it should "enter" edit mode for combo object
  (like editing normal cell)
*/
function gridKeyF2(event)
{
    
 var 
this.currentCellEdited[0];
 var 
this.currentCellEdited[1];  
 var 
column this.getCurrentColumn();
 var 
row this.getCurrentRow();
 
// try "entering" edit mode 
 
if (=== null && === null)
 {
    
this.onCellEditStarted(this.getCellText(columnrow), columnrow);     
 }
}

/* When Double-clicking on cell it should "enter" edit mode for combo object
*/
function cellDblClicked(eventcolumnrow)
{
  
// force "editing" for combo - does nothing for cell template since the called method
  // only handles the combo editable columns  
 
this.onCellEditStarted(this.getCellText(columnrow), columnrow);
}

/*
  When clicking on a cell that is not the current cell edited - exit from editing mode
  I do not require to validate the combo value since I have the values defined for the
  whole column ...
*/
function cellClicked(eventcolumnrow)
{
    var 
this.currentCellEdited[0];
    var 
this.currentCellEdited[1];
    
// check if we have a cell currently being edited
    
if (!== null && !== null)
    {
        if (!(
row == && column == c)) // if we click on a cell that is not the edited cell ...
        
{
           
this.getCellTemplate(cr).hidePopup();
           
this.setCellTemplate(this.oldTemplate[c],c,r);
           
this.getCellTemplate(c,r).refresh();
           
this.currentCellEdited = [nullnull];
        }
    }
    
    
//now we are displaying text and value for clicked cell in order to see the changes in grid
    
    
this.setFooterText(this.getCellText(column,row), column1);
    
this.getFooterTemplate(column1).refresh();
    
this.setFooterText(this.getCellValue(column,row), column0);
    
this.getFooterTemplate(column0).refresh();
}   
 
function 
cellEditStarted(textcolumnrow)
{
  
// if combo editing .... col = 1 or 2 
  
if (column == || column == 1)
  {                                                             
     
this.oldTemplate[column] = this.getCellTemplate(column,row)
     
this.setCellTemplate(new AW.Templates.Combocolumn,row);
     
this.getCellTemplate(column,row).refresh();
     
this.currentCellEdited = [column,row];
  }
}    
    
    
obj.setPopupTemplate(list11);
    
obj.setPopupTemplate(list22);
    
    
/* add lists onItemClicked handler AFTER SETTING the Popup Template (Alex dixit)
     modify the default handler set by sepPopupTemplate to suit my needs
     
    if you want to know the code existing for the handler 
    execute document.write(list1.onItemClicked); or alert(list1.onItemClicked);
    */
    
list1.onItemClicked list2.onItemClicked = function(event,i){
        
try{
            var 
s=this.getItemText(i);
            
//get the combo "selectedItem" value 
            
var v=this.getItemValue(i);  
            
this.$owner.setCellText(s,this.$0,this.$1);
            
// set the value
            
this.$owner.setCellValue(v,this.$0,this.$1);
            
AW.$popup.hidePopup();
            var 
e=this.$owner.getCellTemplate(this.$0,this.$1).getContent("box/text").element();
            if(
AW.ie){e.innerHTML=s}else{e.value=s}
            
// Fix Firefox "possible" bug - not focusing on cell to allow keys control
            
e.parentNode.parentNode.focus();
            
e=null;
        
        }
catch(e){}
    
    }    
    
    
document.write(obj); 

 
Bogdan P.
Monday, April 2, 2007
Nice post,

I was looking for a more generic solution but to get to the scenario this gave me an idea. However, I was more interested in overriding the Grid Controllers instead of overriding every event just like you did. I stepped through aw.js code and analyzed a lots of stuff. In the end I came up with the following generic code which exactly does the above functionality.

Except that my approach is kind of a patch (an override of the original grid cell controllers -- however, it preserves existing functionality as a fall back).

This is a generic code, so you can use it for any template you like, Combo, custom template etc.

The idea is to create a Doublet -- yes, it would be a pair of templates, viz., the view template and the edit template. When the cell is in edit mode the edit template is displayed and when the cell comes out of edit mode the view template is restored.

I have create this concept called Doublet, just because the grid exposes customization to the cell template, either to display all the time or restricts to go with in-line edit with just one template (the base class for all these is AW.Templates.ImageText).

OK, now to the solution. Create a grid just like you usually do. And instead of passing a static Combo template to the setCellTemplate method, pass it through Doublet Pairing

var doublet AW.Templates.Doublet.Pair(
                        
obj.getCellTemplate(1),
                        new 
AW.Templates.Combo
                  
);
    
obj.setCellTemplatedoublet1);
 
(continued)...
Neo (TrioteX)
Monday, May 21, 2007
(continued)

For the above code to work, you will need the following patch to placed:
(either at the end of aw.js or on a newly created .js file including with the HTML.

Here is the patch:


AW.Templates.Doublet={};
AW.Templates.Doublet.Pair=function(viewTemplateeditTemplate){
    
viewTemplate.editTemplate editTemplate;
    
editTemplate.viewTemplate viewTemplate;
    return 
viewTemplate;
};

AW.Grid.Controllers.Cell.DoubletsPatch=function(){
    var 
controller this;
    if (!
controller.editCurrentCell ||
        
controller.editCurrentCell!==controller.onKeyPress){return}
    
// backup original functionality
    
controller._orig_startEdit controller.editCurrentCell;
    
controller.doubletStartEdit = function( event ) {

         var 
c=this.getCurrentColumn();
        var 
r=this.getCurrentRow();
        if(!
this.getCellEditable(c,r)){return}
        var 
t=this.getCellTemplate(c,r);
        if (!
t){return}
        if ( 
t.editTemplate ) {

            var 
self t;
            var 
item=self.getAttribute("aw");
            var 
originalText=self.getControlProperty("text");
            var 
text=originalText;

            function 
raiseEvent(name){
                var 
text1=self.getControlProperty("text");
                var 
fullname=AW.camelCase("on",item,name);
                var 
result=self.raiseEvent(fullname,text,self.$0,self.$1,self.$2);
                var 
text2=self.getControlProperty("text");
                if(
text2!=text1){updateText(text2)}
                return 
result
            
}
            if(
raiseEvent("editStarting")){self=null;return}

            
eT t.editTemplate;
            
//eT._et_startEdit = eT.startEdit;
            //eT.startEdit = t.startEdit;
            
this.setCellTemplate(eT,c,r);
            
t=this.getCellTemplate(c,r);
            
t.refresh();

            
t.onCellEditStarting=function(){
                
//No-Op
            
};
            
t.onCellEditStarted=function(){
                
raiseEvent("editStarted");
            };
            
t.onCellEditEnded=function(){
                var 
vT this.viewTemplate;
                
//this.startEdit = this._et_startEdit;
                
this.$owner.setCellTemplate(vT,c,r);
                var 
t=this.$owner.getCellTemplate(c,r);
                
t.refresh();
                
raiseEvent("editEnded");
                
self vT null;
            };
        }
         if(
t.startEdit && !this.$edit){
             
this.$editCol=c;
            
this.$editRow=r;
            if(
event && event.type=="keypress"){
                if(
event.keyCode !=27){
                    
t.startEdit(String.fromCharCode(event.keyCode || event.charCode))
                }
            }else{
                
t.startEdit()
            }
            
AW.setReturnValue(event,false)
        }

    };
    
// Assign new functionality
    
controller.editCurrentCell controller.onKeyPress controller.doubletStartEdit;
};
AW.Grid.Controllers.Cell.DoubletsPatch();

 
(continued...)
Neo (TrioteX)
Monday, May 21, 2007

Here is a sample code:

This code has the following features:
- Allow inline editing of the grid.
- Allow a column to have a different template instead of the default one (Combo used for second column)
- Allow Combo to appear for the currently selected cell only.
- Commit and Discard changes to the combo or a cell using Enter and Esc keys
- Navigate through the cells/rows using Tab keys


<SCRIPT>


AW.Templates.Doublet={};
AW.Templates.Doublet.Pair=function(viewTemplateeditTemplate){
    
viewTemplate.editTemplate editTemplate;
    
editTemplate.viewTemplate viewTemplate;
    return 
viewTemplate;
};

AW.Grid.Controllers.Cell.DoubletsPatch=function(){
    var 
controller this;
    if (!
controller.editCurrentCell ||
        
controller.editCurrentCell!==controller.onKeyPress){return}
    
// backup original functionality
    
controller._orig_startEdit controller.editCurrentCell;
    
controller.doubletStartEdit = function( event ) {

         var 
c=this.getCurrentColumn();
        var 
r=this.getCurrentRow();
        if(!
this.getCellEditable(c,r)){return}
        var 
t=this.getCellTemplate(c,r);
        if (!
|| this.$edit){return}
        if ( 
t.editTemplate ) {

            var 
self t;
            var 
item=self.getAttribute("aw");
            var 
originalText=self.getControlProperty("text");
            var 
text=originalText;

            function 
raiseEvent(name){
                var 
text1=self.getControlProperty("text");
                var 
fullname=AW.camelCase("on",item,name);
                var 
result=self.raiseEvent(fullname,text,self.$0,self.$1,self.$2);
                var 
text2=self.getControlProperty("text");
                if(
text2!=text1){updateText(text2)}
                return 
result
            
}
            if(
raiseEvent("editStarting")){self=null;return}

            
eT t.editTemplate;
            
eT._et_startEdit eT.startEdit;
            
eT.startEdit t.startEdit;
            
this.setCellTemplate(eT,c,r);
            
t=this.getCellTemplate(c,r);
            
t.refresh();

            
t.onCellEditStarting=function(){
                
//No-Op
            
};
            
t.onCellEditStarted=function(){
                
raiseEvent("editStarted");
            };
            
t.onCellEditEnded=function(){
                var 
vT this.viewTemplate;
                
this.startEdit this._et_startEdit;
                
this.$owner.setCellTemplate(vT,c,r);
                var 
t=this.$owner.getCellTemplate(c,r);
                
t.refresh();
                
raiseEvent("editEnded");
                
self vT null;
            };
        }
         if(
t.startEdit && !this.$edit){
             
this.$editCol=c;
            
this.$editRow=r;
            if(
event && event.type=="keypress"){
                if(
event.keyCode !=27){
                    
t.startEdit(String.fromCharCode(event.keyCode || event.charCode))
                }
            }else{
                
t.startEdit()
            }
            
AW.setReturnValue(event,false)
        }

    };
    
// Assign new functionality
    
controller.onMouseDown controller.editCurrentCell controller.onKeyPress controller.doubletStartEdit;
};
AW.Grid.Controllers.Cell.DoubletsPatch();







        var 
myData = [
            [
"MSFT","Microsoft Corporation""314,571.156""32,187.000""55000"],
            [
"ORCL""Oracle Corporation""62,615.266""9,519.000""40650"],
            [
"SAP""SAP AG (ADR)""40,986.328""8,296.420""28961"],
            [
"CA""Computer Associates Inter""15,606.335""3,164.000""16000"],
            [
"ERTS""Electronic Arts Inc.""14,490.895""2,503.727""4000"],
            [
"SFTBF""Softbank Corp. (ADR)""14,485.840"".000""6865"],
            [
"VRTS""Veritas Software Corp.""14,444.272""1,578.658""5647"],
            [
"SYMC""Symantec Corporation""9,932.483""1,482.029""4300"],
            [
"INFY""Infosys Technologies Ltd.""9,763.851""830.748""15400"],
            [
"INTU""Intuit Inc.""9,702.477""1,650.743""6700"],
            [
"ADBE""Adobe Systems Incorporate""9,533.050""1,230.817""3341"],
            [
"PSFT""PeopleSoft, Inc.""8,246.467""1,941.167""8180"],
            [
"SEBL""Siebel Systems, Inc.""5,434.649""1,417.952""5909"],
            [
"BEAS""BEA Systems, Inc.""5,111.813""965.694""3063"],
            [
"SNPS""Synopsys, Inc.""4,482.535""1,169.786""4254"],
            [
"CHKP""Check Point Software Tech""4,396.853""424.769""1203"],
            [
"MERQ""Mercury Interactive Corp.""4,325.488""444.063""1822"],
            [
"DOX""Amdocs Limited""4,288.017""1,427.088""9400"],
            [
"CTXS""Citrix Systems, Inc.""3,946.485""554.222""1670"],
            [
"KNM""Konami Corporation (ADR)""3,710.784"".000""4313"]
        ];

        var 
myColumns = [
            
"Ticker""Company Name""Market Cap.""$ Sales""Employees"
        
];



    var 
obj = new AW.Grid.Extended;


    
//    provide cells and headers text
    
obj.setCellText(myData);
    
obj.setHeaderText(myColumns);
    
    
// save grid data for future use
    
obj.gridData myData;

    
//    set number of rows/columns
    
obj.setRowCount(myData.length);
    
obj.setColumnCount(myColumns.length);

    
obj.setSelectionMode("single-cell");

    
//    enable row selectors
    
obj.setSelectorVisible(true);
    
obj.setSelectorText(function(i){return this.getRowPosition(i)+1});

    
//    set headers width/height
    
obj.setSelectorWidth(30);
    
obj.setHeaderHeight(20);

    
//    allow editing
    
obj.setCellEditable(true);

    
//    disable virtual rendering
    
obj.setVirtualMode(false);



    
// change the first column to a doublet

    
var itemText1 = ["Francis Ford Coppola","Quentin Tarantino","Sam Mendes","Alfred Hitchcock"];
    var 
itemValues1 = [1,2,3,4];

    var 
list1 = new AW.UI.List;
    
list1.setItemText(itemText1);
    
list1.setItemValue(itemValues1);
    
list1.setItemCount(itemText1.length);

    
obj.setPopupTemplate(list11);




    var 
doublet AW.Templates.Doublet.Pair(
                        
obj.getCellTemplate(1),
                        new 
AW.Templates.Combo
                  
);
    
obj.setCellTemplatedoublet1);



    
// ... Other customizations follow



    /* SET CONTROL KEYS */
    
obj.setController("MyTabKeys", {
        
onKeyTab"mySelectNextCell",
        
onKeyEnter"mySelectNextCell",
        
onKeyShiftTab"mySelectPreviousCell"
    
});

    
/* onKeyTab */
    
obj.mySelectNextCell = function(event) {
        
//alert( event.keyCode );
        
var = new Number(this.getCurrentColumn()) + 1;
        var 
= new Number(this.getCurrentRow());
        if (
>= this.getColumnCount()) {
            
0;
            
r++;
        }
        
try {
            
// move focus out, triggers end of edit
            
this.getContent("focus").element().focus();
        }
        
catch(err) {
            
// ignore focus errors
        
}
        if (
this.getRowCount()) {
            
this.mySelectCell(cr);
        } else {
            
this.gridData.push([]);
            
this.addRow();
            
this.mySelectCell(0r);
        }
        
//
        
AW.setReturnValue(eventfalse);
        
event null;
    }

    
/* onKeyShiftTab */
    
obj.mySelectPreviousCell = function(event) {
        var 
= new Number(this.getCurrentColumn()) - 1;
        var 
= new Number(this.getCurrentRow());
        if (
0) {
            
this.getColumnCount() - 1;
            
r--;
        }
        
try {
            
// move focus out, triggers end of edit
            
this.getContent("focus").element().focus();
        }
        
catch(err) {
            
// ignore focus errors
        
}
        if (
> -1) {
            
this.mySelectCell(cr);
            
AW.setReturnValue(eventfalse);
        }
        
event null;
    }

    
/* Custom Select Cell function */
    
obj.mySelectCell = function(cr){

        if (
!= this.getCurrentColumn()){
            
this.setCurrentColumn(c);
        }

        if (
!= this.getCurrentRow()){
            
this.setCurrentRow(r);
        }

        var 
cc this.getSelectedColumns();
        if (
cc.length != || cc[0] != c){
            
this.setSelectedColumns([c]);
        }

        var 
rr this.getSelectedRows();
        if (
rr.length != || rr[0] != r){
            
this.setSelectedRows([r]);
        }
    }

/*
    // override original selection controllers //
    obj.setController("selection", {
        selectPreviousCell: mySelectNextCell,
        selectNextCell: mySelectPreviousCell
    });
*/

    /**
     * auto edit on cell selection (if cell supports edit)
     */
    
obj.onCellSelectedChanged = function(selectedcolrow) {
       if (
selected) {
            
this.setTimeout(function(){
                
this.raiseEvent("editCurrentCell", {}, colrow);
            });
        }
    }

    
obj.onCellClicked = function(eventcolrow) {
       if (
obj.getCellSelected(colrow)) {
            
this.setTimeout(function(){
                
this