PDA

View Full Version : custom function to enable/disable grid editing



mjlecomte
7 Aug 2008, 6:16 PM
A fairly frequent question in the forums is how to disable editing in editor grids. Several options have been discussed, some of them are shown in #28 of the Grid FAQs.

I was thinking it might be more convenient to allow a custom function to be specified - a similar method is used with form validator (http://extjs.com/deploy/dev/docs/?class=Ext.form.TextField&method=validator) whereby the user can specify a custom function will be injected into the validation process.

Just thought I'd share my crude crack at it.



/**
* Ext grid ColumnModel Override
*
* @class Ext.grid.ColumnModel
* @extends Ext.util.Observable
*
*/

Ext.override(Ext.grid.ColumnModel, {

/**
* Returns true if the cell is editable. A custom function may be specified to enable or disable editing.
* @param {Number} colIndex The column index
* @param {Number} rowIndex The row index
* @param {Object} grid The grid
* @return {Boolean}
*/
isCellEditable : function(colIndex, rowIndex, grid){
// isCellEditable : function(colIndex, rowIndex){
/*change begin*/
if (typeof this.config[colIndex].enableEditor == "function") {

var field = this.getDataIndex(colIndex);

var selModel = grid.getSelectionModel();
var record = selModel.selection.record;
var cellValue = record.data[this.getDataIndex(colIndex)];

var result = this.config[colIndex].enableEditor(cellValue, colIndex, rowIndex, selModel, record, grid);
if (true !== result) {
//this.fireEvent('editDenied', this);
return false;
}
return true;
}
/*change end*/

return (this.config[colIndex].editable || (typeof this.config[colIndex].editable == "undefined" && this.config[colIndex].editor)) ? true : false;
},
/*change begin*/

/*
* @cfg {Function} enableEditor A custom function that will be called before the editor is activated (defaults to null).
* If available, this function will be called and expected to return boolean true if the editor should be activated or false if the editor should be disabled.
* <p>The funtion will be called with the following parameters:
* <div class="mdetail-params"><ul>
* <li><b>cellValue</b> : String<p class="sub-desc">current field value</p></li>
* <li><b>colIndex</b> : Number<p class="sub-desc">The column index</p></li>
* <li><b>rowIndex</b> : Number<p class="sub-desc">The row index</p></li>
* <li><b>selModel</b> : Object<p class="sub-desc">The grid's selection model</p></li>
* <li><b>record</b> : Object<p class="sub-desc">Ext.data.Record[]</p></li>
* <li><b>grid</b> : Object<p class="sub-desc">The grid</p></li>
* </ul></div></p>
*/
enableEditor : null
/*change end*/

});



Ext.override(Ext.grid.EditorGridPanel, {
startEditing : function(row, col){

this.stopEditing();
/*change begin*/
// if(this.colModel.isCellEditable(col, row)){
if(this.colModel.isCellEditable(col, row, this)){
/*change end*/
this.view.ensureVisible(row, col, true);
var r = this.store.getAt(row);
var field = this.colModel.getDataIndex(col);
var e = {
grid: this,
record: r,
field: field,
value: r.data[field],
row: row,
column: col,
cancel:false
};
if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
this.editing = true;
var ed = this.colModel.getCellEditor(col, row);
if(!ed.rendered){
ed.render(this.view.getEditorParent(ed));
}
(function(){ // complex but required for focus issues in safari, ie and opera
ed.row = row;
ed.col = col;
ed.record = r;
ed.on("complete", this.onEditComplete, this, {single: true});
ed.on("specialkey", this.selModel.onEditorKey, this.selModel);
this.activeEditor = ed;
var v = this.preEditValue(r, field);
ed.startEdit(this.view.getCell(row, col).firstChild, v);
}).defer(50, this);
}
}
}
});



Ext.override(Ext.grid.CellSelectionModel, {
/*change begin*/
//normalize selection models getSelected() method
getSelected: Ext.grid.CellSelectionModel.prototype.getSelectedCell,
/*change end*/

acceptsNav: function(row, col, cm){
/*change begin*/
//return !cm.isHidden(col) && cm.isCellEditable(col, row);
return !cm.isHidden(col) && cm.isCellEditable(col, row, this.grid);
/*change end*/
}
});

Note I think it would be nice if the selection model getSelected() method were normalized across the various selection models. That way if the selection model changes it doesn't cause errors, just returns different results.

