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
Test my inherited Grid @import "/ext/js/dj102/dojox/grid/_grid/tundraGrid.css"; @import "/ext/js/dj102/dijit/themes/tundra/tundra.css"; @import "/ext/js/dj102/dojo/resources/dojo.css"; #DataGrid {border: 1px solid #333;width: 49em;height: 30em;} var gridLayout = [{ cells: [[ {name: 'Title' , width: "25em", field: "title"}, {name: 'Style', width: "10em", field: "style"}, {name: 'Finish', width: "10em", field: "finish"} ]] }]; dojo.require("php.dynamicGridData");
<head>
- Login or register to post comments
Submitted by Maine on Wed, 01/02/2008 - 09:32.
- Login or register to post comments
Submitted by rhonda on Wed, 01/02/2008 - 17:07.
- Login or register to post comments
Submitted by Maine on Wed, 01/02/2008 - 17:33.
dojo.declare("php.dynamicGridData", dojox.grid.data.DojoData,
markupFactory: function(params, domNode, constructorFunction){
- Login or register to post comments
Submitted by rhonda on Thu, 01/03/2008 - 20:49.
- Login or register to post comments
Submitted by Maine on Fri, 01/04/2008 - 12:20.
- Login or register to post comments
Submitted by rhonda on Fri, 01/04/2008 - 21:43.
- Login or register to post comments
Submitted by kchopein_2 on Fri, 01/04/2008 - 12:09.
- Login or register to post comments
Submitted by Maine on Fri, 01/04/2008 - 12:15.
- Login or register to post comments
Submitted by kchopein_2 on Mon, 01/07/2008 - 16:09.
- Login or register to post comments
Submitted by rhonda on Wed, 01/09/2008 - 00:34.
dojo.provide("php.newComboBox");
dojo.provide("php.newGridData");
And have a class defined in /ext/js/dj102/php/dynamicGridData.js that looks like this
dojo.require("dojox.data.QueryReadStore"); dojo.require("dojox.grid.Grid"); dojo.require("dojox.grid._data.model"); dojo.provide("php.dynamicGridData"); dojo.declare("php.dynamicGridData", dojox.grid.data.DojoData, { markupFactory: function(args, node) { return new dojox.grid.data.DojoData(null, null, args); }, requestRows: function (inRowIndex, inCount) { console.log("request"); var row = inRowIndex || 0; var params = { // onItem:gotone, start: row, count: inCount || this.rowsPerPage, serverQuery: dojo.mixin( { start: row, count: inCount || this.rowsPerPage, sort:(this.sortColumn || '') }, this.query ), query: this.query, onComplete: dojo.hitch(this, "processRows") } this.store.fetch(params); }, getRowCount: function () { return 7000; }, setData: function(inData) { this.data = []; this.allChange(); }, sort: function(colIndex) { this.clearData(); this.sortColumn = colIndex; this.requestRows(); }, canSort: function () { return true; } } );The class I created just does not seem to come alive (instantiated). I get the first pull of data from the server and nothing else happens. Its like I have the default behavior of dojox.grid.data.DojoData. I have no errors or indications reported in firebug. I have a feeling it may have to do with the order of loading of the js but as I am very new to dojo and looked everywhere at every relevant object and method call without success I hope someone out there might be able to assist
I have had great results and love the grid when using inline html. The problem for me is that doing it that way will result in horrible maintenance but just so other people that may be viewing this thread can see it works and works so well its amazing THANK YOU DOJO PEOPLE!!
<title>Test dojox.Grid PHP on demand</title>
<style type="text/css">
@import "/ext/js/dj102/dojox/grid/_grid/tundraGrid.css";
@import "/ext/js/dj102/dijit/themes/tundra/tundra.css";
@import "/ext/js/dj102/dojo/resources/dojo.css";
#grid {border: 2px solid #333;width: 49em;height: 30em;}
</style>
<script type="text/javascript" src="/ext/js/dj102/dojo/dojo.js" djConfig="parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dojox.data.QueryReadStore");
dojo.require("dojox.grid.Grid");
dojo.require("dojox.grid._data.model");
var layoutitems = [{
cells: [[
{name: 'Title', width: "25em", field: "title"},
{name: 'Style', width: "10em", field: "style"},
{name: 'Finish', width: "10em", field: "finish"}
]]
}];
</script>
</head>
<body class="tundra">
<div dojoType="dojox.data.QueryReadStore" jsId="continentStore" url="dojogridresp.php" doClientPaging="false">
<div dojoType="dojox.grid.data.DojoData" jsId="dataModel2" rowsPerPage="20" store="continentStore" query="{ id : '*' }">
<div id="grid" dojoType="dojox.Grid" elasticView="2" model="dataModel2" structure="layoutitems"> </div>
<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 7000;
</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>
</body>
AND THE PHP dojogridresp.php
<? // Sort out some constants define("CONFIG_DATABASE_NAME" ,"somedb"); define("CONFIG_DATABASE_USER" ,"someuser"); define("CONFIG_DATABASE_PASSWORD" ,"somepassword"); define("CONFIG_DATABASE_HOST" ,"localhost"); // A basic function to connect to the databse function dbconnect() { $db = mysql_connect(CONFIG_DATABASE_HOST, CONFIG_DATABASE_USER, CONFIG_DATABASE_PASSWORD); if (!$db) { echo "function dbconnect() failed at mysql_connect() " . mysql_error() ; exit; } mysql_select_db(CONFIG_DATABASE_NAME, $db); return $db; } // Read the incoming get / post vars from the dojo widgets requestRows method (names are determined by your own scripts) $start = $_REQUEST["start"]; $count = $_REQUEST["count"]; $query = $_REQUEST["id"]; $sort = $_REQUEST["sort"]; // Make some simple decisions on what they mean // are we looking for specific titles ? if ($query !="*") $filter = " WHERE title LIKE '" . $query . "%' "; else $filter = " WHERE title > ' ' "; // How are we going to sort the results ? if ($sort == 2) $order = "style"; elseif ($sort == 3) $order = "finish"; elseif ($sort == -1) $order = "title DESC"; elseif ($sort == -2) $order = "style DESC"; elseif ($sort == -3) $order = "finish DESC"; else $order = "title"; // Finally lets do it.. connect to the db $db = dbconnect(); // Form the squirrel query $sql = "SELECT title, style, finish, description FROM inventory " . $filter . " ORDER BY " . $order . " LIMIT " . $count . " OFFSET " . $start; // build the response data in an array (its easy that way cause PHP can transform arrays to JSON in a one liner) $i =0; $result = mysql_query($sql,$db); while ($row = mysql_fetch_array($result)) { $node = array("title"=> $row["title"] , "style" => $row["style"], "finish" => $row["finish"]); $returnArray[$i] = $node; $i++; } //Send the data back to the dojo widget header("Content-Type", "text/json"); echo '/*'.json_encode(array('items'=>$returnArray)).'*/'; ?>with regards
Inheriting DojoData-model Requires Overriding Markup Factory
Sounds familiar... Check my comment above: Classes that inherit DojoData need to override method called markupFactory.
Would a real world example be possible ?
Hi Maine,
I had checked your info above before my initial posting. I have in my script above the following. This is how I understood to declare the class based on the information on declaring a class in the The book of Dojo.
dojo.declare("php.dynamicGridData", dojox.grid.data.DojoData, { markupFactory: function(args, node) { return new dojox.grid.data.DojoData(null, null, args); } });Thanks
Override markup factory to return instance of your own class
A little confusion here - I meant to provide that markupFactory as sample that should be overridden to return an instance of your own class. So, in your case the markupFactory would be:
{
markupFactory: function(args, node)
{
return new php.dynamicGridData(null, null, args);
}
});
Hope this helps!
Plus, when looking at markup factory example on parser page it seems like the class is unnecessarily hard-coded in DojoData-class. The following markupFactory should do it for everyone (Haven't tested yet):
{
return new constructorFunction(null, null, params);
}
Thank you
Maine,
Thanks very much your a star. They both seemed to work !!
I also want to thank scrollpane you both put some very useful examples up. I now have a working editable data grid (its got its issues) but very cool. I will be happy to paste the scripts but don't want to clutter this page with too much. I wonder if it is possible to replace my old posts
Thanks again everybody
You can edit your posts
Glad that I could help. It's a good practice to share your working code here for the others. You can always edit your posts.
Hi Maine regarding editing content
It seems once someone has replied to your post the edit feature goes away. Oh well, will just have to clutter all these pages with my stuff.
Have a great weekend
Didn't get it...
Hi!
I've read all the thread but I'm sure there's something I don't get because my grids pagination does not work. When I use the scrollbar I can see in Firebug that the QueryReadStore store is fetching new records from the DB, but they aren't passed to the DojoData instance so the grid never show them. Here's my code (it's a little messy because of all the dirty thing I'm trying with it!):
// 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") } var dataurl = "/cake/products/dojoindex/" + (this.sortColumn || 'title') +"/asc/"+row+"/"+inCount || this.rowsPerPage; this.store.url = dataurl; this.store.fetch(params); console.log("in getRowCount()"); // should return total count (fetch from server), not "rowsPerPage" return 300; console.log("in sort()"); // clears old data to force loading of new, then requests new rows this.clearData(); this.sortColumn = colIndex; this.requestRows(); console.log("in setData()"); // edited not to reset the store this.data = []; this.allChange(); console.log("in canSort()"); // always true return true;The URL change in the requestRows method is there because I'm working with CakePHP and it uses this nice URLs to pass parameters to the server methods. Could be that the problem?
Thanks a lot in advance!!
Malformatted data?
Check Getting grid to work with dojo.data and Dojo Grid+CakePHP.
Hi Maine!Sorry I didn't
Hi Maine!
Sorry I didn't reply earlier. I read those two threads some time ago (in fact, I opened the second one!), and I know my data is well formatted because the grid loads it with no problem. The problem is that it only loads the data returned by the very first query, not all the rest. Firebug tells me that when I scroll down dojo asks for more data, and that it's loaded right in the store, but not in the grid.
Thanks a lot anyway!!
Update to my data grid using php and mysql
So I have been working with this for a couple of days now and seem to have got an editable data grid working on a LAMP setup. A special thanks to Maine for answering all my silly questions. I must warn you all I am probably like the blind leading the blind as far as dojo and javascript so you may see things I have done that work but I probably should not have done or there are more syntactically correct ways in dojo / javascript. Of course I had to jump head first into dojo using the grid control as my starting point..... There are some strange things happening for instance I have two combobox editors that on occasion just stop accessing the datastore. I have not done any test cases with this code just clicked around here or there and for the most part it works. I hope that someone might find this useful and if anyone has any comments or suggestions that would be great !!
So there are four files. Two are extensions of existing classes which i placed in a php subdirectory off the dojo stuff
dojox.grid.editors.ComboBox newComboBox.js
dojox.grid.data.DojoData newGridData.js
The main html file dojogrid.html
The PHP stuff dojogridresp.php
newComboBox.js
dojo.declare("php.newComboBox", dojox.grid.editors.ComboBox,
{
getEditorProps: function(inDatum)
{
if(typeof this.cell.options != "undefined")
{
var items=[];
dojo.forEach(this.cell.options, function(o) {items.push({name: o, value: o});});
var store = new dojo.data.ItemFileReadStore({data: {identifier:"name", items: items}});
this.cell.editor.editorClass.superclass.searchAttr = "name";
}
else //if no options set, check for a store
{
if(typeof this.cell.store != "undefined") //check that a store is specified
{
var store = this.cell.store;
this.cell.editor.editorClass.superclass.searchAttr = (typeof this.cell.searchAttr != "undefined") ? this.cell.searchAttr : "name"; //set the searchAttr based on user specified searchAttr if set; otherwise, default to name
}
else
{
console.error("Error setting store for dojox.grid.editors.ComboBox...");
}
}
return dojo.mixin({}, this.cell.editorProps||{}, { value: inDatum, store: store});
}
}
);
newGridData.js
dojo.declare("php.newGridData", dojox.grid.data.DojoData,
{
testparam: "", // default parameter from markup
markupFactory: function(params, domNode, constructorFunction) // Used to create the object instance rather than the constructor
{
var instance = new php.newGridData(null, null, params);
// Do any initialisation here
return instance;
},
requestRows: function (inRowIndex, inCount) // Fetch rows of the grid
{ // Return from the server is in JSON format
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,
onComplete: dojo.hitch(this, "processRows")
}
this.store.fetch(params);
},
getRowCount: function () // Returns the total number of records for the query
{ // The return value is a simple number
var qtype = ""; // As you can see I have no idea of what I am doing.
var qvalue = ""; // I think this.query is some kind of assosiative array but dont know any dojo methods to access it cleanly
var serverURL = this.store.url + "?action=getTotalRowCount&";
for (ent in this.query)
{
qtype = ent;
qvalue = this.query[ent];
}
serverURL +=qtype + "=" + qvalue;
dojo.xhrGet(
{
preventCache: true,
url: serverURL ,
handleAs: "text",
sync: true,
timeout: 5000,
load: function(response, ioArgs) // The LOAD function will be called on a successful response.
{
rowCount = parseInt(response);
},
error: function(response, ioArgs) // The ERROR function will be called in an error case.
{
console.error("HTTP status code: ", ioArgs.xhr.status);
return response;
}
}
);
return rowCount;
},
setData: function(inData) // Called at startup to Initialize the grids data
{
this.data = [];
this.allChange();
},
sort: function(colIndex) // Called when user clicks on grid header to change sort order
{
this.clearData(); // Clear the grids data
this.sortColumn = colIndex; // set the sort order
this.requestRows(); // fetch the new data
},
canSort: function () // Determines if clicking on the grid header will resort the data
{
return true;
},
} );
dojogrid.html
Test a Dojo Grid @import "/ext/js/dj102/dojox/grid/_grid/tundraGrid.css"; @import "/ext/js/dj102/dijit/themes/tundra/tundra.css"; @import "/ext/js/dj102/dojo/resources/dojo.css"; #DataGrid {width: 59em;height: 28em;} dojo.require("dojo.parser"); dojo.require("dojox.data.QueryReadStore"); dojo.require("dijit.form.ComboBox"); dojo.require("dojox.grid.Grid"); dojo.require("dojox.grid._data.model"); dojo.require("dojox.grid._data.editors"); dojo.require("dojox.grid._data.dijitEditors"); dojo.require("php.newGridData"); dojo.require("php.newComboBox"); dojo.require("dojo.data.ItemFileReadStore"); var isGridInserting = false; // Global to track the insertion state of the grid. Yep its a kludge I am sure there are better ways var recIdToDelete = 0; // global that stores the recid of the row we are going to delete (issue with me or Dojo) var theGrid; // Reference to the dojo grid widget var theForm; // Reference to a hidden html form var styleStore = new dojo.data.ItemFileReadStore({jsId: 'styleStore', url: "/dojogridresp.php?action=getStyleTypes"}); var finishStore = new dojo.data.ItemFileReadStore({jsId: 'finishStore', url: "/dojogridresp.php?action=getFinishTypes"}); var gridLayout = [{ cells: [[ {name: 'Title' , width: "25em", styles: 'text-align: left;', field: "title", editor: dojox.grid.editors.Dijit, trim: true, propercase: true, required: true} , {name: 'Style', width: "15em", styles: 'text-align: left;', field: "style", editor: php.newComboBox, searchAttr: "style", store: styleStore }, {name: 'Finish', width: "15em", styles: 'text-align: left;', field: "finish", editor: php.newComboBox, searchAttr: "finish", store: finishStore}, {name: 'RecId', width: "0px", styles: 'display:none;', field: "id"} ]] }]; // Fired by button new item function addRow() { theGrid.addRow([ "Unknown", "", "", 0]); // The defaults dont get set like this. Needs some more investigation. } // Fired by button delete selected item function deleteRow() { var theSelectedRow = theGrid.selection.getSelected(); // An array of rowid items if (theSelectedRow.length > 0 ) { if (theSelectedRow.length > 1 ) { alert("you have selected several items to delete. I cant do that !"); // would use sql in() if going to put it to use } else { recIdToDelete = theGrid.model.data[theSelectedRow].id if (confirm("Delete item " + theGrid.model.data[theSelectedRow].title + " recId=" + recIdToDelete) ) theGrid.removeSelectedRows(); theGrid.refresh(); } } else { alert("Please select an item to delete first"); } } /* Fired by button Set Filter simple test to see if we can requery the sql table with a where clause. Appears to work however the grids internal structure on occasion seems to get destroyed as get several errors when trying to edit a field on the grid such as i has no properties (VirtualGrid.js line 351) */ function changeFilter() { var filterData = dojo.byId("filter"); theGrid.model.query.filter= filterData.value; theGrid.model.clearData(); theGrid.model.requestRows(); } // blocking call to post the form to the server and wait for response in JSON function submitForm() { retval = ""; dojo.xhrPost( { url: theGrid.model.store.url, form: 'updateFieldForm', handleAs: "json", sync: true, // block until done or timeout timeout: 5000, load: function(response) { retval = response;}, error: function (error) { retval = error; } } ); return retval; } function processResponse(response) { isGridInserting = false; recIdToDelete=0; if (response.name == "Error") // Problem at the transport level { // now we have to do something to sort out the problem and not make the user have to retype the data and or resync the table // but for today we will let the user curse us alert ("opps something went wrong " + response.message); } else { if (response.result.error) // Problem from the app { alert("opps could not do that " + response.result.message); } else { if (response.result.action == "insertRow") // We need to populate the grid with the recid of the newly created row theGrid.model.data[response.result.rowId].id = response.result.recId; //alert(response.result.message); // Silence is golden } } theGrid.update(); } // Fired when a key such as the enter key has been pressed on a cell being edited // It is probably wise to check if the data on the server has changed since the grid might contain stale data. ahh too much trouble for now. function dataGridApplyCellEdit(value,rowIndex,columnIndex) { theForm.dataFieldName.value = theGrid.layout.cells[columnIndex].field; theForm.dataFieldValue.value = value; theForm.recId.value = theGrid.model.data[rowIndex].id; theForm.rowId.value = rowIndex; if (isGridInserting) theForm.action.value = "insertRow"; else theForm.action.value = "updateField"; response= submitForm(); processResponse(response); } //Fired when a row is deleted // Maybe its just me but rowIndex seems to return the new row that has taken the place of the deleted row. I wonder if there is a beforeRemoval event ?? function dataGridDeleting(rowIndex) { theForm.recId.value = recIdToDelete; theForm.action.value="deleteRow"; isGridInserting = false; response = submitForm(); processResponse(response); } // Fired when a row is intially created just keeping track of the editor state. I am sure the grid must have this in it somewhere function dataGridInserting(rowIndex) { isGridInserting = true; } // Fired when the editing of the cell is cancelled just keeping track of the editor state. I am sure the grid must have this in it somewhere function dataGridCancelCellEdit(rowIndex) { isGridInserting = false; } // Hook up to some events of the grid. seems I should be using observers but cannot find any concrete examples so this seems to work !! function init() { theGrid = dijit.byId("DataGrid"); theForm = dojo.byId("updateFieldForm"); dojo.connect(theGrid,'onApplyCellEdit',dataGridApplyCellEdit); dojo.connect(theGrid.model,'insertion',dataGridInserting); dojo.connect(theGrid.model,'removal',dataGridDeleting); dojo.connect(theGrid,'onCancelEdit',dataGridCancelCellEdit); } dojo.addOnLoad(init);
- Login or register to post comments
Submitted by kchopein_2 on Mon, 01/07/2008 - 16:43.
- Login or register to post comments
Submitted by rhonda on Tue, 01/08/2008 - 00:09.
- Login or register to post comments
Submitted by kchopein_2 on Tue, 01/08/2008 - 09:36.
- Login or register to post comments
Submitted by rhonda on Tue, 01/08/2008 - 14:55.
- Login or register to post comments
Submitted by kchopein_2 on Tue, 01/08/2008 - 16:16.
- Login or register to post comments
Submitted by rhonda on Tue, 01/08/2008 - 16:56.
- Login or register to post comments
Submitted by Maine on Tue, 01/08/2008 - 20:14.
<div id="grid" jsId="grid" dojoType="dojox.Grid" elasticView="2" rowsPerPage="15" model="model" structure="structure"></div>
- Login or register to post comments
Submitted by rhonda on Tue, 01/08/2008 - 23:28.
- Login or register to post comments
Submitted by lance.spellman on Sat, 01/26/2008 - 14:52.
- Login or register to post comments
Submitted by kesling on Mon, 01/21/2008 - 17:23.
New Item Delete Selected Item Set Filter
One silly question...
Hi again!!
I'm trying to write a store class (derivative of the QueryReadStore) to work with CakePHP, witch uses non-standard URLs. The question is that my new store can retrieve data from server, but I'm not sure what it should do with them. Explanation and example: it asks for the first page (let's say 5 records) and works ok. Now it asks for the second page (records 6 to 10, both included). Now I see that the records of the first page are replaced with this new ones in the store. Is this what it should do? If yes, I guess that the model should keep all the records so it's not necessary get them from the server again. I'm a little confused with all these things (all new for me...)
Thanks in advance!!
Not a silly question
My observations are at least with the code I presented above. that the store, at least on the first pass reads ahead so when the page starts up I see
GET http://192.168.0.3/dojogridresp.php?start=0&count=20&sort=&filter=
GET http://192.168.0.3/dojogridresp.php?start=20&count=20&sort=&filter=
Seems to go ahead and pull two lots of data. That might be an issue with my code not had time to figure it out.
The rest of it seems to be as you say. As you page through your grid you will see GET requests for pages that have not yet been pulled from the server. Going straight back to the top will not result in a reload of the data.
If you note however in my code above for instance in newGridData.js when you issue a this.clearData() then you have removed all the data from the store so it has to go back and get it all again which seems to make sense.
Hope that helps
(Partially) Solved.
Hi!
Finally I've been able to get it working. It was my fault: I wasn't setting the doClientPaging parameter to false. Now I understand (I think ;-P). If you set the rowsPerPage parameter to, let's say, 3, you'll see that it does more requests so it gets more or less the same number of rows, witch varies automatically depending on the size of the grid.
Now the new problem is to sort. There's something wrong with the colIndex variable in the sort method in the xyz.data.ServerGridData class. Any ideas?
This (the grid and the whole Dojo thing) is definitively great!! I love it!!
Sorting using your example of 01/04/2008 12:09
clicking on a col header of the data grid will result in the colIndex arg having the value of the index (starting from 1) so if you have the following cols
firstname = 1
lastname = 2
If you click on the same header again it will reverse the order so you will get for descending
firstname = -1
lastname = -2
It's upto your webserver to return the data in the order you want it displayed. The sort does not actually happen within the grid itself.
Look in firebug you should see something like
GET http://192.168.0.3/dojogridresp.php?start=20&count=20&sort=2&filter=
Take a look at my example dojogrid.php at the function processList() specifically at $sort arg
Hope that helps
The sort method was wrong.
Hi rhonda!
Now I understand how the whole thing works, and it's wonderful (I love this job!). Anyway there's something something more that's wrong in the xyz.data.ServerGridData class wrote by scrollpane: the sort method. This is how he/she wrote it:
sort: function() { this.clearData(); this.sortColumn = colIndex; this.requestRows(); },but this doesn't work (at least for me). Here's my proposal:
sort: function(inColIndex) { this.clearData(); this.sortColumn = inColIndex; this.requestRows(); },This way it works fine (at least for me).
Thanks a lot for you precious help!!
Opps forgot to mention that
Glad to be able to help. Yes, I should have mentioned that the argument colIndex was missing from that example. Glad to see you figured it out
Grid doesn't know how to look ahead - problem with rowsPerPage
I dug into this as the behavior seemed slightly strange - there's no functionality for loading rows ahead (even though maybe there should?). It turned out that the grid (VirtualGrid) has it's own "rowsPerPage" variable independent from the model, and the default value for that is 25. When the model and the grid have have different values for "rowsPerPage", it leads to described behavior.
A simple solution is to add rowsPerPage attribute for grid declaration:
Thanks again Maine
I noticed the number 25 in the DOM while debugging a seperate issue and found it a little weird. As always THANKS AGAIN for a quick and clear answer. That puts that quirk to rest ! Read ahead might be a good thing if you had a generic grid where you wanted it to figure out the optimum fetch strategy, even then I wonder if that might not end up being a complex and somewhat hit or miss algorithm to implement.
Ideally you would be looking at the totalRows the grid might have to deliver and the average transit time across the network for each page before you could determine such ? You might need the user to be paging through the data a few times before you could derive a semi accurate sample...
I am interested in the idea of a noCache type of scenario ( I am sure I read it somewhere on this site but did not keep track of it) where by the datastore only holds whats in view or have a setting to purge the datastore after it reaches a certain size. Or even purge the x amount of oldest reads from the datastore..... The rational behind that is I am guessing that the benefit of the queryReadStore eventually is outweighed by its memory consumption on the client. What would happen if a user paged through a grid with 50,000 rows!! It might be ok on the average desktop but on a mobile device with limited resources would that not cause some real issues with the queryReadStore holding all of the data within the client (browser)?
Food for thought or maybe just fodder :-)
Are you sure the data is being cached?
It's been a month now, but I remember digging in with firebug to see just what exactly the store was keeping in it, and if I recall correctly, with each call it makes to the server it wipes the current dataset out and starts over. So I think noCache is the DEFAULT behavior and if you wanted it to cache you'd have to overwrite code in QueryReadStore.
However, I could be wrong, I've slept since. A quick scan of QueryReadStore.js shows that in the fetch function there doesn't appear to be a clearing of the current store, and _fetchHandler is certainly doing a .push call for adding new items into the store.
I'll be debugging some other things in the next few days, I'll take a look at this as well as I agree with you about your concerns. I have a 40K entry set that could get ugly if the store is retaining it all.
Lance Spellman
Having trouble with programmatic approach, declaritive works
I have been working on subclassing QueryReadStore to use a JsonService for data retrieval. I have a declarative example that works, but I have not been able to get the programmatic version working (no matter how many times I read this thread :).
I can see the JsonService being called for both approaches but the Programmatic version does not update the Grid after the first fetch.
Any help is appreciated:
Here is my subclass of the QueryReadStore, JSONRPCQueryReadStore:
dojo.provide("emory.data.JSONRPCQueryReadStore_04"); dojo.require("dojox.data.QueryReadStore"); dojo.require("dojo.rpc.JsonService") dojo.declare("emory.data.JSONRPCQueryReadStore", dojox.data.QueryReadStore, { /* // Extends dojox.data.QueryReadStore to use dojo.rpc.JsonService // for retrieving Items. // The result of the Json Service call is expected to be // an array of items. */ constructor: function(/* Object */ params){ console.log("constructor"); this.service = new dojo.rpc.JsonService("/CourierApp/RPCAdapter/jsonrpc/EventLookup"); }, /* // overrides superclass method and calls getJSONService // to call the JsonService and add Callbacks for populating Items */ _fetchItems: function(request, fetchHandler, errorHandler){ var serverQuery = typeof request["serverQuery"]=="undefined" ? request.query : request.serverQuery; //Need to add start and count if(!this.doClientPaging){ serverQuery = serverQuery||{}; serverQuery.start = request.start?request.start:0; // Count might not be sent if not given. if (request.count) { serverQuery.count = request.count; } } // Compare the last query and the current query by simply json-encoding them, // so we dont have to do any deep object compare ... is there some dojo.areObjectsEqual()??? if(this.doClientPaging && this._lastServerQuery!==null && dojo.toJson(serverQuery)==dojo.toJson(this._lastServerQuery) ){ fetchHandler(this._items, request); }else{ /* // call getJSONService(request) to invoke JsonService */ var serviceHandler = this.getJSONService(request) /* // Add Callbacks to JsonService // Expect service result to contain array of items. */ serviceHandler.addCallback(dojo.hitch(this, function(result){ console.log("serviceHandler.callback, logging result"); console.log(result); this._items = []; // Store a ref to "this" in each item, so we can simply check if a