PDA

View Full Version : remoteSort on multiple values



adetogni
6 Jul 2010, 12:58 PM
Hi
I've a grid which is working fine. However, I'd like to have a remoteSorting on it (because I'm paging it). However I'm facing two issues:
- I can't remoteSort on a single field, since some values are very similar. If for example I have a table with "name" and "priority" (1-5) and I sort by "name" it works fine, but if I sort by priority only, I'll have names in random order. So I would like to have "priority" and then "name"
- Some fields are rendered with a custom renderer. For example, I've a cell that contains a series of icons: I have to have a custom sorting function when that field is selected. Is it possible?

Also, I can't find anywhere an example of php code that works with remote sorting....

Thank you!

adetogni
11 Jul 2010, 11:33 PM
Really no one has an answer for this?

Condor
12 Jul 2010, 2:20 AM
I did this for remoteSort:false by patching Ext.data.Store, but for remoteSort:true you can simply make your server sort by:

<sortField>, name, id

adetogni
12 Jul 2010, 2:23 AM
I did this for remoteSort:false by patching Ext.data.Store, but for remoteSort:true you can simply make your server sort by:

<sortField>, name, id
Can you please be more detailed?
I need to sort before the query is exectuted, otherwise my output won't work correctly. So remoteSort must be true.
How can I have that result?

THanks

Condor
12 Jul 2010, 2:26 AM
Sort before the query is executed? No, you simply specify the 'sort' and 'dir' parameters in the ORDER BY clause of the query you are executing (followed by the fields you want to sort on by default).

adetogni
12 Jul 2010, 2:41 AM
Sorry, not meant "sort before the query is executed" :-) but "sort during the query" and not in the result in the grid. In other words, remote sorting.
I understand your point, you can simply add additional sorting values in the query, but what if those additional values are different if the "first field" is different?

Also, I can't find any sample of PHP for remote sorting...

Condor
12 Jul 2010, 3:35 AM
Do you actually know how remort sorting works?

When you configure a store with remoteSort:true, it will send a request to the server every time the data needs sorting.
The request contains 'sort' and 'dir' parameters with the field to sort on and the sort direction.
Your server needs to use those parameters to return the data in the correct order, which is usually done by modifying the ORDER BY clause of the used query.

adetogni
12 Jul 2010, 4:32 AM
Do you actually know how remort sorting works?

When you configure a store with remoteSort:true, it will send a request to the server every time the data needs sorting.
The request contains 'sort' and 'dir' parameters with the field to sort on and the sort direction.

That was the missing part. :) So I just have to check the parameters sort and dir, which are sent like any other parameter (i.e. paging parameters,like start and limit)?

kaendsle
13 Apr 2011, 10:06 AM
I was also frustrated to find that when serving data remotely, multiple sorting isn't as easy as plugging in Ext.ux.Reorderer, Ext.ux.Droppable, and Ext.ux.ToolbarDroppable and extending a few components.

Also frustrating was that the multiple sorting example on the ExtJS website is implemented in the global namespace, making it a bit more work to include in an existing application of any significant scale. Acknowledging I'm not the best Javascript developer and didn't want to rewrite the example from scratch, I wrapped that example in a function to get the functions and components to work the way they did when they were all global. My function accepts an Ext.data.Store and Ext.grid.ColumnModel as arguments because in my application those live elsewhere (importantly, as the store is shared by other components).