Sample usage (replace column config shown in the edit-grid.js demo):


var cm = new Ext.grid.ColumnModel([{
...
},{
header: "Price",
dataIndex: 'price',
width: 70,
align: 'right',
renderer: 'usMoney',
editor: new fm.NumberField({
allowBlank: false,
allowNegative: false,
maxValue: 100000
}),
enableEditor: function(cellValue, colIndex, rowIndex, selModel, record, grid){
//'this' is the individual column's config object
return (cellValue<5) ? true : false;
}
},{
...
]);


Maybe could also have some defaultEnableEditor (similar to defaultSortable (http://extjs.com/deploy/dev/docs/?class=Ext.grid.ColumnModel&method=defaultSortable)) config so could specify on the grid in general.

mystix
7 Aug 2008, 7:33 PM
interesting... perhaps the ColumnModel override could be simplified to this instead?

[edit 1]
updated override so it's now standalone (and can be used as a drop-in replacement for stock Ext 2.2).

[edit 2]
added defaultEditable config



Ext.override(Ext.grid.ColumnModel, {
/**
* Default editable of columns which do not have the "editable" config specified (defaults to false)
* @cfg {Boolean/Function} defaultEditable true if the column is editable, or a function which returns
* true/false when passed the arguments: grid, row index, column index (defaults to false).
* If set to true, a custom {@link #getCellEditor} method
* (which returns a default Editor instance for columns without Editors) must be specified.
*/
defaultEditable: false,

/**
* Returns true if the cell is editable.
* @param {Number} colIndex The column index
* @param {Number} rowIndex The row index
* @param {Ext.grid.GridPanel} The {@link Ext.grid.GridPanel} which owns this ColumnModel
* @return {Boolean}
*/
isCellEditable: function(colIndex, rowIndex) {
var col = this.config[colIndex],
ok = col.editable != undefined? col.editable : col.editor != undefined? true : this.defaultEditable;

return ok === true || (typeof ok == 'function' && ok(this.grid, rowIndex, colIndex));
},

/**
* Sets if a column is editable.
* @param {Number} col The column index
* @param {Boolean/Function} editable true if the column is editable, or a function which returns
* true/false when passed the arguments: grid, row index, column index.
*/
setEditable: function(col, editable) {
this.config[col].editable = editable;
}
});

Ext.override(Ext.grid.GridPanel, {
initComponent: Ext.grid.GridPanel.prototype.initComponent.createSequence(function() {
if (this.colModel) {
// give the ColumnModel a reference to this grid
// (the grid's SelectionModel has a reference to the grid, so why not the ColumnModel too?)
this.colModel.grid = this;
}
})
});

carol.ext
8 Aug 2008, 9:59 AM
Here's my first report testing :

I tried Mystix's simplified version. It works for the conditions I've tried.

Using editable function, but did not try passing the function to setEditable().
Columns with/without editors and no editable defined.
Columns where setEditable() is called toggling between true/false based on data loaded into the grid.


[Edit: Add strikethrough on my misunderstanding parts]
Mystix version of isCellEditable() is not passed the grid, so cannot pass it to the editable function. MJ's override also changed calls to isCellEditable to get the grid passed; not sure all those work as I haven't tried it and don't see where ColumnModel has access to its grid.

Grid not passed here:

/**
* Returns true if the cell is editable.
* @param {Number} colIndex The column index
* @param {Number} rowIndex The row index
* @param {Ext.grid.GridPanel} The {@link Ext.grid.GridPanel} which owns this ColumnModel
* @return {Boolean}
*/
isCellEditable : function(colIndex, rowIndex, grid) {
var col = this.config[colIndex],
ok = col.editable;

return ok === true || (!!col.editor && (typeof ok == "undefined" || (typeof ok == "function" && ok(colIndex, rowIndex, grid))));
},

mystix
8 Aug 2008, 10:31 AM
my original intention was for my ColumnModel override to be used in place of @mj's colmodel override, and together with the rest of his overrides.

i've since updated my posted override so it functions standalone -- the gridpanel now gives the colmodel a direct reference to itself, so the method signatures for isCellEditable() and setEditable() remain unchanged from what they are in the stock Ext 2.2 distro.

example usage:


columns: [
// ... other column configs

{header: 'Price', dataIndex: 'price', editable: function(grid, row, col) {
// make some decision and return true / false.

// all necessary info about the grid / current selection(s) may be
// obtained from the passed arguments grid, row, and col
}, editor: new Ext.form.NumberField({ /* ... */ })}

// ... other column configs
]


[edit]
forgot to answer @carol's question on the grid reference not being passed --
@mj's EditorGridPanel.startEditing() override includes the following line:


//...

if(this.colModel.isCellEditable(col, row, this)){

//...

this is the reference to the EditorGridPanel which gets passed to the isCellEditable() method you quoted. HTH.

mjlecomte
8 Aug 2008, 11:02 AM
Looking good. How about the defaultEditable config I mentioned? I was thinking the use case here would be that you would need to specify editable on every column config, instead the defaultEditable could handle it so editing could be controlled per record (so basically per grid) through a single function.

Current code might work by simply specifying a reference to a function rather than inline, but then you'd still have to specify it for each column's config.

I agree about the grid reference to the column model, I noticed the selection model has a reference to it.

And if we could figure out how to get this to work for check boxes also... :) I haven't kept up on the latest checkbox stuff. (:|

carol.ext
8 Aug 2008, 12:47 PM
@mystix - Nice! I like. I plugged the updated override code in (and updated my params) and it works well, same scenarios I mentioned previously.

@MJ - Thanks for starting this thread. I was thinking I didn't currently have a use case needing defaultEditable config, but now that I think about it I have a requirement where the user selects a different data set and the the whole thing is supposed to be non-editable. I can grab all the columns and call setEditable(), but then I need to remember how to set them back to editable including passing the function in. I will probably start working on that on Monday and that will test this out some more (looking pretty solid :) ).

mystix
12 Aug 2008, 9:16 PM
How about the defaultEditable config I mentioned? I was thinking the use case here would be that you would need to specify editable on every column config, instead the defaultEditable could handle it so editing could be controlled per record (so basically per grid) through a single function.

Current code might work by simply specifying a reference to a function rather than inline, but then you'd still have to specify it for each column's config.

added the defaultEditable config. turns out to be easier than i thought.
let me know how it works out ;)



And if we could figure out how to get this to work for check boxes also... :) I haven't kept up on the latest checkbox stuff. (:|

it works for Checkbox editors. if it's a CheckboxColumn you're talking about, then you'll need to add custom code to inspect/stop the grid's cellclick event, since there's no editor for a checkboxcolumn in the first place. HTH.

mystix
25 Sep 2008, 12:52 AM
@mystix - Nice! I like. I plugged the updated override code in (and updated my params) and it works well, same scenarios I mentioned previously.

@MJ - Thanks for starting this thread. I was thinking I didn't currently have a use case needing defaultEditable config, but now that I think about it I have a requirement where the user selects a different data set and the the whole thing is supposed to be non-editable. I can grab all the columns and call setEditable(), but then I need to remember how to set them back to editable including passing the function in. I will probably start working on that on Monday and that will test this out some more (looking pretty solid :) ).

[a little bump] to find out how this override turned out for you (plus there was a tiny bugfix for the line which was previously col.editable != 'undefined').

Condor
25 Sep 2008, 12:55 AM
Why name the property 'enableEditor'? Why not reuse 'editable' for this?

editable:false -> not editable
editable:true -> editable
editable:function() -> depends on result

ps. And while you are at it, why not also make getCellEditor also handle an editor function (I know this is more work, because editors need to be rendered).

mystix
25 Sep 2008, 1:00 AM
Why name the property 'enableEditor'? Why not reuse 'editable' for this?

editable:false -> not editable
editable:true -> editable
editable:function() -> depends on result

ps. And while you are at it, why not also make getCellEditor also handle an editor function (I know this is more work, because editors need to be rendered).

i think you're looking at the wrong post... ;)
check out post #2.

Condor
25 Sep 2008, 1:22 AM
Sorry, I missed that.

ps. Your code can return true if no editor was defined.

I think the return from isCellEditable should be:

return !!col.editor && (ok !== false) && (typeof ok != 'function' || ok(this.grid, rowIndex, colIndex));

mystix
25 Sep 2008, 1:56 AM
I think the return from isCellEditable should be:

return !!col.editor && (ok !== false) && (typeof ok != 'function' || ok(this.grid, rowIndex, colIndex));

yeah i just encountered that after i fixed that line with the quoted undefined. :((
your suggested patch will prevent all cells from being edited when defaultEditable: false... :-?

i'll tinker with this some more...

mystix
25 Sep 2008, 2:07 AM
fixed it by setting defaultEditable: false (also updated the description) and changing isCellEditable to


isCellEditable: function(colIndex, rowIndex) {
var col = this.config[colIndex],
ok = col.editable != undefined? col.editable : col.editor != undefined? true : this.defaultEditable;

return ok === true || (typeof ok == 'function' && ok(this.grid, rowIndex, colIndex));
},

mjlecomte
25 Sep 2008, 4:58 AM
[a little bump] to find out how this override turned out for you (plus there was a tiny bugfix for the line which was previously col.editable != 'undefined').

Hopefully carol will come back and comment on the improvements here. I actually had not even used this (even though I started the thread (:| ). As mentioned in first post, I started it after seeing all of the people in the forums that were trying to approach this situation (there's several approaches shown in Grid FAQ). So I thought there could be some more accessible way to get this implemented.

Thanks for the improvements... ;)

mystix
25 Sep 2008, 9:46 AM
I actually had not even used this (even though I started the thread (:| ).

not a problem, cos i'm actually using it ;)

carol.ext
25 Sep 2008, 6:16 PM
I've been using the version mystix posted before adding [I think] defaultEditable. It has been working great for me.

I'll try out the updated version and let you know.


Hopefully carol will come back and comment on the improvements here...

mjlecomte
14 Nov 2008, 9:39 AM
Just wondering if the editable property should be moved to be a property of the column's editor, not the column config:
http://extjs.com/forum/showthread.php?p=251038#post251038

mystix
14 Nov 2008, 10:24 AM
Just wondering if the editable property should be moved to be a property of the column's editor, not the column config:
http://extjs.com/forum/showthread.php?p=251038#post251038

IMO the editable property should stay in the column config -- you need to prevent the editor from appearing if the column is not editable.

mjack003
12 Dec 2008, 3:10 PM
IMO, for what its worth, I agree with mystix. This is a pre-condition to the editor being displayed.

1) it would be nice to have the capability to set editor logic at the column or row
levels to cover most dynamics of complex apps...this override is a step closer. (would be nice to see a rowModel for that matter for those horizontal oriented tables such as gantt charts...but that's not for this thread)
2) Have a class assigned to cells that are not editable for easy dynamic styling...which
is problematic as with this override, the function would have to be evaled before
being able to make the determination.

mystix
12 Dec 2008, 9:40 PM
2) Have a class assigned to cells that are not editable for easy dynamic styling...which
is problematic as with this override, the function would have to be evaled before
being able to make the determination.

the cell itself (every single cell for the matter) would have to contain its own css style information initially, i.e. at render time. the only additional capability this override could provide is to add / remove a css style to the cell on-the-fly.

mjack003
13 Dec 2008, 8:54 PM
Yea that's currently how I'm handling styling/editing for larger grids...and I use this override to check for a custom attribute on the selected cell for editing. Couldn't find an efficient solution that was purely javascript classes and scalable so I resorted to splitting the grid logic between perl and javascript while keeping as much possible in the javascript to keep up the interface speed. This is a nice override for dynamic grids. Thanks for the post.

mystix
14 Dec 2008, 6:29 AM
Yea that's currently how I'm handling styling/editing for larger grids...and I use this override to check for a custom attribute on the selected cell for editing. Couldn't find an efficient solution that was purely javascript classes and scalable so I resorted to splitting the grid logic between perl and javascript while keeping as much possible in the javascript to keep up the interface speed. This is a nice override for dynamic grids. Thanks for the post.

you might also want to check out the column renderer's metaData parameter:
http://extjs.com/docs/?class=Ext.grid.ColumnModel&member=renderer

mjack003
15 Dec 2008, 6:34 AM
I guess I should do some more reading in the docs...next I'll be finding out this library can make me breakfast and change the oil in my truck. Thanks for the info marc, all I was using the renderer for was simple value masking. Back to reading...

mystix
15 Dec 2008, 7:02 AM
I guess I should do some more reading in the docs...next I'll be finding out this library can make me breakfast and change the oil in my truck. Thanks for the info marc, all I was using the renderer for was simple value masking. Back to reading...

n.p. ;)