PDA

View Full Version : Add server-side validation to your gridpanel



Onkelborg
18 Sep 2010, 12:53 AM
Hi!

This is my first post on this forum :) I've been using ExtJS for a few weeks now, and I've purchased a license to use ExtJS + Ext Designer within two weeks. I love it :)

However, much like with ASP.NET (which is what I use on the server-side), ExtJS isn't ready for everything. I make my own customizations everywhere, extensionmethods and T4-templates.. :)

In my case, I got a problem when I had an EditorGridPanel, which, of course, worked like a charm, if it wasn't for one tiny detail: how to enable display of server validation errors?

I found a thread with a rather malfunctioning solution, however, still a solution: http://www.sencha.com/forum/showthread.php?87570-Store-save-with-server-side-validation-errors&highlight=server+side+validation

I wasn't satisfied with that. Time to start digging documentation :)

JSON from server from my not so successful, restful:


{
"success": false,
"errors": {
"LastName": "Fill in your lastname!",
"ZipCode": "Do you know what a zip code is?"
}
}


Theory:

Before writing, map the json data to the record
In exception, get the json data, and use the map to..
Get the erroneous record
Get the rowIndex for the record
Get all columns that's editable
Get all errors from the server
For each erroneous column, get the errormessage and colIndex
Get the element, and the first child
Mark as "invalid" and set the error message
And the opposite for the good ones


That's pretty much it :) (And it works for both create and update, I've modified the example I mentioned ;) )

Code:


// Oskar Johansson, http://onkelborg.com
activateGridValidation = function (grid) {
var store = grid.getStore();
var mapRecordToRequestJsonData = [];
var mapRecordToRequestRecord = [];
var storeOnBeforeSave = function (store, data) {
mapRecordToRequestJsonData = [];
mapRecordToRequestRecord = [];
};
var storeOnBeforeWrite = function (dataProxy, action, rs, params) {
mapRecordToRequestJsonData.push(params.jsonData);
mapRecordToRequestRecord.push(rs);
};
var storeOnException = function (dataProxy, type, action, options, response, arg) {
if (action == "create" || action == "update") {
var erroneousObject = mapRecordToRequestRecord[mapRecordToRequestJsonData.indexOf(options.jsonData)];
var rowIndex = store.indexOf(erroneousObject);
var colModel = grid.getColumnModel();
var gridView = grid.getView();
var errorsInner;
var errors = response.raw ? response.raw.errors : ((errorsInner = Ext.decode(response.responseText)) ? errorsInner.errors : null);
var editableColumns = [];
for (var i = 0; i < colModel.getColumnCount(); i++) {
var column = colModel.getColumnById(colModel.getColumnId(i));
if (column.getCellEditor(rowIndex)) {
editableColumns.push(colModel.getDataIndex(i));
}
}
if (errors) {
var erroneousColumns = [];
for (var x in errors) {
erroneousColumns.push(x);
}
for (var i = 0; i < erroneousColumns.length; i++) {
editableColumns.splice(editableColumns.indexOf(erroneousColumns[i]), 1);
}
for (var i = 0; i < erroneousColumns.length; i++) {
var errKey = erroneousColumns[i];
var colIndex = colModel.findColumnIndex(errKey);
var msg = errors[errKey];
var cell = gridView.getCell(rowIndex, colIndex);
var cellElement = Ext.get(cell);
var cellInnerElement = cellElement.first();
cellInnerElement.addClass("x-form-invalid");
cellInnerElement.set({ qtip: msg });
}
}
for (var i = 0; i < editableColumns.length; i++) {
var colIndex = colModel.findColumnIndex(editableColumns[i]);
var cell = gridView.getCell(rowIndex, colIndex);
var cellElement = Ext.get(cell);
var cellInnerElement = cellElement.first();
cellInnerElement.removeClass("x-form-invalid");
cellInnerElement.set({ qtip: "" });
}
}
};
store.proxy.on("exception", storeOnException);
store.proxy.on("beforewrite", storeOnBeforeWrite);
store.on("beforesave", storeOnBeforeSave);
grid.on("destroy", function () {
store.proxy.un("exception", storeOnException);
store.proxy.un("beforewrite", storeOnBeforeWrite);
store.un("beforesave", storeOnBeforeSave);
});
};