App.view.MultiSortGrid = function(config, store, columnModel) {
var reorderer = new Ext.ux.ToolbarReorderer(), tbar, obj,
droppable = new Ext.ux.ToolbarDroppable({
// Creates the new toolbar item from the drop event
createItem: function(data) {
var column = this.getColumnFromDragDrop(data);
return createSorterButton({
text: column.header,
sortData: {
field: column.dataIndex,
direction: "ASC"
}
});
},
/**
* Custom canDrop implementation which returns true if a column can be added to the toolbar
* @param {Object} data Arbitrary data from the drag source
* @return {Boolean} True if the drop is allowed
*/
canDrop: function(dragSource, event, data) {
var sorters = getSorters(),
column = this.getColumnFromDragDrop(data);
for (var i=0; i < sorters.length; i++) {
if (sorters[i].field == column.dataIndex) return false;
}
return true;
},
afterLayout: doSort,
/**
* Helper function used to find the column that was dragged
* @param {Object} data Arbitrary data from
*/
getColumnFromDragDrop: function(data) {
var index = data.header.cellIndex,
colModel = obj.colModel,
column = colModel.getColumnById(colModel.getColumnId(index));
return column;
}
});
tbar = new Ext.Toolbar({
items: ['Sorting order:',' '],
plugins: [reorderer, droppable],
style: {padding: '5px'},
listeners: {
scope: this,
reordered: function(button) {
changeSortDirection(button, false);
}
}
});
config = config || {
region: 'west', width: 500,
bubbleEvents: ['load', 'rowclick'],
store: store,
colModel: columnModel,
selModel: new Ext.grid.RowSelectionModel({
singleSelect: true
}),
tbar: tbar,
bbar: new Ext.PagingToolbar({
store: store,
beforePageText: 'Page&nbsp;',
afterPageText: '&nbsp;of {0}',
displayInfo: true,
displayMsg: 'Displaying results {0} - {1} of {2}',
emptyMsg: 'No results to display',
pageSize: 30
})
};
obj = new App.view.GridPanel(config);
// The following functions are used to get the sorting data
// from the toolbar and apply it to the store
function doSort() {
//store.sort(getSorters(), "ASC");
var last = store.lastOptions, s = '', f = '';
Ext.each(getSorters(), function(item) {
f = f.concat(item.field,','); // Array of fields, respective
s = s.concat(item.direction,','); // Array of directions
});
store.reload(Ext.apply(last.params, {fields: f, sorts: s}));
};
/**
* Callback handler used when a sorter button is clicked or reordered
* @param {Ext.Button} button The button that was clicked
* @param {Boolean} changeDirection True to change direction (default). Set to false for reorder
* operations as we wish to preserve ordering there
*/
function changeSortDirection(button, changeDirection) {
var sortData = button.sortData,
iconCls = button.iconCls;
if (sortData != undefined) {
if (changeDirection !== false) {
button.sortData.direction = button.sortData.direction.toggle("ASC", "DESC");
button.setIconClass(iconCls.toggle("sort-asc", "sort-desc"));
}
store.clearFilter();
doSort();
}
};
/**
* Returns an array of sortData from the sorter buttons
* @return {Array} Ordered sort data from each of the sorter buttons
*/
function getSorters() {
var sorters = [];
Ext.each(tbar.findByType('button'), function(button) {
sorters.push(button.sortData);
}, this);
bloo = sorters;
return sorters;
}
/**
* Convenience function for creating Toolbar Buttons that are tied to sorters
* @param {Object} config Optional config object
* @return {Ext.Button} The new Button object
*/
function createSorterButton(config) {
config = config || {};
Ext.applyIf(config, {
listeners: {
click: function(button, e) {
changeSortDirection(button, true);
}
},
iconCls: 'sort-' + config.sortData.direction.toLowerCase(),
reorderable: true
});
return new Ext.Button(config);
};
/**
* Returns an array of fake data
* @param {Number} count The number of fake rows to create data for
* @return {Array} The fake record data, suitable for usage with an ArrayReader
*/
obj.on({
'render': function() {
blah = store;
var dragProxy = this.getView().columnDrag;
droppable.addDDGroup(dragProxy.ddGroup);
}
});
return obj;
};


Note the biggest change to the ExtJS example is the new doSort() function. Previously, this function just performed client-side sorting. Now, it makes a request for remotely-sorted (pre-sorted, if you will) data:


function doSort() {
var last = store.lastOptions, s = '', f = '';
Ext.each(getSorters(), function(item) {
f = f.concat(item.field,','); // Array of fields, respective
s = s.concat(item.direction,','); // Array of directions
});
store.reload(Ext.apply(last.params, {fields: f, sorts: s}));
};

Requests include the parameters fields and sorts. The fields parameter is a comma-separated string of field names, each corresponding to a field to sort on. The sorts parameter is a comma-separated string of the corresponding sort directions. Skip down to the Python code (for Django, Django-Piston) to see how this request is handled.

