I have been working on extending QueryReadStore to use a dojo.rpc.JsonService for data retrieval and wanted to provide this information for discussion.
Goals:
1. Have Grid with scrollable paging as described here: http://dojotoolkit.org/book/dojo-book-0-9-1-0/part-2-dijit-dojo-widget-l...
2. Retrieve data via JSON-RPC from services implemented with IBM's RPCAdapter, as described here: http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/index.jsp?topic=/c...
Approach:
1. Extend QueryReadStore to retrieve data via dojo.rpc.JsonService
Rational:
I choose to extend QueryReadStore because because It seemed like the most logical choice for me. It has all of the capabilities to support scrollable paging (as described above) and few changes were needed to add the call to the JsonService.
Here is my extension of QueryReadStore:
dojo.provide("emory.data.JSONRPCQueryReadStore_05");
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 from an IBM RPCAdapter.
*/
/*
// creates service (dojo.rpc.JsonService) from params.url
// and introspects the serviceMethod to build array of argument
// names for invoking serviceMethod
// All of this only needs to be done once so constructor makes good choice.
*/
constructor: function(/* Object */ params){
// create dojo.rpc.JsonService
// JsonService is called to get SMD
this.service = new dojo.rpc.JsonService(params.url);
// set service method that will be invoked
this.serviceMethod = params.serviceMethod;
// set service method argument names
this.serviceArgNames = []
// introspect service method to get argument names
var smdMethodArray = this.service.smd.methods
for (var i in smdMethodArray) {
var smdMethod = smdMethodArray[i]
if (smdMethod.name == this.serviceMethod) {
var paramArray = smdMethod.parameters
for (var j in paramArray) {
this.serviceArgNames[j] = paramArray[j].name;
}
break;
}
}
},
/*
// overrides superclass method to call JsonService for fetching 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{
// invokeJSONService(request)
var serviceHandler = this.invokeJSONService(request)
// Add Callbacks to JsonService
serviceHandler.addCallback(dojo.hitch(this, function(result){
this._items = [];
// ** customized data translation **
// Modified to work with Json result expected from IBM RPCAdapter.
// Result returned is very similar to the format of items but
// different enough I had to make this change.
for(var i in result){
this._items.push({i:result[i], r:this});
}
// The identifier is not provided in the result from RPCAdapter
// May address this later, setting to result.identifier for now.
var identifier = result.identifier;
this._itemsByIdentity = {};
if(identifier){
this._identifier = identifier;
for(i = 0; i < this._items.length; ++i){
var item = this._items[i].i;
var identity = item[identifier];
if(!this._itemsByIdentity[identity]){
this._itemsByIdentity[identity] = item;
}else{
throw new Error("dojo.data.QueryReadStore: The json data as specified by: [" + this._url + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]");
}
}
}else{
this._identifier = Number;
for(i = 0; i < this._items.length; ++i){
this._items[i].n = i;
}
}
// TODO actually we should do the same as dojo.data.ItemFileReadStore._getItemsFromLoadedData() to sanitize
// (does it really sanititze them) and store the data optimal. should we? for security reasons???
fetchHandler(this._items, request);
}));
serviceHandler.addErrback(function(error){
errorHandler(error, request);
});
// Generate the hash using the time in milliseconds and a randon number.
// Since Math.randon() returns something like: 0.23453463, we just remove the "0."
// probably just for esthetic reasons :-).
this.lastRequestHash = new Date().getTime()+"-"+String(Math.random()).substring(2);
this._lastServerQuery = serverQuery;
}
},
/*
// invokes service by retrieving serviceMethod parameters from the serverQuery by name
*/
invokeJSONService: function(request){
// hold serviceMethod arguments in array
var serviceArgValues = [];
// get serviceMethod arguments from serverQuery by name defined in SMD
for (var i in this.serviceArgNames) {
serviceArgValues[i] = request.serverQuery[this.serviceArgNames[i]];
}
// call service method
return this.service[this.serviceMethod].apply(this, serviceArgValues);
}
});Here is an example of a new instance being created programmatically.
Test dojox.Grid Basic
@import "dojox/grid/_grid/tundraGrid.css";
@import "dijit/themes/tundra/tundra.css";
@import "dojo/resources/dojo.css"
body {
font-size: 0.9em;
font-family: Geneva, Arial, Helvetica, sans-serif;
}
.heading {
font-weight: bold;
padding-bottom: 0.25em;
}
#grid, #grid2 {
width: 65em;
height: 25em;
padding: 1px;
}
Query Read Store Paging Grid
// 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);
// should return total count (fetch from server), not "rowsPerPage"
return 15000;
// clears old data to force loading of new, then requests new rows
this.clearData();
this.sortColumn = colIndex;
this.requestRows();
// edited not to reset the store
this.data = [];
this.allChange();
// always true
return true;
var grid = dijit.byId('grid');
// Debug the Event
console.debug(event);
// Get the Row
console.debug("getting rowIndex(" + event.rowIndex + ")");
thisRow = myModel.getRow(event.rowIndex);
console.debug(thisRow);
// Get the EventId from the Row Object
//eventId = thisRow.eventId;
//console.debug('eventId: ' + eventId);
I would appreciate any feedback on this approach.
Thanks,
Tom

