Ronaldo
23 Jun 2009, 10:52 AM
Hi all,
As mentioned here (http://extjs.com/forum/showthread.php?t=71868), I'm working on the new batch save functionality, and more specifically, on visualizing the errors returned for unsuccessfully saved records.
AFAIK, Ext 3.0 assumes (wrongfully IMHO) that, when saving a batch, all records were saved succesfully (or all were rejected). With this plugin, you can return the following json:
{
success: true,
data: [{"username":"","id":"1"},{"email":"","id":"2"}],
errors: [{
"id": 1, // Error for record with id=1, field 'username'
"field": "username",
"msg": "The field username can not be empty."
}, {
"id": 2,
"field": "email", // Error for record with id=2, field 'email'
"msg": "The field email can not be empty."
}, {
"id": 2,
"field": "email", // Another error for record with id=2, field 'email'
"msg": "Invalid email address." // Must conform to x@y.z
}]
}
Note that it's possible to
- return multiple errors for the same record / different fields
- return multiple errors for the same record / same field (as in email, record id=2)
Visually, this amounts to the pictures attached. The tooltip shows all the server side generated error messages for that record/field.
I'm using the private dataView.findCellIndex method, I don't know why it is private, as there's also a findRowIndex method. Anyway, it works for now.
Note that the plugin is not production ready, I'd like to hear your opinion, and if you have any code improvements (there should be many), please let me know.
For one thing, I'm using the innerHTML dom property, but it should be easy to insert a div with a certain class using Ext, shouldn't it? (See the onViewUpdate method).
/**
* GridBatchErrorDisplayPlugin v0.1 - Display error messages for batch save
* functionality in Ext 3.0 RC2.
* See http://extjs.com/forum/newthread.php?do=newthread&f=42
*
* Provided without as is and without warranty. You may use this plugin anywhere you
* want, but you cannot sell it.
*/
Ext.twensoc.GridBatchErrorDisplayPlugin = function(config) {
config = config || {};
Ext.apply(this, config);
}
Ext.twensoc.GridBatchErrorDisplayPlugin.prototype = {
init : function(grid){
this.errors = [];
this.grid = grid;
grid.store.on('write', this.onWrite, this);
grid.store.on('update', this.onUpdate, this);
grid.view.on('refresh', this.onViewUpdate, this);
grid.view.on('rowupdated', this.onViewUpdate, this);
grid.on('render', this.onGridRender, this);
},
onGridRender: function(grid) {
grid.tip = new Ext.ToolTip({
target: grid.view.mainBody, // The overall target element.
delegate: '.x-grid3-cell-error', // Each grid row causes its own seperate show and hide.
renderTo: document.body, // Render immediately so that tip.body can be referenced prior to the first show.
listeners: { // Change content dynamically depending on which element triggered the show.
scope: this,
beforeshow: function updateTipBody(tip) {
var v = this.grid.view;
var r = this.grid.store.getAt(v.findRowIndex(tip.triggerElement));
var fld = this.grid.colModel.config[v.findCellIndex(tip.triggerElement)].name;
var errs = r.errs.get(fld);
if(!errs) {
return;
}
var msg = "";
Ext.each(errs, function(m) {
msg += m + "<br/>";
}, this);
tip.body.dom.innerHTML = msg;
}
}
})
},
onViewUpdate: function(view, firstRow, r) {
if(!r || !r.errs) {
return;
}
var rowIndex = this.grid.store.data.indexOf(r);
r.errs.eachKey(function(k, v) {
var cell = view.getCell(rowIndex, this.grid.colModel.findColumnIndex(k));
Ext.fly(cell).addClass("x-grid3-cell-error");
}, this);
},
onUpdate:function(s, r, operation) {
if(operation != Ext.data.Record.COMMIT || this.errors.length==0) {
return;
}
var errs = [];
Ext.each(this.errors, function(e) {
if(e.id == r.id) {
errs.push(e);
}
}, this);
if(errs.length==0) {
return; // No errors found for record r
}
// One or more errors found for record r, remove the all from the errors list received
Ext.each(errs, function(e) { this.errors.remove(e); }, this);
var v = this.grid.view;
var se = this.grid.store.errors;
if(r.errs) {
r.errs.clear();
} else {
r.errs = new Ext.util.MixedCollection();
}
Ext.each(errs, function(e) {
r.dirty = true;
if(!r.modified){
r.modified = {};
}
r.modified[e.field] = r.data[e.field];
// 'Remodify' other fields from the record
// that produced a server side error when saved
var m = e.modified;
for(var p in m){
r.modified[p] = m[p];
}
// Register the record as changed in the store...
s.afterEdit(r);
r.errs = r.errs || new Ext.util.MixedCollection();
// Save errormsg per field in an array in the record.errs (mixedCollection)
// To allow multiple errors per field, save all messages in an array
var msgs = r.errs.get(e.field);
if(!msgs) {
msgs = [];
r.errs.add(e.field, msgs);
}
msgs.push(e.msg);
}, this);
},
onWrite: function(s, action, result, res, rs) {
// Clear errors from a previous batch request
this.errors = [];
Ext.each(res.errors, function(e) {
// Save the error in the plugin, to be retrieved in the onUpdate listener.
this.errors.push(e);
var r=s.getById(e.id);
// Clone the modified fields of the record
e.modified = r.getChanges();
}, this);
}
};
Here's the css you need to include (You may need to adapt the path):
.x-grid3-cell-error {
background-image:url(/js/ext3.0/resources/images/default/grid/invalid_line.gif);
background-position:0 16px;
background-repeat:repeat-x;
width:198px;
}
As mentioned here (http://extjs.com/forum/showthread.php?t=71868), I'm working on the new batch save functionality, and more specifically, on visualizing the errors returned for unsuccessfully saved records.
AFAIK, Ext 3.0 assumes (wrongfully IMHO) that, when saving a batch, all records were saved succesfully (or all were rejected). With this plugin, you can return the following json:
{
success: true,
data: [{"username":"","id":"1"},{"email":"","id":"2"}],
errors: [{
"id": 1, // Error for record with id=1, field 'username'
"field": "username",
"msg": "The field username can not be empty."
}, {
"id": 2,
"field": "email", // Error for record with id=2, field 'email'
"msg": "The field email can not be empty."
}, {
"id": 2,
"field": "email", // Another error for record with id=2, field 'email'
"msg": "Invalid email address." // Must conform to x@y.z
}]
}
Note that it's possible to
- return multiple errors for the same record / different fields
- return multiple errors for the same record / same field (as in email, record id=2)
Visually, this amounts to the pictures attached. The tooltip shows all the server side generated error messages for that record/field.
I'm using the private dataView.findCellIndex method, I don't know why it is private, as there's also a findRowIndex method. Anyway, it works for now.
Note that the plugin is not production ready, I'd like to hear your opinion, and if you have any code improvements (there should be many), please let me know.
For one thing, I'm using the innerHTML dom property, but it should be easy to insert a div with a certain class using Ext, shouldn't it? (See the onViewUpdate method).
/**
* GridBatchErrorDisplayPlugin v0.1 - Display error messages for batch save
* functionality in Ext 3.0 RC2.
* See http://extjs.com/forum/newthread.php?do=newthread&f=42
*
* Provided without as is and without warranty. You may use this plugin anywhere you
* want, but you cannot sell it.
*/
Ext.twensoc.GridBatchErrorDisplayPlugin = function(config) {
config = config || {};
Ext.apply(this, config);
}
Ext.twensoc.GridBatchErrorDisplayPlugin.prototype = {
init : function(grid){
this.errors = [];
this.grid = grid;
grid.store.on('write', this.onWrite, this);
grid.store.on('update', this.onUpdate, this);
grid.view.on('refresh', this.onViewUpdate, this);
grid.view.on('rowupdated', this.onViewUpdate, this);
grid.on('render', this.onGridRender, this);
},
onGridRender: function(grid) {
grid.tip = new Ext.ToolTip({
target: grid.view.mainBody, // The overall target element.
delegate: '.x-grid3-cell-error', // Each grid row causes its own seperate show and hide.
renderTo: document.body, // Render immediately so that tip.body can be referenced prior to the first show.
listeners: { // Change content dynamically depending on which element triggered the show.
scope: this,
beforeshow: function updateTipBody(tip) {
var v = this.grid.view;
var r = this.grid.store.getAt(v.findRowIndex(tip.triggerElement));
var fld = this.grid.colModel.config[v.findCellIndex(tip.triggerElement)].name;
var errs = r.errs.get(fld);
if(!errs) {
return;
}
var msg = "";
Ext.each(errs, function(m) {
msg += m + "<br/>";
}, this);
tip.body.dom.innerHTML = msg;
}
}
})
},
onViewUpdate: function(view, firstRow, r) {
if(!r || !r.errs) {
return;
}
var rowIndex = this.grid.store.data.indexOf(r);
r.errs.eachKey(function(k, v) {
var cell = view.getCell(rowIndex, this.grid.colModel.findColumnIndex(k));
Ext.fly(cell).addClass("x-grid3-cell-error");
}, this);
},
onUpdate:function(s, r, operation) {
if(operation != Ext.data.Record.COMMIT || this.errors.length==0) {
return;
}
var errs = [];
Ext.each(this.errors, function(e) {
if(e.id == r.id) {
errs.push(e);
}
}, this);
if(errs.length==0) {
return; // No errors found for record r
}
// One or more errors found for record r, remove the all from the errors list received
Ext.each(errs, function(e) { this.errors.remove(e); }, this);
var v = this.grid.view;
var se = this.grid.store.errors;
if(r.errs) {
r.errs.clear();
} else {
r.errs = new Ext.util.MixedCollection();
}
Ext.each(errs, function(e) {
r.dirty = true;
if(!r.modified){
r.modified = {};
}
r.modified[e.field] = r.data[e.field];
// 'Remodify' other fields from the record
// that produced a server side error when saved
var m = e.modified;
for(var p in m){
r.modified[p] = m[p];
}
// Register the record as changed in the store...
s.afterEdit(r);
r.errs = r.errs || new Ext.util.MixedCollection();
// Save errormsg per field in an array in the record.errs (mixedCollection)
// To allow multiple errors per field, save all messages in an array
var msgs = r.errs.get(e.field);
if(!msgs) {
msgs = [];
r.errs.add(e.field, msgs);
}
msgs.push(e.msg);
}, this);
},
onWrite: function(s, action, result, res, rs) {
// Clear errors from a previous batch request
this.errors = [];
Ext.each(res.errors, function(e) {
// Save the error in the plugin, to be retrieved in the onUpdate listener.
this.errors.push(e);
var r=s.getById(e.id);
// Clone the modified fields of the record
e.modified = r.getChanges();
}, this);
}
};
Here's the css you need to include (You may need to adapt the path):
.x-grid3-cell-error {
background-image:url(/js/ext3.0/resources/images/default/grid/invalid_line.gif);
background-position:0 16px;
background-repeat:repeat-x;
width:198px;
}