Creating an instance of the MultiSortGrid looks kind of like this, within yet another function that returns an instance of the MultiSortGrid:


App.dss.inventory = {
UsefulGrid: function(config) {
var obj, reader, store, columnModel;
columnModel = new Ext.grid.ColumnModel({});
reader = new Ext.data.JsonReader({});
store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
api: {read: '/bridge_dss/api/rest/mdot/bridge/inventory'},
restful: true
}),
reader: reader,
remoteSort: false // Custom call for remote sorting implemented
});
obj = App.view.MultiSortGrid(null, this.store, this.columnModel);
obj.on({
'render': function() {
// Any other behaviors
}
});
return obj;
}
}


Now, in Python, here's how I handled the remote sorting using the Django database API within a Django-Piston request handler:


class PageMultiSortingHandler(BaseHandler):
'''
An abstract handler class for paging and remote sorting of multiple fields
of tabular data.
'''
def read(self, request):
attrs = self.flatten_dict(request.GET)
if attrs.has_key('data'):
ext_posted_data = simplejson.loads(request.GET.get('data'))
attrs = self.flatten_dict(ext_posted_data)

try:
begin = int(attrs['start']) # Starting record index
end = begin + int(attrs['limit']) # Ending record index
except KeyError:
return rc.BAD_REQUEST

if 'fields' in attrs and 'sorts' in attrs:
fields = attrs['fields'].split(',')
sorts = attrs['sorts'].lower().split(',')

# Check that there aren't too many sort fields (or malicious input)
if len(fields) < 5 and len(sorts) < 5:
i = 0
j = len(fields)
else:
return rc.BAD_REQUEST

sort_fields = []
while i < j:
if fields[i] != '' and sorts[i] != '':
if sorts[i] == 'desc':
sort_fields.append('-' + fields[i])
elif sorts[i] == 'asc':
sort_fields.append(fields[i])
else:
return rc.BAD_REQUEST
i += 1

query = self.model.objects.order_by(*sort_fields)

else:
query = self.model.objects.all()

return query[begin:end]


Unlike the client-side multiple sorting example, the grid data can be sorted yet again on any field from the header menus. This is actually meaningful when paging is implemented. The remote multiple sorting provides the entire dataset (page by page) sorted and you can sort any subset of the data (say, 30 records on a page) as well.

puattmas
2 Oct 2011, 1:33 PM
Thanks for your hack kaendsale.
Just one more thing, if you are using JsonReader the only way to send that params is this one:


function doSort() {
var last = mainStore.lastOptions, s = '', f = '';
Ext.each(getSorters(), function(item) {
f = f.concat(item.field,','); // Array of fields, respective
s = s.concat(item.direction,','); // Array of directions
});
mainStore.baseParams = {fields: f, sorts: s};
mainStore.reload();
};


And this modification let's you have field without sorting, in my application is very interesting to do that:


function changeSortDirection(button, changeDirection) {
var sortData = button.sortData,
iconCls = button.iconCls;

if (sortData != undefined) {
if (changeDirection !== false) {
if (button.sortData.direction == 'ASC')
button.sortData.direction = 'DESC';
else if (button.sortData.direction == 'DESC')
button.sortData.direction = 'NONE';
else if (button.sortData.direction == 'NONE')
button.sortData.direction = 'ASC';

if (button.sortData.direction == 'ASC')
iconCls = 'sort-asc';
else if (button.sortData.direction == 'DESC')
iconCls = 'sort-desc';
else if (button.sortData.direction == 'NONE')
iconCls = 'sort-none';
button.setIconClass(iconCls);
}

mainStore.clearFilter();
doSort();
}
};


Thanks for helping!
Puattmas

puattmas
2 Oct 2011, 1:45 PM
If you do not want to send NONE directions, use this.



function doSort() {
//mainStore.sort(sd.field, sd.direction);
var last = mainStore.lastOptions, s = '', f = '';
Ext.each(getSorters(), function(item) {
if (item.direction != 'NONE') {
f = f.concat(item.field,','); // Array of fields, respective
s = s.concat(item.direction,','); // Array of directions
}
});
if (f) {
mainStore.baseParams = {fields: f, sorts: s};
mainStore.reload();
}
};