Feel free to use it, if you want to, however, there are som quirks I know about:

It wont survive you changing store for the grid
I've only tested this against a restful store, I suspect this wont work if it isn't restful

martijnbak
27 Jan 2011, 7:54 AM
Hi,

Thanks for your post and your code.
I've made a version that works fine with a store with batch = true, i.e. 1 ajax call for all dirty records, and 1 for all newly created ones.
The JSON sent back from the server should be something like



{ "success": false:
"errors": {
"id1": {"city": "can't be blank"},
"id2": {"city": "can't be blank", "zip": "format error"}
}
}Here, id1 and id2 are values of the IdProperty of records with errors, and city and zip are fields in the store.



Ext.ux.myspace.EditorGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
constructor: function(config) {
Ext.ux.myspace.EditorGridPanel.superclass.constructor.call(this, config);
var grid = this;
var store = grid.getStore();
var exceptionHandler = function(dataProxy, type, action, options, response, arg) {
if (action == "create" || action == "update") {
var errorsInner;
var all_errors = response.raw ? response.raw.errors : ((errorsInner = Ext.decode(response.responseText)) ? errorsInner.errors : null);
if (all_errors) {
for (var key in all_errors) {
var errors = all_errors[key];
var record = store.getById(key);

var rowIndex = store.indexOf(record);
var colModel = grid.getColumnModel();
var gridView = grid.getView();

var editableColumns = [];
for (var i = 0; i < colModel.getColumnCount(); i++) {
var column = colModel.getColumnById(colModel.getColumnId(i));
if (column.getCellEditor(rowIndex)) {
editableColumns.push(colModel.getDataIndex(i));
}
}
if (errors) {
var erroneousColumns = [];
for (var x in errors) {
erroneousColumns.push(x);
}
for (i = 0; i < erroneousColumns.length; i++) {
editableColumns.splice(editableColumns.indexOf(erroneousColumns[i]), 1);
}
for (i = 0; i < erroneousColumns.length; i++) {
var errKey = erroneousColumns[i];
var msg = errors[errKey];
var colIndex = colModel.findColumnIndex(errKey);
var cell = gridView.getCell(rowIndex, colIndex);
var cellElement = Ext.get(cell);
var cellInnerElement = cellElement.first();
cellInnerElement.addClass("x-form-invalid");
cellInnerElement.set({qtip: msg});
}
}
for (i = 0; i < editableColumns.length; i++) {
colIndex = colModel.findColumnIndex(editableColumns[i]);
cell = gridView.getCell(rowIndex, colIndex);
cellElement = Ext.get(cell);
cellInnerElement = cellElement.first();
cellInnerElement.removeClass("x-form-invalid");
cellInnerElement.set({qtip: ""});
}
}
}
}
}
store.proxy.on("exception", exceptionHandler);
}
});

plantijaabjcbd
29 Feb 2012, 11:24 PM
I am working for a company who use tabulated html/JS interfaces.I'm trying to leverage Zend_Form decorators (http://framework.zend.com/manual/en/learning.form.decorators.simplest.html) to create Ext.grid.Panel column defintions (http://docs.sencha.com/ext-js/4-0/#%21/api/Ext.panel.Table-cfg-columns). For this, I would need to use decorators to export an array (and then json it using the ViewHelper), or render a JSON string directly.So this would be something like:$dateElement = new Zend_Form_Element_Text('startDate', array(

'label' => 'Start Date',
'validators' => array(
new Zend_Validate_Date()
)
));

echo (string)$dateElement;
would output:{ text: 'Start Date', dataIndex:'startDate', xtype:'datecolumn'}


Has anyone here tried to do anything similiar to this (getting a
JSON/XML/other markups output, rather than HTML from Zend_Forms using
Decorators) or if they could point me to any resources?
plantillas web (http://www.plantilandia.com/)
*ACTIONS speak louder than words*

ecoimpjaabjaag
14 Mar 2012, 11:15 PM
What you want is something like print a field and his name as JSON or XML and not has HTML? I ask this because I never used ExtJS but, if this is what you want, I think I have part of the solution... Could you give an example of what you want as output from Decorators?
impresoras cd (http://www.ecoimpresoras.com/)


*Good FRIENDS are hard to find, harder to leave, and impossible to forget*