3.0.5

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 c = this.currentCellEdited[0];
 var r = this.currentCellEdited[1];  
 var column = this.getCurrentColumn();
 var row = this.getCurrentRow();
 // try "exiting" edit mode 
 if (column == c && row == r)
 {
   this.getCellTemplate(c, r).hidePopup();
   this.setCellTemplate(this.oldTemplate[c],c,r);
   this.getCellTemplate(c,r).refresh();
   this.currentCellEdited = [null, null];  
 }
}
/*
  when tapping Enter on cell it should "enter" or "exit" edit mode for combo object
*/
function gridKeyEnter(event)
{
 var c = this.currentCellEdited[0];
 var r = this.currentCellEdited[1];  
 var column = this.getCurrentColumn();
 var row = this.getCurrentRow();
 
 // try "exiting" edit mode
 if (column == c && row == r)
 {
   this.getCellTemplate(c, r).hidePopup();
   this.setCellTemplate(this.oldTemplate[c],c,r);
   this.getCellTemplate(c,r).refresh();
   this.currentCellEdited = [null, null];  
 }  
 // try "entering" edit mode
  if (c === null && r === null)
 {
    this.onCellEditStarted(this.getCellText(column, row), column, row);     
 }
}

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

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

/*
  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(event, column, row)
{
    var c = this.currentCellEdited[0];
    var r = this.currentCellEdited[1];
    // check if we have a cell currently being edited
    if (c !== null && r !== null)
    {
        if (!(row == r && column == c)) // if we click on a cell that is not the edited cell ...
        {
           this.getCellTemplate(c, r).hidePopup();
           this.setCellTemplate(this.oldTemplate[c],c,r);
           this.getCellTemplate(c,r).refresh();
           this.currentCellEdited = [null, null];
        }
    }
    
    //now we are displaying text and value for clicked cell in order to see the changes in grid
    
    this.setFooterText(this.getCellText(column,row), column, 1);
    this.getFooterTemplate(column, 1).refresh();
    this.setFooterText(this.getCellValue(column,row), column, 0);
    this.getFooterTemplate(column, 0).refresh();
}   
 
function cellEditStarted(text, column, row)
{
  // if combo editing .... col = 1 or 2 
  if (column == 2 || column == 1)
  {                                                             
     this.oldTemplate[column] = this.getCellTemplate(column,row)
     this.setCellTemplate(new AW.Templates.Combo, column,row);
     this.getCellTemplate(column,row).refresh();
     this.currentCellEdited = [column,row];
  }
}    
    
    obj.setPopupTemplate(list1, 1);
    obj.setPopupTemplate(list2, 2);
    
    /* 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.
April 2,
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.setCellTemplate( doublet, 1);


(continued)...
Neo (TrioteX)
May 21,
(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(viewTemplate, editTemplate){
    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 = t = 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)
May 21,

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(viewTemplate, editTemplate){
    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 || 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 = t = 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(list1, 1);




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



    // ... Other customizations follow



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

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

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

    /* Custom Select Cell function */
    obj.mySelectCell = function(c, r){

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

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

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

        var rr = this.getSelectedRows();
        if (rr.length != 1 || 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(selected, col, row) {
       if (selected) {
            this.setTimeout(function(){
                this.raiseEvent("editCurrentCell", {}, col, row);
            });
        }
    }

    obj.onCellClicked = function(event, col, row) {
       if (obj.getCellSelected(col, row)) {
            this.setTimeout(function(){
                this.raiseEvent("editCurrentCell", {}, col, row);
            });
        }
    }

    document.write(obj);

    </SCRIPT>


Hope this helps!
Neo (TrioteX)
May 21,

This topic is archived.

See also:


Back to support forum