Store abstraction not necessary
So, let me start by repeating what I already mentioned on grid server side sorting thread: it can be done without a store. This level of abstraction is not necessary for the grid - the model alone can serve the grid. So, unless you're planning to use the same data to populate a tree or a combobox, you should probably pick dojox.grid.data.Table/Dynamic and build your solution on top of that one. To elaborate: grid's features, like virtual scrolling, are independent from the underlying data. To ease the job of using different kind of data sources there are few abstraction layers: model and optionally store. Some models fetch their data directly from the servers, some connect to a data store that then fetches the data. As an example of a model that doesn't use a store see dojox.grid.data.DbTable.
The grid scrolling 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). In a model implementation that doesn't use a store, requestRows takes care of the heavy lifting.
This is not to say that using a store would be a bad thing - just to illustrate that it adds another layer. Sometimes it's really useful, though.
I'm not familiar with dojo.rpc.JsonService, so I don't know what are its advantages. I'll try to read up.
Distinction between Model and Store
Thanks for the feedback.
I think discussing this more will help me better understand the distinction between model and store.
My "understanding":
Grid => provides the UI behavior.
Model => provides data definition (data abstraction).
Store => provides the data access layer (data provider).
Should I change my thinking about this?
Thanks,
Tom
Unclear roles of model and store
That's a good question you're asking. The roles of grid/model/store seem to be unclear for many of us. I hope that one of the grid developers would join the discussion to clarify the underlying ideas. There certainly is some unnecessary complexity. To my understanding DojoData-model was developed as an adapter to connect model and store interfaces. However, for an adapter, it's a heavyweight component as it duplicates store information.
Yep, I got a little twisted
Yep, I got a little twisted on the implementation when using a read store, because of a defect (now fixed #5555) in write store usage in the Grid that I had to work around.
My understanding all along has been:
Store: Conceptually, a local copy of the full dataset, with rows retrievable by identity or query.
Model: A subset (may be in entirety) *copy* of the store, resulting from a query against the store.
Grid: The widget that the user sees, basically the table headers and rows/cells. The visible rows are "rendered" from the model data. Visible row # 22 corresponds to Model row #22.
Sorting a column keeps the visible rows and Model rows in sync. Model data may have more columns and be in a different column order than the Grid's visible columns. The Model's get/setDatum's use of columnIndex uses the Model's columnIndex, not the Grid's visible columnIndex. Grid row/cell events use the Model/visible rowIndex and the Grid's visible columnIndex/cellIndex (not the Model's).
After Jan 15/Ticket #5555, a change to the Model data via the UI or setDatum is reflected in both the Model and in the Store. Also, a direct change in the Store is reflected in the Model. And a Sort now sorts the store, with the Model and Grid rows reflecting that Sort.
As I understand it, read stores may no longer retain model changes, since now the model and store must be kept in sync, and a read store can/should not be modified. Prob not a problem, just use a write store.
DataGrid issue
Hi I've tried this store with my custom jsonrpc service. Everything works fine (data loads from server, is in store etc), but the DataGrid doesn't render it, any suggestions ?
CODE:
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<style type="text/css">
@import "./scripts/dojoRoot/dijit/themes/soria/soria.css";
@import "./scripts/dojoRoot/dojo/resources/dojo.css";
@import "./scripts/dojoRoot/dojox/grid/_grid/soriaGrid.css";
@import "./scripts/dojoRoot/dojox/grid/resources/soriaGrid.css";
@import "./styles/main.css";
</style>
<script type="text/javascript" src="./scripts/dojoRoot/dojo/dojo.js"></script>
<script type="text/javascript">
function load(){
try{
dojo.require("test.testStore");
dojo.require("dojox.grid.DataGrid");
ts = new test.testStore({url: "http://localhost/MaWebRPC/index/jsonrpc", serviceMethod: 'getCardsByDivId',identifier: 'ID' });
var layout = [
{"name":"Identyfikator karty","field":"ID","ids":""},{"name":"Numer inwentarzowy","field":"NUMINW","ids":""},
{"name":"Numer ksiegi wp\u0142ywu","field":"KW","ids":""},{"name":"Numer negatywu","field":"NUMNEG","ids":""},
{"name":"Nazwa","field":"NAZWA","ids":""},{"name":"Tytu\u0142","field":"TYTUL","ids":""},
{"name":"Dzia\u0142","field":"DZIAL","ids":""},{"name":"Materia\u0142","field":"MATER","ids":""},
{"name":"Technika","field":"TECHN","ids":""},{"name":"Opis","field":"OPIS","ids":""},
{"name":"Dane dodatkowe","field":"DANEDODATKOWE","ids":""},{"name":"Numer karty ewidencyjnej","field":"NUMEWID","ids":""},
{"name":"Bibliografia","field":"BIBL","ids":""},{"name":"Komentarz","field":"KOMENT","ids":""},
{"name":"Data opracowania","field":"POWSTANIE\/OPRACOWANIE\/DATASTR","ids":""},{"name":"Opracowa\u0142","field":"POWSTANIE\/OPRACOWANIE\/OPRACOWAL","ids":""},
{"name":"Zapis wprowadzi\u0142","field":"POWSTANIE\/ZAPIS\/WPROW","ids":""},{"name":"Data zapisu","field":"POWSTANIE\/ZAPIS\/DATASTR","ids":""},
{"name":"Autor","field":"POWSTANIE\/AUTOR","ids":""},{"name":"Okres powstania","field":"POWSTANIE\/CZASPOWST\/OPIS","ids":""},
{"name":"Od","field":"POWSTANIE\/CZASPOWST\/ODNUM","ids":""},{"name":"Do","field":"POWSTANIE\/CZASPOWST\/DONUM","ids":""}
];
grid = new dojox.grid.DataGrid({
autoRender: true,
query: {divId: 12},
store: ts,
structure: layout,
style: "height: 500px; outline: 1px solid black",
},dojo.byId('ala'));
grid.startup();
grid.render();
} catch (e){
console.log(e);
}
}
</script>
</head>
<body onload="load()">
aaa
<div id="ala" >
</div>
</body>
and 'invokeJsonService' method slightly modified:
invokeJSONService: function(request){
// hold serviceMethod arguments in array
var serviceArgValues = [];
// get serviceMethod arguments from serverQuery by name defined in SMD
for (var i in this.serviceArgNames) {
serviceArgValues[i] = request.query[this.serviceArgNames[i]];
}
// call service method
return this.service[this.serviceMethod].apply(this, serviceArgValues);
}