Sorting and Other Dojo.Data Considerations
Submitted by criecke on Thu, 11/08/2007 - 15:21.
By default, when dojo.data datasources feed a Grid, the columns are not user-sortable. That's easy to rectify. Just set clientSort="true" in the tag:
<div dojoType="dojox.grid.data.DojoData" jsId="model"
rowsPerPage="20" store="jsonStore" query="{ namespace: '*' }"
clientSort="true">
</div>
rowsPerPage="20" store="jsonStore" query="{ namespace: '*' }"
clientSort="true">
</div>
Now the user can click on any column to sort it. You may also set the sort order programmatically:
// Sort 4'th field in ascending order myGrid.setSortIndex(3, true);
Filtering
To filter a list, you can create a new adapter with a different query then attach it to the grid.
//Construct a new model. The store needs to be passed into the constructor, as the constructor for the //DojoData model examines the store and configures the model to support various features of the store //such as Write, Identity, and Notification. Setting the store after the constructor will leave the //model in a misconfigured state and be unable to support data writebacks in the case of Write implementing // datastores. var newModel = new dojox.grid.data.DojoData(null,null,{rowsPerPage: 20, store: myStore, query: {type: someOtherType}, clientSort: true}); myGrid.setModel(newModel); // Remember to call newModel.destroy() when you're done.
A popular use of filtering is to display a grid with everything, then let the user chop the list down incrementally. In this case, you can define an initial model with a minimal query. Save it, and then you can setModel back to it for quickly resetting all the filters. But do not keep unused models lying around! They take up memory.
- Printer-friendly version
- Login or register to post comments
- Subscribe post
Server Side Sorting and Paging with DojoData and QueryReadStore
EDIT: Fixed to use the full HTML input format, so you get the highlight code.
Out-of-box DojoData only supports sorting/paging on client side. A relatively straight-forward way to pass a query for server side sorting and paging uses dojox.data.QueryReadStore and requires altering few DojoData methods. (This snippet builds on a Dojox.Grid test - see that one first.)
jsId="continentStore"
url="continents.php"
doClientPaging="false"
>
<span dojoType="dojox.grid.data.DojoData"
jsId="dataModel2"
rowsPerPage="20"
store="continentStore"
query="{ name : '*' }"
>
<script type="dojo/method" event="requestRows" args="inRowIndex, inCount">
// creates serverQuery-parameter
var row = inRowIndex || 0;
var params = {
start: row,
count: inCount || this.rowsPerPage,
serverQuery: dojo.mixin(
{ start: row,
count: inCount || this.rowsPerPage,
sort:(this.sortColumn || '')
},
this.query
),
query: this.query,
// onBegin: dojo.hitch(this, "beginReturn"),
onComplete: dojo.hitch(this, "processRows")
}
this.store.fetch(params);
</script>
<script type="dojo/method" event="getRowCount">
// should return total count (fetch from server), not "rowsPerPage"
return 50;
</script>
<script type="dojo/method" event="sort" args="colIndex">
// clears old data to force loading of new, then requests new rows
this.clearData();
this.sortColumn = colIndex;
this.requestRows();
</script>
<script type="dojo/method" event="setData" args="inData">
// edited not to reset the store
this.data = [];
this.allChange();
</script>
<script type="dojo/method" event="canSort">
// always true
return true;
</script>
</span>
Programmatic version of server-side scrolling?
NOTE: I edited this comment to fix a couple of small errors, and now it has included a buch of weird CSS type stuff. Please ignore it.
Maine, thanks a lot for the sample above! It saved me a ton of work. But as you know, no good deed goes unpunished, so I have a follow up question. After some work, I got the above to work against my JSON-producing servlet and paging works without issue. Now I have two questions. The first concerns server side sorting. I'm not sure if this is not fully implemented in the example above, or if I need to rewrite my server code somehow, though I have tried it a few different ways without any luck. Basically, sorting is fine when going forward, but in reverse order (assuming a page size of 5), records come out in the following order: 5, 4, 3, 2, 1, 10, 9, 8, 7, 6, 15, 14, etc. In other words, each page is sorted properly, but the pages are still arranged in the original order. One question has to do with the "start" parameter that is passed to the server. In the case of reverse sort, should that be considered "end". In other words, should "start" indicate the high or low limit of the page?
The next question regards how to transform the above example from the declarative model to the programmatic. I have written a new class that extends dojox.grid.data.DojoData with the intent of replicating the above markup.
Here's what I have:
// paging and sorting
// ES 11/28/2007
dojo.provide("xyz.data.ServerGridData");
// dojox.grid._data.model is the module with dojox.grid.data.DojoData
dojo.require("dojox.grid._data.model");
// our declared class
dojo.declare("xyz.data.ServerGridData",
// we inherit from this class
dojox.grid.data.DojoData,
// class properties:
{
// custom override of requestRows()
requestRows: function (inRowIndex, inCount)
{
console.log("in requestRows()");
// creates serverQuery-parameter
var row = inRowIndex || 0;
var params = {
start: row,
count: inCount || this.rowsPerPage,
serverQuery: dojo.mixin(
{ start: row,
count: inCount || this.rowsPerPage,
sort:(this.sortColumn || '')
},
this.query
),
query: this.query,
// onBegin: dojo.hitch(this, "beginReturn"),
onComplete: dojo.hitch(this, "processRows")
}
this.store.fetch(params);
},
getRowCount: function ()
{
console.log("in getRowCount()");
//should get row count from server
// for now just return 1000
return 1000;
},
// src: String
// src url for AccordionPaneExtension header
setData: function(inData)
{
console.log("in setData()");
// edited not to reset the store
this.data = [];
this.allChange();
},
sort: function()
{
this.clearData();
this.sortColumn = colIndex;
this.requestRows();
},
canSort: function ()
{
// always true
return true;
}
});
I then have an HTML page that includes:
jsId="serverStore"
url="pbgridapi?action=getLineItems"
doClientPaging="false"
/>
<span dojoType="xyz.data.ServerGridData"
jsId="serverModel"
rowsPerPage="20"
store="serverStore"
query="{ value : '*' }"
/>
<div id="grid2" dojoType="dojox.Grid" elasticView="2"
model="serverModel" structure="simpleLayout">
</div>
This sort of works in the sense that it loads my custom class; the table appears and the first 20 rows are fetched from the server, but none of my overridden functions get called. Breakpoints in Firebug never get hit, the console statements never execute, etc.
So maybe this is just me misunderstanding how to extend a class with Dojo, but I've tried to follow the examples and can't see what is wrong.
Thanks in advance for any suggestions.
Rhyme & Reason
Online tools for poets and lyricists
Inheriting DojoData-model Requires Overriding Markup Factory
No problem. I'm not quite sure I understood your sorting/paging problem. However, I can help you with the other problem. Classes that inherit DojoData need to override method called markupFactory. In DojoData it's defined:
return new dojox.grid.data.DojoData(null, null, args);
},
MarkupFactory is sort of a constructor for object instances created from markup. If it's not overridden in a subclass, it will only create instances of DojoData, not the subclass.
calls superclass constructor
More generally: if a class is instantiated from tags ("declaratively") and doesn't have a markupFactory defined, then the constructor of its superclass will be used -- not its own constructor!
Great, thanks.
I was under the false impression that markupFactory was required for visible components; didn't realize that it was required to be created from a tag, visible or not.
I'll figure out the sorting issue and post the code here later. I really appreciate your help.
Rhyme & Reason
Online tools for poets and lyricists
Grid editors don't update the model, and where does paging ...
...actually come from? I've implemented this same code moving the markup to custom DojoData as nihou did. The grid gets data from the server at load like it should. I added a cell editor, and can edit a row. However, a model observer for ModelRowChange never fires and therefore I can't do an xhrPost back to the server b/c I don't know the data's changed.
An additional problem is that after the initial data load to the grid (5 rows for example), what do I do to trigger getting the next set of rows from the server via QueryReadStore? Scrolling the grid doesn't do it. Am I supposed to provide a button or something? I tried coding a button that tells the store to fetch more data, and then a grid.update() command. I saw that the url was called and data brought back, but nothing updated in the grid.
I'm looking more for procedure here than code. I can see where the QueryReadStore url call provides start and count for it's initial call. It seems like the grid should be triggering new QueryReadStore url calls where the count and start are automatically figured out. What am I missing?
auto-scrolling, etc.
Lance,
You don't need a button. I was able to get the auto scrolling to work against the server when the scroll bar is used. Not sure what the issue is for you, but you should make sure that the grid thinks the total number of rows is large (I hard coded 1000 to test). Also, try making the page size larger. I used 20. I don't have my code in front of me right now, but will try to post it later.
As for editing, I haven't implemented that, so I don't know how to solve it, but I'm not surprised it doesn't work with the sample given; I don't believe it is implemented.
Rhyme & Reason
Online tools for poets and lyricists
Virtual scrolling triggers requestRows and loads new pages
As scrollpane already pointed out above, scrolling the table (Virtual scrolling) triggers requestRows and fetches data from server. Just check and try the example on this page.
This call stack gives you a fairly good picture of the procedure of turning scrolling into loading new pages from the server (grid->model->store->server):
scroller.base.scroll
scroller.base.needPage
scroller.base.buildPage
scroller.columns.renderPage
views.renderRow
GridView.renderRow
GridView.buildRow
GridView.buildRowContent
contentBuilder.generateHtml
cell.format
VirtualGrid.get
data.DojoData.getDatum
data.Dynamic.getRow
data.Dynamic.preparePage
data.Dynamic.needPage
data.Dynamic.requestPage
data.DojoData.requestRows
QueryReadStore.fetch
QueryReadStore._fetchItems
Thanks for helping, have it working now!
I took a look at the 2 variables that influence this. If you have DojoData's rowsPerPage field set higher than the rowCount returned by getRowCount, you end up with duplicated data in the grid. Not good.
When you have rowCount>rowsPerPage, the scroller now performs as expected and QueryReadStore requests are made to the backend automatically. The count=30 it asks for is equal to rowsPerPage=30 setting I'd put in for DojoData.
So, problem resolved. However, I'm not comfortable with the getRowCount function as the comment in it says to replace the hardcoded value with that of the QueryReadStore's fetch. That's not correct either. It assumes the fetch will be returning the entire data set from the server at once. As a working matter, this would probably result in an initial setting that is sufficiently large to guarantee paging, and then at some point an AJAX call has to be made to see what the actual size of the entire data set is.
Anyway, minor issue. Thanks for getting me over the hurdle.
Problematic getRowCount
To clarify, in my example the "fetch from server" means that you would make a separate query to get the row count (data set size) - just as Lance pointed out. To get the grid render nicely you'd postpone rendering until you have this number. It seems like DbTable and yahooSearch models also fetch row count on a separate query (see Grid Mysql table editing).
However, this technique works nicely as long as you're only sorting. When you add server side filtering, it gets complicated as different filters cause the data set size to vary. As a consequence, you'd always make two queries to fetch the data - one to get the count and second one to fetch the actual row content. (Plus, if you're fetching stuff from different db tables, you'd also query for the grid structure.)
getRowCount
In case anybody might find this helpful, here's the implementation of getRowCount I'm using. It works, but is synchronous. Have not yet tried to get it to work asynch...
{
console.log("in getRowCount()");
var rowCount = 0;
dojo.xhrGet( {
preventCache: true,
url: "/gridapi?action=getNumLineItems",
handleAs: "text",
sync: true, //may be a bad idea, but for now make this synchronous
timeout: 5000, // Time in milliseconds
// The LOAD function will be called on a successful response.
load: function(response, ioArgs) {
console.log("in getNumLineItems callback");
var result = eval('(' + response + ')');
rowCount = result.numLineItems;
console.log("rowCount = " + rowCount);
},
// The ERROR function will be called in an error case.
error: function(response, ioArgs) {
console.error("HTTP status code: ", ioArgs.xhr.status);
return response;
}
});
return rowCount;
}
Rhyme & Reason
Online tools for poets and lyricists
I just cannot get this to work, any help would be great
/ext/js/dj102 is the path to my install base of dojo
I have for my main html page