PDA

View Full Version : [OPEN-1419] RowEditor + CheckboxSelectionModel causes error.



FCTim
24 Nov 2010, 9:35 AM
Ext version tested:

Ext 3.3.0


Adapter used:

ext-base.js


css used:

ext-all.css
ux-all.css
(In that order)


Browser versions tested against:


IE8


Operating System:

Windows XP Professional SP3


Description:

When activating the row editor, a JS error is thrown because it is trying to call column.getEditor() on the checkbox column but the method getEditor doesn't exist.
P.S. I think this is the same issue that is happening in Ext GWT reported here (http://www.sencha.com/forum/showthread.php?115154-RowEditor-and-CheckboxSelectionModel-together-Problem&p=536808&langid=1) (http://www.sencha.com/forum/showthread.php?115154-RowEditor-and-CheckboxSelectionModel-together-Problem&p=536808&langid=1)


Test Case:

Modified the following CheckboxSelectionModel example to include the Row Editor plugin.

Here is the original Example URL : http://dev.sencha.com/deploy/dev/examples/grid/grid-plugins.html


Ext.onReady(function(){

// Array data for the grids
Ext.grid.dummyData = [
['3m Co',71.72,0.02,0.03,'9/1 12:00am', 'Manufacturing'],
['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am', 'Manufacturing'],
['Altria Group Inc',83.81,0.28,0.34,'9/1 12:00am', 'Manufacturing'],
['American Express Company',52.55,0.01,0.02,'9/1 12:00am', 'Finance']
];

var sm = new Ext.grid.CheckboxSelectionModel();
var grid2 = new Ext.grid.GridPanel({
store: new Ext.data.Store({
reader: new Ext.data.ArrayReader({}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'},
{name: 'industry'},
{name: 'desc'}
]),
data: Ext.grid.dummyData
}),
cm: new Ext.grid.ColumnModel({
defaults: {
width: 120,
sortable: true
},
columns: [
sm,
{id:'company',header: "Company", width: 200, dataIndex: 'company'},
{header: "Price", renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
{header: "Change", dataIndex: 'change'},
{header: "% Change", dataIndex: 'pctChange'},
{header: "Last Updated", width: 135, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
]
}),
sm: sm,
columnLines: true,
width:600,
height:300,
frame:true,
title:'Framed with Checkbox Selection and Horizontal Scrolling',
iconCls:'icon-grid',
plugins: [
new Ext.ux.grid.RowEditor({
saveText : 'Update',
})
],
renderTo: document.body
});

});

Steps to reproduce the problem:

Double click on a row to open the Row Editor.


The result that was expected:

No Javascript errors.


The result that occurs instead:

Javascript error


Screenshot or Video:

none
(http://www.radikal.ru/)


Debugging already done:

Looks like RowEditor.initFields and RowEditor.doFocus() have references to c.getEditor() which doesn't exist for whatever checkbox object the CheckBoxSelectionModel creates.


Possible fix:

Any reference to c.getEditor() should either be validated with Ext.isFunction(c.getEditor) first or checked for existence some other way.

renatordl
28 Jan 2011, 9:11 PM
Solution Ext.ux.grid.RowEdit + Ext.grid.CheckboxSelectionModel + Ext.ux.grid.RowActions

Aonde tem o laço for é so colocar a verificação da função:
if(Ext.isFunction(c.getEditor){

Aonde tem o laço for junto com o fields[i] adiciona:
if(Ext.isFunction(c.getEditor) && fields[i]){

Assim o rowEditor verifica se é realmente uma coluna.

English

Where has the loop is placed so the verification of the function:
if(Ext.isFunction(c.getEditor){

Where has the loop along with the fields [i] adds:
if(Ext.isFunction(c.getEditor) && fields[i]){

So rowEditor checks whether it is really a column.


var c = cm.getColumnAt(i);
if(Ext.isFunction(c.getEditor) && fields[i]){
val = this.preEditValue(record, cm.getDataIndex(i));
f = fields[i];
f.setValue(val);
this.values[f.id] = Ext.isEmpty(val) ? '' : val;
}
if(Ext.isFunction(c.getEditor)){
if(!cm.isHidden(i)){
var dindex = cm.getDataIndex(i);
if(!Ext.isEmpty(dindex)){
var oldValue = r.data[dindex],
value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
if(String(oldValue) !== String(value)){
changes[dindex] = value;
hasChange = true;
}
}
}
}

RowEditor Modified



/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
Ext.ns('Ext.ux.grid');

/**
* @class Ext.ux.grid.RowEditor
* @extends Ext.Panel
* Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
* A validation mode may be enabled which uses AnchorTips to notify the user of all
* validation errors at once.
*
* @ptype roweditor
*/
Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
floating: true,
shadow: false,
layout: 'hbox',
cls: 'x-small-editor',
buttonAlign: 'center',
baseCls: 'x-row-editor',
elements: 'header,footer,body',
frameWidth: 5,
buttonPad: 3,
clicksToEdit: 'auto',
monitorValid: true,
focusDelay: 250,
errorSummary: true,

saveText: 'Save',
cancelText: 'Cancel',
commitChangesText: 'You need to commit or cancel your changes',
errorText: 'Errors',

defaults: {
normalWidth: true
},

initComponent: function(){
Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
this.addEvents(
/**
* @event beforeedit
* Fired before the row editor is activated.
* If the listener returns <tt>false</tt> the editor will not be activated.
* @param {Ext.ux.grid.RowEditor} roweditor This object
* @param {Number} rowIndex The rowIndex of the row just edited
*/
'beforeedit',
/**
* @event canceledit
* Fired when the editor is cancelled.
* @param {Ext.ux.grid.RowEditor} roweditor This object
* @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid.
*/
'canceledit',
/**
* @event validateedit
* Fired after a row is edited and passes validation.
* If the listener returns <tt>false</tt> changes to the record will not be set.
* @param {Ext.ux.grid.RowEditor} roweditor This object
* @param {Object} changes Object with changes made to the record.
* @param {Ext.data.Record} r The Record that was edited.
* @param {Number} rowIndex The rowIndex of the row just edited
*/
'validateedit',
/**
* @event afteredit
* Fired after a row is edited and passes validation. This event is fired
* after the store's update event is fired with this edit.
* @param {Ext.ux.grid.RowEditor} roweditor This object
* @param {Object} changes Object with changes made to the record.
* @param {Ext.data.Record} r The Record that was edited.
* @param {Number} rowIndex The rowIndex of the row just edited
*/
'afteredit'
);
},

init: function(grid){
this.grid = grid;
this.ownerCt = grid;
if(this.clicksToEdit === 2){
grid.on('rowdblclick', this.onRowDblClick, this);
}else{
grid.on('rowclick', this.onRowClick, this);
if(Ext.isIE){
grid.on('rowdblclick', this.onRowDblClick, this);
}
}

// stopEditing without saving when a record is removed from Store.
grid.getStore().on('remove', function() {
this.stopEditing(false);
},this);

grid.on({
scope: this,
keydown: this.onGridKey,
columnresize: this.verifyLayout,
columnmove: this.refreshFields,
reconfigure: this.refreshFields,
beforedestroy : this.beforedestroy,
destroy : this.destroy,
bodyscroll: {
buffer: 250,
fn: this.positionButtons
}
});
grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
},

beforedestroy: function() {
this.stopMonitoring();
this.grid.getStore().un('remove', this.onStoreRemove, this);
this.stopEditing(false);
Ext.destroy(this.btns, this.tooltip);
},

refreshFields: function(){
this.initFields();
this.verifyLayout();
},

isDirty: function(){
var dirty;
this.items.each(function(f){
if(String(this.values[f.id]) !== String(f.getValue())){
dirty = true;
return false;
}
}, this);
return dirty;
},

startEditing: function(rowIndex, doFocus){
if(this.editing && this.isDirty()){
this.showTooltip(this.commitChangesText);
return;
}
if(Ext.isObject(rowIndex)){
rowIndex = this.grid.getStore().indexOf(rowIndex);
}
if(this.fireEvent('beforeedit', this, rowIndex) !== false){
this.editing = true;
var g = this.grid, view = g.getView(),
row = view.getRow(rowIndex),
record = g.store.getAt(rowIndex);

this.record = record;
this.rowIndex = rowIndex;
this.values = {};
if(!this.rendered){
this.render(view.getEditorParent());
}
var w = Ext.fly(row).getWidth();
this.setSize(w);
if(!this.initialized){
this.initFields();
}
var cm = g.getColumnModel(), fields = this.items.items, f, val;
for(var i = 0, len = cm.getColumnCount(); i < len; i++){
var c = cm.getColumnAt(i);
if(Ext.isFunction(c.getEditor) && fields[i]){
val = this.preEditValue(record, cm.getDataIndex(i));
f = fields[i];
f.setValue(val);
this.values[f.id] = Ext.isEmpty(val) ? '' : val;
}
}
this.verifyLayout(true);
if(!this.isVisible()){
this.setPagePosition(Ext.fly(row).getXY());
} else{
this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
}
if(!this.isVisible()){
this.show().doLayout();
}
if(doFocus !== false){
this.doFocus.defer(this.focusDelay, this);
}
}
},

stopEditing : function(saveChanges){
this.editing = false;
if(!this.isVisible()){
return;
}
if(saveChanges === false || !this.isValid()){
this.hide();
this.fireEvent('canceledit', this, saveChanges === false);
return;
}
var changes = {},
r = this.record,
hasChange = false,
cm = this.grid.colModel,
fields = this.items.items;
for(var i = 0, len = cm.getColumnCount(); i < len; i++){
var c = cm.getColumnAt(i);
if(Ext.isFunction(c.getEditor)){
if(!cm.isHidden(i)){
var dindex = cm.getDataIndex(i);
if(!Ext.isEmpty(dindex)){
var oldValue = r.data[dindex],
value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
if(String(oldValue) !== String(value)){
changes[dindex] = value;
hasChange = true;
}
}
}
}
}
if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
r.beginEdit();
Ext.iterate(changes, function(name, value){
r.set(name, value);
});
r.endEdit();
this.fireEvent('afteredit', this, changes, r, this.rowIndex);
}
this.hide();
},

verifyLayout: function(force){
if(this.el && (this.isVisible() || force === true)){
var row = this.grid.getView().getRow(this.rowIndex);
this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + 9 : undefined);
var cm = this.grid.colModel, fields = this.items.items;
for(var i = 0, len = cm.getColumnCount(); i < len; i++){
var c = cm.getColumnAt(i);
if(Ext.isFunction(c.getEditor) && fields[i]){
if(!cm.isHidden(i)){
var adjust = 0;
if(i === (len - 1)){
adjust += 3; // outer padding
} else{
adjust += 1;
}
fields[i].show();
fields[i].setWidth(cm.getColumnWidth(i) - adjust);
} else{
fields[i].hide();
}
}}
this.doLayout();
this.positionButtons();
}
},

slideHide : function(){
this.hide();
},

initFields: function(){
var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
this.removeAll(false);
for(var i = 0, len = cm.getColumnCount(); i < len; i++){
var c = cm.getColumnAt(i);
if(Ext.isFunction(c.getEditor)){


c = cm.getColumnAt(i),
ed = c.getEditor();
if(!ed){
ed = c.displayEditor || new Ext.form.DisplayField();
}
if(i == 0){
ed.margins = pm('0 1 2 1');
} else if(i == len - 1){
ed.margins = pm('0 0 2 1');
} else{
if (Ext.isIE) {
ed.margins = pm('0 0 2 0');
}
else {
ed.margins = pm('0 1 2 0');
}
}
ed.setWidth(cm.getColumnWidth(i));
ed.column = c;
if(ed.ownerCt !== this){
ed.on('focus', this.ensureVisible, this);
ed.on('specialkey', this.onKey, this);
}
this.insert(i, ed);
}
}
if(Ext.isFunction(c.getEditor)){
this.initialized = true;
}
},

onKey: function(f, e){
if(e.getKey() === e.ENTER){
this.stopEditing(true);
e.stopPropagation();
}
},

onGridKey: function(e){
if(e.getKey() === e.ENTER && !this.isVisible()){
var r = this.grid.getSelectionModel().getSelected();
if(r){
var index = this.grid.store.indexOf(r);
this.startEditing(index);
e.stopPropagation();
}
}
},

ensureVisible: function(editor){
if(this.isVisible()){
this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
}
},

onRowClick: function(g, rowIndex, e){
if(this.clicksToEdit == 'auto'){
var li = this.lastClickIndex;
this.lastClickIndex = rowIndex;
if(li != rowIndex && !this.isVisible()){
return;
}
}
this.startEditing(rowIndex, false);
this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
},

onRowDblClick: function(g, rowIndex, e){
this.startEditing(rowIndex, false);
this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
},

onRender: function(){
Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
this.btns = new Ext.Panel({
baseCls: 'x-plain',
cls: 'x-btns',
elements:'body',
layout: 'table',
width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
items: [{
ref: 'saveBtn',
itemId: 'saveBtn',
xtype: 'button',
text: this.saveText,
width: this.minButtonWidth,
handler: this.stopEditing.createDelegate(this, [true])
}, {
xtype: 'button',
text: this.cancelText,
width: this.minButtonWidth,
handler: this.stopEditing.createDelegate(this, [false])
}]
});
this.btns.render(this.bwrap);
},

afterRender: function(){
Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
this.positionButtons();
if(this.monitorValid){
this.startMonitoring();
}
},

onShow: function(){
if(this.monitorValid){
this.startMonitoring();
}
Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
},

onHide: function(){
Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
this.stopMonitoring();
this.grid.getView().focusRow(this.rowIndex);
},

positionButtons: function(){
if(this.btns){
var g = this.grid,
h = this.el.dom.clientHeight,
view = g.getView(),
scroll = view.scroller.dom.scrollLeft,
bw = this.btns.getWidth(),
width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());

this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
}
},

// private
preEditValue : function(r, field){
var value = r.data[field];
return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
},

// private
postEditValue : function(value, originalValue, r, field){
return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
},

doFocus: function(pt){
if(this.isVisible()){
var index = 0,
cm = this.grid.getColumnModel(),
c;
if(pt){
index = this.getTargetColumnIndex(pt);
}
for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
c = cm.getColumnAt(i);
if(Ext.isFunction(c.getEditor)){
if(!c.hidden && c.getEditor()){
c.getEditor().focus();
break;
}
}}
}
},

getTargetColumnIndex: function(pt){
var grid = this.grid,
v = grid.view,
x = pt.left,
cms = grid.colModel.config,
i = 0,
match = false;
for(var len = cms.length, c; c = cms[i]; i++){
if(!c.hidden){
if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
match = i;
break;
}
}
}
return match;
},

startMonitoring : function(){
if(!this.bound && this.monitorValid){
this.bound = true;
Ext.TaskMgr.start({
run : this.bindHandler,
interval : this.monitorPoll || 200,
scope: this
});
}
},

stopMonitoring : function(){
this.bound = false;
if(this.tooltip){
this.tooltip.hide();
}
},

isValid: function(){
var valid = true;
this.items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
return valid;
},

// private
bindHandler : function(){
if(!this.bound){
return false; // stops binding
}
var valid = this.isValid();
if(!valid && this.errorSummary){
this.showTooltip(this.getErrorText().join(''));
}
this.btns.saveBtn.setDisabled(!valid);
this.fireEvent('validation', this, valid);
},

lastVisibleColumn : function() {
var i = this.items.getCount() - 1,
c;
for(; i >= 0; i--) {
c = this.items.items[i];
if (!c.hidden) {
return c;
}
}
},

showTooltip: function(msg){
var t = this.tooltip;
if(!t){
t = this.tooltip = new Ext.ToolTip({
maxWidth: 600,
cls: 'errorTip',
width: 300,
title: this.errorText,
autoHide: false,
anchor: 'left',
anchorToTarget: true,
mouseOffset: [40,0]
});
}
var v = this.grid.getView(),
top = parseInt(this.el.dom.style.top, 10),
scroll = v.scroller.dom.scrollTop,
h = this.el.getHeight();

if(top + h >= scroll){
t.initTarget(this.lastVisibleColumn().getEl());
if(!t.rendered){
t.show();
t.hide();
}
t.body.update(msg);
t.doAutoWidth(20);
t.show();
}else if(t.rendered){
t.hide();
}
},

getErrorText: function(){
var data = ['<ul>'];
this.items.each(function(f){
if(!f.isValid(true)){
data.push('<li>', f.getActiveError(), '</li>');
}
});
data.push('</ul>');
return data;
}
});
Ext.preg('roweditor', Ext.ux.grid.RowEditor);