PDA

View Full Version : [OPEN-1242] Store's remoteSort requests ignore a field's mapping value



le_lag
27 Aug 2010, 2:08 AM
Ext version tested:

Ext 3.3.0


Adapter used:

ext


css used:

only default ext-all.css




Browser versions tested against:

FF3 (firebug 1.3.0.10 installed)
Safari 4


Operating System:

Mac OS X 10.6


Description:
I'm not sure this count as a bug but the behaviour of Ext.data.Store's remoteSort seems inconsistant with other's client to server data operations as it will request that sorting be done using a field's name instead of the field's mapping when a mapping exists.

The point of mapping field's name should be to allow different semantics on the server than on the client. It works as expected when using an Ext.data.Writer to save a store on the server. The field's name are converted back to the mapping value.

Test Case:

Given this store :


TestStore = Ext.extend(Ext.data.JsonStore, {
constructor: function(cfg) {
cfg = cfg || {};
TestStore.superclass.constructor.call(this, Ext.apply({
storeId: 'TestStore1',
root: 'data',
writer: new Ext.data.JsonWriter({
encode:false,
writeAllFields:true
}),
autoSave:false,
remoteSort:true,
proxy: new Ext.data.HttpProxy({
method:"POST",
api: {
read: {url: '/read.json', method: 'GET'},
create: "/create.fake",
update: "/update.fake",
destroy: "/destroy.fake"
}
}),
fields: [
{
name: 'id',
allowBlank: false,
type: 'int'
},
{
name: 'arg1'
},
{
name: 'arg2',
mapping: 'real_name_arg2'
}
]
}, cfg));
}
});
TestStore = new TestStore();


load.json :


{"message":"","data":[
{"id":1, "arg1":"record1_arg1", "real_name_arg2":"record1_arg2"}
],"success":true,"total":1}



Steps to reproduce the problem:

Request to sort by "arg2"
TestStore.sort('arg2', 'ASC');


The result that was expected:

GET http://localhost:3000/read.json?_dc=1282900779935&sort=real_name_arg2&dir=ASC


The result that occurs instead:

GET http://localhost:3000/read.json?_dc=1282900779935&sort=arg2&dir=ASC


Screenshot or Video:

See http://gist.github.com/553104


Debugging already done:

A store always use a field's name for all sorting operation (including remoteSort)


Possible fix:

Modify store's sort function to use a field's mapping is one is available
Well since this behaviour has been in Ext forever, it could potentially break existing applications to change it now but adding a remoteSortUseMapping(bool) options to the store config would solve this.

sumit.madan
27 Aug 2010, 3:06 AM
I confirm that this is also the case in ExtJS version 3.2.2, a mapping should always translate the posted field dataIndex, if present.

In addition to sort, the groupBy parameter also ignores a mapped dataIndex.

Condor
27 Aug 2010, 4:05 AM
'mapping' is only in a single direction from server data to client data. It isn't used to do the reverse (and it can't be used - 'mapping' can even be a function!).

I you want, you can modify the parameters that are actually sent to the server in the beforeload event of the store.

sumit.madan
27 Aug 2010, 4:12 AM
'mapping' is only in a single direction from server data to client data. It isn't used to do the reverse (and it can't be used - 'mapping' can even be a function!).
A writable store maps the dataIndexes when posting new records or edited records to server. If mapping is unidirectional only, then this behavior should not exist.

le_lag
27 Aug 2010, 5:09 AM
I agree with sumit.madan.

If you use the DataProxy API with a store, the mapping is used to write back to the backend.

It seems only logical this works this way : if your backend identify a field with a name, you need to send back the same name if you want it to understand your request.

Since the server/client mapping is known in the fields configuration, I can't see how to justify sending a client specific info to the server hoping it will understand it...

Example Firebug Console Log :


>>> TestStore.load();
GET http://localhost:3000/read.json?_dc=1282900743785
>>> TestStore.getAt(0).set('arg2', 'new arg2');
>>> TestStore.save();
POST http://localhost:3000/update.fake

POST data
arg1 "record1_arg1"
id 1
real_name_arg2 "new arg2" # <- the server is returned the proper
# mapping name for the field

Source
{"data":{"id":1,"arg1":"record1_arg1","real_name_arg2":"new arg2"}}


Using the beforeload event is a smart workaround but I feel the framework would behave in a more consistent manner if it could directly use the mappings.

le_lag
27 Aug 2010, 8:03 AM
Here's a patch that solves the issue for store's remotesort while not breaking anyone's existing applications.



