PDA

View Full Version : [3.0RC2] GridBatchErrorDisplayPlugin alfa



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;
}

Scorpie
25 Jun 2009, 12:32 AM
Nice to see fellow Dutchies developing extensions :)

Ronaldo
27 Jun 2009, 11:16 AM
Heja, we should organize a Dutch Ext Meeting after the holiday :)

Scorpie
29 Jun 2009, 1:48 AM
Heja, we should organize a Dutch Ext Meeting after the holiday :)

Count me in!

Ronaldo
20 Jul 2009, 4:46 AM
Hi all,

Here's a new version that's updated for the latest 3.0 release. Don't forget to include this css:

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;
}

/**
* GridBatchErrorDisplayPlugin v0.2 - Display error messages for batch save
* functionality in Ext 3.0.
* 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.proxy.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);
},
/**
* Callback from the proxy. We need to extract errors from the result and save them
* locally (to this plugin) before the grid is updated and rendered, as this is the only change
* we have to get the complete json result
*/
onWrite: function(proxy, action, data, response, rs, arg) {
var s=this.grid.store;
// Clear errors from a previous batch request
this.errors = [];
s.data.each(function(item) {
delete(item.errs);
});
// Copy errors locally
Ext.each(response.errors, function(e) {
this.errors.push(e);
var r=s.getById(e.id);
// Clone the modified fields of the record
e.modified = r.getChanges();
}, this);
}
};

dotnetwise
30 Jan 2011, 8:49 AM
Very nice plugin!
I can see that it only works for 'update' action.
Thus "phantom" records are not working the same way.

I have made some changes and now everything works like a charm on 3.3.1.
I hope I will have time to share the code soon!