*** OriginalStore.js 2010-07-27 14:38:48.000000000 +0200
--- Store.js 2010-08-27 15:46:02.000000000 +0200
***************
*** 166,185 ****
--- 166,192 ----
* {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
* the following two parameters to the <b><tt>{@link #load params}</tt></b>:<div class="mdetail-params"><ul>
* <li><b><tt>sort</tt></b> : String<p class="sub-desc">The <tt>name</tt> (as specified in the Record's
* {@link Ext.data.Field Field definition}) of the field to sort on.</p></li>
* <li><b><tt>dir</tt></b> : String<p class="sub-desc">The direction of the sort, 'ASC' or 'DESC' (case-sensitive).</p></li>
* </ul></div></p>
*/
remoteSort : false,

/**
+ * @cfg {boolean} remoteSortUseMapping <tt>true</tt> if a store field's mapping must be used when sorting using remoteSort = true
+ * If no mapping exists, the field name is used.
+ * Defaults to <tt>false</tt>).
+ */
+ remoteSortUseMapping : false,
+
+ /**
* @cfg {Boolean} autoDestroy <tt>true</tt> to destroy the store when the component the store is bound
* to is destroyed (defaults to <tt>false</tt>).
* <p><b>Note</b>: this should be set to true when using stores that are bound to only 1 component.</p>
*/
autoDestroy : false,

/**
* @cfg {Boolean} pruneModifiedRecords <tt>true</tt> to clear all modified record information each time
* the store is loaded or when a record is removed (defaults to <tt>false</tt>). See {@link #getModifiedRecords}
* for the accessor method to retrieve the modified records.
***************
*** 833,853 ****
* </ul>
* @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
* <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
*/
load : function(options) {
options = Ext.apply({}, options);
this.storeOptions(options);
if(this.sortInfo && this.remoteSort){
var pn = this.paramNames;
options.params = Ext.apply({}, options.params);
! options.params[pn.sort] = this.sortInfo.field;
options.params[pn.dir] = this.sortInfo.direction;
}
try {
return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
} catch(e) {
this.handleException(e);
return false;
}
},

--- 840,865 ----
* </ul>
* @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
* <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
*/
load : function(options) {
options = Ext.apply({}, options);
this.storeOptions(options);
if(this.sortInfo && this.remoteSort){
var pn = this.paramNames;
options.params = Ext.apply({}, options.params);
! var f = this.fields.get(this.sortInfo.field);
! if(this.remoteSortUseMapping && f && f.mapping) {
! options.params[pn.sort] = f.mapping;
! } else {
! options.params[pn.sort] = this.sortInfo.field;
! }
options.params[pn.dir] = this.sortInfo.direction;
}
try {
return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
} catch(e) {
this.handleException(e);
return false;
}
},

sumit.madan
27 Aug 2010, 11:08 AM
@le_lag
Thanks!

A small observation, Store.fields MixedCollection may not exist on load, if it is configured by metaData from server.

Here are the overrides for sending the sort and the groupBy params based on the mapping. Only apply the override if you want the sort and groupBy params to contains mapped fields dataIndexes.



Ext.override(Ext.data.Store, {
load : function(options) {
options = Ext.apply({}, options);
this.storeOptions(options);
if (this.sortInfo && this.remoteSort) {
var pn = this.paramNames;
options.params = Ext.apply({}, options.params);

var field = (this.fields instanceof Ext.util.MixedCollection) ? this.fields.get(this.sortInfo.field): null;
var sortInfoField = (field && field.mapping) ? field.mapping : this.sortInfo.field;

options.params[pn.sort] = sortInfoField;
options.params[pn.dir] = this.sortInfo.direction;
}
try {
return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
} catch(e) {
this.handleException(e);
return false;
}
}
});

Ext.override(Ext.data.GroupingStore, {
applyGroupField: function(){
if (this.remoteGroup) {
if(!this.baseParams){
this.baseParams = {};
}

var field = (this.fields instanceof Ext.util.MixedCollection) ? this.fields.get(this.groupField): null;
var groupField = (field && field.mapping) ? field.mapping : this.groupField;

Ext.apply(this.baseParams, {
groupBy : groupField,
groupDir: this.groupDir
});

var lo = this.lastOptions;
if (lo && lo.params) {
lo.params.groupDir = this.groupDir;

//this is deleted because of a bug reported at http://www.extjs.com/forum/showthread.php?t=82907
delete lo.params.groupBy;
}
}
}
});