I want to make use of Dojo DataStores in my current application. The JSON data in my application has a different structure from the JSON data I see in all the examples. I'm not able to change the structure of the data I receive.
From reading the documentation, I can't tell if I need to make a custom dataStore for for my data's structure or is there is some sort of mapping that I can pass into and existing store.
Here's the format of my data (meant for tabular layout)...
{
"data":{
"objects":[
{
"67":1,
"is_template":0,
"name":"blah blah",
"object_id":15088
},
{
"182":"the Other Thing",
"is_template":0,
"name":12345678901234567890123456789012345678901,
"282":"False",
"326":[
"Newark",
"San Francisco"
],
"object_id":653
},
{
"67":1,
"182":"the Other Thing",
"is_template":0,
"name":"hello",
"326":[
"Newark"
],
"object_id":12170
},
{
"67":1,
"is_template":0,
"name":"some other name",
"object_id":13335
},
{
"67":1,
"is_template":0,
"name":"some name",
"object_id":15313
}
],
"total_size":5,
"attr_cols":[
{
"name":"Column 1",
"is_multi":0,
"key":67
},
{
"name":"Column 3",
"is_multi":0,
"key":395
},
{
"name":"Column 2",
"is_multi":0,
"key":182
}
]
},
"error_message":null,
"error_code":0
}
"data":{
"objects":[
{
"67":1,
"is_template":0,
"name":"blah blah",
"object_id":15088
},
{
"182":"the Other Thing",
"is_template":0,
"name":12345678901234567890123456789012345678901,
"282":"False",
"326":[
"Newark",
"San Francisco"
],
"object_id":653
},
{
"67":1,
"182":"the Other Thing",
"is_template":0,
"name":"hello",
"326":[
"Newark"
],
"object_id":12170
},
{
"67":1,
"is_template":0,
"name":"some other name",
"object_id":13335
},
{
"67":1,
"is_template":0,
"name":"some name",
"object_id":15313
}
],
"total_size":5,
"attr_cols":[
{
"name":"Column 1",
"is_multi":0,
"key":67
},
{
"name":"Column 3",
"is_multi":0,
"key":395
},
{
"name":"Column 2",
"is_multi":0,
"key":182
}
]
},
"error_message":null,
"error_code":0
}
Thanks.

I have modified
I have modified ItemFileReadStore to include a "pass through" method called "formatRawData" that can be overridden. Your version of "formatRawData" can take your raw data and reformat it to "Item" format.
Re-written ItemFileReadStore is (modified lines are terminated with "//M.N."):
dojo.provide("mn.dojo.data.ItemFileReadStore"); //M.N. dojo.require("dojo.data.util.filter"); dojo.require("dojo.data.util.simpleFetch"); dojo.require("dojo.date.stamp"); dojo.declare("mn.dojo.data.ItemFileReadStore", null,{ //M.N. // summary: // The ItemFileReadStore implements the dojo.data.api.Read API and reads // data from JSON files that have contents in this format -- // { items: [ // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, // { name:'Fozzie Bear', wears:['hat', 'tie']}, // { name:'Miss Piggy', pets:'Foo-Foo'} // ]} // Note that it can also contain an 'identifer' property that specified which attribute on the items // in the array of items that acts as the unique identifier for that item. // constructor: function(/* Object */ keywordParameters){ // summary: constructor // keywordParameters: {url: String} // keywordParameters: {data: jsonObject} // keywordParameters: {typeMap: object) // The structure of the typeMap object is as follows: // { // type0: function || object, // type1: function || object, // ... // typeN: function || object // } // Where if it is a function, it is assumed to be an object constructor that takes the // value of _value as the initialization parameters. If it is an object, then it is assumed // to be an object of general form: // { // type: function, //constructor. // deserialize: function(value) //The function that parses the value and constructs the object defined by type appropriately. // } this._arrayOfAllItems = []; this._arrayOfTopLevelItems = []; this._loadFinished = false; this._jsonFileUrl = keywordParameters.url; this._jsonData = keywordParameters.data; this._datatypeMap = keywordParameters.typeMap || {}; if(!this._datatypeMap['Date']){ //If no default mapping for dates, then set this as default. //We use the dojo.date.stamp here because the ISO format is the 'dojo way' //of generically representing dates. this._datatypeMap['Date'] = { type: Date, deserialize: function(value){ return dojo.date.stamp.fromISOString(value); } }; } this._features = {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}; this._itemsByIdentity = null; this._storeRefPropName = "_S"; // Default name for the store reference to attach to every item. this._itemNumPropName = "_0"; // Default Item Id for isItem to attach to every item. this._rootItemPropName = "_RI"; // Default Item Id for isItem to attach to every item. this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset. this._queuedFetches = []; }, url: "", // use "" rather than undefined for the benefit of the parser (#3539) _assertIsItem: function(/* item */ item){ // summary: // This function tests whether the item passed in is indeed an item in the store. // item: // The item to test for being contained by the store. if(!this.isItem(item)){ throw new Error("dojo.data.ItemFileReadStore: a function was passed an item argument that was not an item"); } }, _assertIsAttribute: function(/* attribute-name-string */ attribute){ // summary: // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. // attribute: // The attribute to test for being contained by the store. if(typeof attribute !== "string"){ throw new Error("dojo.data.ItemFileReadStore: a function was passed an attribute argument that was not an attribute name string"); } }, getValue: function( /* item */ item, /* attribute-name-string */ attribute, /* value? */ defaultValue){ // summary: // See dojo.data.api.Read.getValue() var values = this.getValues(item, attribute); return (values.length > 0)?values[0]:defaultValue; // Anything }, getValues: function(/* item */ item, /* attribute-name-string */ attribute){ // summary: // See dojo.data.api.Read.getValues() this._assertIsItem(item); this._assertIsAttribute(attribute); return item[attribute] || []; // Array }, getAttributes: function(/* item */ item){ // summary: // See dojo.data.api.Read.getAttributes() this._assertIsItem(item); var attributes = []; for(var key in item){ // Save off only the real item attributes, not the special id marks for O(1) isItem. if((key !== this._storeRefPropName) && (key !== this._itemNumPropName) && (key !== this._rootItemPropName)){ attributes.push(key); } } return attributes; // Array }, hasAttribute: function( /* item */ item, /* attribute-name-string */ attribute) { // summary: // See dojo.data.api.Read.hasAttribute() return this.getValues(item, attribute).length > 0; }, containsValue: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ value){ // summary: // See dojo.data.api.Read.containsValue() var regexp = undefined; if(typeof value === "string"){ regexp = dojo.data.util.filter.patternToRegExp(value, false); } return this._containsValue(item, attribute, value, regexp); //boolean. }, _containsValue: function( /* item */ item, /* attribute-name-string */ attribute, /* anything */ value, /* RegExp?*/ regexp){ // summary: // Internal function for looking at the values contained by the item. // description: // Internal function for looking at the values contained by the item. This // function allows for denoting if the comparison should be case sensitive for // strings or not (for handling filtering cases where string case should not matter) // // item: // The data item to examine for attribute values. // attribute: // The attribute to inspect. // value: // The value to match. // regexp: // Optional regular expression generated off value if value was of string type to handle wildcarding. // If present and attribute values are string, then it can be used for comparison instead of 'value' var values = this.getValues(item, attribute); for(var i = 0; i < values.length; ++i){ var possibleValue = values[i]; if(typeof possibleValue === "string" && regexp){ return (possibleValue.match(regexp) !== null); }else{ //Non-string matching. if(value === possibleValue){ return true; // Boolean } } } return false; // Boolean }, isItem: function(/* anything */ something){ // summary: // See dojo.data.api.Read.isItem() if(something && something[this._storeRefPropName] === this){ if(this._arrayOfAllItems[something[this._itemNumPropName]] === something){ return true; } } return false; // Boolean }, isItemLoaded: function(/* anything */ something){ // summary: // See dojo.data.api.Read.isItemLoaded() return this.isItem(something); //boolean }, loadItem: function(/* object */ keywordArgs){ // summary: // See dojo.data.api.Read.loadItem() this._assertIsItem(keywordArgs.item); }, getFeatures: function(){ // summary: // See dojo.data.api.Read.getFeatures() return this._features; //Object }, getLabel: function(/* item */ item){ // summary: // See dojo.data.api.Read.getLabel() if(this._labelAttr && this.isItem(item)){ return this.getValue(item,this._labelAttr); //String } return undefined; //undefined }, getLabelAttributes: function(/* item */ item){ // summary: // See dojo.data.api.Read.getLabelAttributes() if(this._labelAttr){ return [this._labelAttr]; //array } return null; //null }, _fetchItems: function( /* Object */ keywordArgs, /* Function */ findCallback, /* Function */ errorCallback){ // summary: // See dojo.data.util.simpleFetch.fetch() var self = this; var filter = function(requestArgs, arrayOfItems){ var items = []; if(requestArgs.query){ var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the //same value for each item examined. Much more efficient. var regexpList = {}; for(var key in requestArgs.query){ var value = requestArgs.query[key]; if(typeof value === "string"){ regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase); } } for(var i = 0; i < arrayOfItems.length; ++i){ var match = true; var candidateItem = arrayOfItems[i]; if(candidateItem === null){ match = false; }else{ for(var key in requestArgs.query) { var value = requestArgs.query[key]; if (!self._containsValue(candidateItem, key, value, regexpList[key])){ match = false; } } } if(match){ items.push(candidateItem); } } findCallback(items, requestArgs); }else{ // We want a copy to pass back in case the parent wishes to sort the array. // We shouldn't allow resort of the internal list, so that multiple callers // can get lists and sort without affecting each other. We also need to // filter out any null values that have been left as a result of deleteItem() // calls in ItemFileWriteStore. for(var i = 0; i < arrayOfItems.length; ++i){ var item = arrayOfItems[i]; if(item !== null){ items.push(item); } } findCallback(items, requestArgs); } }; if(this._loadFinished){ filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); }else{ if(this._jsonFileUrl){ //If fetches come in before the loading has finished, but while //a load is in progress, we have to defer the fetching to be //invoked in the callback. if(this._loadInProgress){ this._queuedFetches.push({args: keywordArgs, filter: filter}); }else{ this._loadInProgress = true; var getArgs = { url: self._jsonFileUrl, handleAs: "json-comment-optional" }; var getHandler = dojo.xhrGet(getArgs); getHandler.addCallback(function(data){ try{ self._getItemsFromLoadedData(data); self._loadFinished = true; self._loadInProgress = false; filter(keywordArgs, self._getItemsArray(keywordArgs.queryOptions)); self._handleQueuedFetches(); }catch(e){ self._loadFinished = true; self._loadInProgress = false; errorCallback(e, keywordArgs); } }); getHandler.addErrback(function(error){ self._loadInProgress = false; errorCallback(error, keywordArgs); }); } }else if(this._jsonData){ try{ this._loadFinished = true; this._getItemsFromLoadedData(this._jsonData); this._jsonData = null; filter(keywordArgs, this._getItemsArray(keywordArgs.queryOptions)); }catch(e){ errorCallback(e, keywordArgs); } }else{ errorCallback(new Error("dojo.data.ItemFileReadStore: No JSON source data was provided as either URL or a nested Javascript object."), keywordArgs); } } }, _handleQueuedFetches: function(){ // summary: // Internal function to execute delayed request in the store. //Execute any deferred fetches now. if (this._queuedFetches.length > 0) { for(var i = 0; i < this._queuedFetches.length; i++){ var fData = this._queuedFetches[i]; var delayedQuery = fData.args; var delayedFilter = fData.filter; if(delayedFilter){ delayedFilter(delayedQuery, this._getItemsArray(delayedQuery.queryOptions)); }else{ this.fetchItemByIdentity(delayedQuery); } } this._queuedFetches = []; } }, _getItemsArray: function(/*object?*/queryOptions){ // summary: // Internal function to determine which list of items to search over. // queryOptions: The query options parameter, if any. if(queryOptions && queryOptions.deep) { return this._arrayOfAllItems; } return this._arrayOfTopLevelItems; }, close: function(/*dojo.data.api.Request || keywordArgs || null */ request){ // summary: // See dojo.data.api.Read.close() }, _getItemsFromLoadedData: function(/* Object */ dataObject){ // summary: // Function to parse the loaded data into item format and build the internal items array. // description: // Function to parse the loaded data into item format and build the internal items array. // // dataObject: // The JS data object containing the raw data to convery into item format. // // returns: array // Array of items in store item format. // First, we define a couple little utility functions... function valueIsAnItem(/* anything */ aValue){ // summary: // Given any sort of value that could be in the raw json data, // return true if we should interpret the value as being an // item itself, rather than a literal value or a reference. // examples: // false == valueIsAnItem("Kermit"); // false == valueIsAnItem(42); // false == valueIsAnItem(new Date()); // false == valueIsAnItem({_type:'Date', _value:'May 14, 1802'}); // false == valueIsAnItem({_reference:'Kermit'}); // true == valueIsAnItem({name:'Kermit', color:'green'}); // true == valueIsAnItem({iggy:'pop'}); // true == valueIsAnItem({foo:42}); var isItem = ( (aValue != null) && (typeof aValue == "object") && (!dojo.isArray(aValue)) && (!dojo.isFunction(aValue)) && (aValue.constructor == Object) && (typeof aValue._reference == "undefined") && (typeof aValue._type == "undefined") && (typeof aValue._value == "undefined") ); return isItem; } var self = this; function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ self._arrayOfAllItems.push(anItem); for(var attribute in anItem){ var valueForAttribute = anItem[attribute]; if(valueForAttribute){ if(dojo.isArray(valueForAttribute)){ var valueArray = valueForAttribute; for(var k = 0; k < valueArray.length; ++k){ var singleValue = valueArray[k]; if(valueIsAnItem(singleValue)){ addItemAndSubItemsToArrayOfAllItems(singleValue); } } }else{ if(valueIsAnItem(valueForAttribute)){ addItemAndSubItemsToArrayOfAllItems(valueForAttribute); } } } } } var newDataObject = this.formatRawData(dataObject); //M.N. this._labelAttr = dataObject.label; // We need to do some transformations to convert the data structure // that we read from the file into a format that will be convenient // to work with in memory. // Step 1: Walk through the object hierarchy and build a list of all items var i; var item; this._arrayOfAllItems = []; this._arrayOfTopLevelItems = newDataObject.items; //M.N. for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ item = this._arrayOfTopLevelItems[i]; addItemAndSubItemsToArrayOfAllItems(item); item[this._rootItemPropName]=true; } // Step 2: Walk through all the attribute values of all the items, // and replace single values with arrays. For example, we change this: // { name:'Miss Piggy', pets:'Foo-Foo'} // into this: // { name:['Miss Piggy'], pets:['Foo-Foo']} // // We also store the attribute names so we can validate our store // reference and item id special properties for the O(1) isItem var allAttributeNames = {}; var key; for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; for(key in item){ if (key !== this._rootItemPropName) { var value = item[key]; if(value !== null){ if(!dojo.isArray(value)){ item[key] = [value]; } }else{ item[key] = [null]; } } allAttributeNames[key]=key; } } // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName // This should go really fast, it will generally never even run the loop. while(allAttributeNames[this._storeRefPropName]){ this._storeRefPropName += "_"; } while(allAttributeNames[this._itemNumPropName]){ this._itemNumPropName += "_"; } // Step 4: Some data files specify an optional 'identifier', which is // the name of an attribute that holds the identity of each item. // If this data file specified an identifier attribute, then build a // hash table of items keyed by the identity of the items. var arrayOfValues; var identifier = dataObject.identifier; this._itemsByIdentity = {}; if(identifier){ this._features['dojo.data.api.Identity'] = identifier; for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; arrayOfValues = item[identifier]; var identity = arrayOfValues[0]; if(!this._itemsByIdentity[identity]){ this._itemsByIdentity[identity] = item; }else{ if(this._jsonFileUrl){ throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); }else if(this._jsonData){ throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); } } } }else{ this._features['dojo.data.api.Identity'] = Number; } // Step 5: Walk through all the items, and set each item's properties // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; item[this._storeRefPropName] = this; item[this._itemNumPropName] = i; } // Step 6: We walk through all the attribute values of all the items, // looking for type/value literals and item-references. // // We replace item-references with pointers to items. For example, we change: // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } // into this: // { name:['Kermit'], friends:[miss_piggy] } // (where miss_piggy is the object representing the 'Miss Piggy' item). // // We replace type/value pairs with typed-literals. For example, we change: // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'July 18, 1918'}] } // into this: // { name:['Kermit'], born:(new Date('July 18, 1918')) } // // We also generate the associate map for all items for the O(1) isItem function. for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } for(key in item){ arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] for(var j = 0; j < arrayOfValues.length; ++j) { value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} if(value !== null && typeof value == "object"){ if(value._type && value._value){ var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} if(!mappingObj){ throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); }else if(dojo.isFunction(mappingObj)){ arrayOfValues[j] = new mappingObj(value._value); }else if(dojo.isFunction(mappingObj.deserialize)){ arrayOfValues[j] = mappingObj.deserialize(value._value); }else{ throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); } } if(value._reference){ var referenceDescription = value._reference; // example: {name:'Miss Piggy'} if(dojo.isString(referenceDescription)){ // example: 'Miss Piggy' // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} arrayOfValues[j] = this._itemsByIdentity[referenceDescription]; }else{ // example: {name:'Miss Piggy'} // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } for(var k = 0; k < this._arrayOfAllItems.length; ++k){ var candidateItem = this._arrayOfAllItems[k]; var found = true; for(var refKey in referenceDescription){ if(candidateItem[refKey] != referenceDescription[refKey]){ found = false; } } if(found){ arrayOfValues[j] = candidateItem; } } } } } } } } }, getIdentity: function(/* item */ item){ // summary: // See dojo.data.api.Identity.getIdentity() var identifier = this._features['dojo.data.api.Identity']; if(identifier === Number){ return item[this._itemNumPropName]; // Number }else{ var arrayOfValues = item[identifier]; if(arrayOfValues){ return arrayOfValues[0]; // Object || String } } return null; // null }, fetchItemByIdentity: function(/* Object */ keywordArgs){ // summary: // See dojo.data.api.Identity.fetchItemByIdentity() // Hasn't loaded yet, we have to trigger the load. if(!this._loadFinished){ var self = this; if(this._jsonFileUrl){ if(this._loadInProgress){ this._queuedFetches.push({args: keywordArgs}); }else{ var getArgs = { url: self._jsonFileUrl, handleAs: "json-comment-optional" }; var getHandler = dojo.xhrGet(getArgs); getHandler.addCallback(function(data){ var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; try{ self._getItemsFromLoadedData(data); self._loadFinished = true; self._loadInProgress = false; var item = self._getItemByIdentity(keywordArgs.identity); if(keywordArgs.onItem){ keywordArgs.onItem.call(scope, item); } self._handleQueuedFetches(); }catch(error){ self._loadInProgress = false; if(keywordArgs.onError){ keywordArgs.onError.call(scope, error); } } }); getHandler.addErrback(function(error){ self._loadInProgress = false; if(keywordArgs.onError){ var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; keywordArgs.onError.call(scope, error); } }); } }else if(this._jsonData){ // Passed in data, no need to xhr. self._getItemsFromLoadedData(self._jsonData); self._jsonData = null; self._loadFinished = true; var item = self._getItemByIdentity(keywordArgs.identity); if(keywordArgs.onItem){ var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; keywordArgs.onItem.call(scope, item); } } }else{ // Already loaded. We can just look it up and call back. var item = this._getItemByIdentity(keywordArgs.identity); if(keywordArgs.onItem){ var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; keywordArgs.onItem.call(scope, item); } } }, _getItemByIdentity: function(/* Object */ identity){ // summary: // Internal function to look an item up by its identity map. var item = null; if(this._itemsByIdentity){ item = this._itemsByIdentity[identity]; if(item === undefined){ item = null; } }else{ this._arrayOfAllItems[identity]; } return item; // Object }, getIdentityAttributes: function(/* item */ item){ // summary: // See dojo.data.api.Identity.getIdentifierAttributes() var identifier = this._features['dojo.data.api.Identity']; if(identifier === Number){ // If (identifier === Number) it means getIdentity() just returns // an integer item-number for each item. The dojo.data.api.Identity // spec says we need to return null if the identity is not composed // of attributes return null; // null }else{ return [identifier]; // Array } }, _forceLoad: function(){ // summary: // Internal function to force a load of the store if it hasn't occurred yet. This is required // for specific functions to work properly. var self = this; if(this._jsonFileUrl){ var getArgs = { url: self._jsonFileUrl, handleAs: "json-comment-optional", sync: true }; var getHandler = dojo.xhrGet(getArgs); getHandler.addCallback(function(data){ try{ //Check to be sure there wasn't another load going on concurrently //So we don't clobber data that comes in on it. If there is a load going on //then do not save this data. It will potentially clobber current data. //We mainly wanted to sync/wait here. //TODO: Revisit the loading scheme of this store to improve multi-initial //request handling. if (self._loadInProgress !== true && !self._loadFinished) { self._getItemsFromLoadedData(data); self._loadFinished = true; } }catch(e){ console.log(e); throw e; } }); getHandler.addErrback(function(error){ throw error; }); }else if(this._jsonData){ self._getItemsFromLoadedData(self._jsonData); self._jsonData = null; self._loadFinished = true; } }, formatRawData: function(dataObject_in) { //M.N. // // Override this to reformat data into DataStore format!!! // return dataObject_in; //M.N. } //M.N. }); //Mix in the simple fetch implementation to this class. dojo.extend(mn.dojo.data.ItemFileReadStore,dojo.data.util.simpleFetch); //M.N.My webservce data format:
{"ResultSet": {"ResultLength":5, "Result":[ {"courseid":1500,"course":"BLS for the Healthcare Provider"}, {"courseid":1359,"course":"CHCS & AHLTA Provider"}, {"courseid":196,"course":"iSTAT 1 Analyzer"}, {"courseid":2177,"course":"M.E.D.I.C. Training, Skills Set 4"}, {"courseid":374,"course":"Pain Management and Patient Controlled Analgesia"} ]} }Desired ItemFileReadStore format:
{"items":[ {"courseid":1500,"course":"BLS for the Healthcare Provider"}, {"courseid":1359,"course":"CHCS & AHLTA Provider"}, {"courseid":196,"course":"iSTAT 1 Analyzer"}, {"courseid":2177,"course":"M.E.D.I.C. Training, Skills Set 4"}, {"courseid":374,"course":"Pain Management and Patient Controlled Analgesia"} ]}My program code:
var myFormatRawData = function(jsonData) { return {items:jsonData.ResultSet.Result}; } var Body_onload = function() { coursesStore = new dojo.mn.data.ItemFileReadStore({url:"https://courses.medical.bogus/tesservices/coursesclasses?type=courses&adminid=0startdate=10/10/2007&enddate=10/10/2007"}); coursesStore.formatRawData = myFormatRawData; //Override default formatRawData !!! coursesStore.fetch( {onComplete: gotList, onError: gotError} ); //, queryOptions:{ignoreCase:true, deep:true} }); // }I have tested this simple modification and it works. I suggested that these modifications be made to dojo.data.JSONItemDataStore a couple of months ago. So far, no response.
A better way to do this is
A better way to do this is to extend "dojo.data.ItemFileReadStore", add a new "pass-through" method called "formatRawData", and replace method "_getItemsFromLoadedData" with a re-written version that calls "formatRawData". You can then override "formatRawData" with a method that reformats your webservice data into "Items" format.
New version of "ItemFileReadStore":
dojo.provide("mn.dojo.data.ItemFileReadStore2"); //M.N. dojo.require("dojo.data.ItemFileReadStore"); //M.N. dojo.declare("mn.dojo.data.ItemFileReadStore", dojo.data.ItemFileReadStore,{ //M.N. //summary: // The ItemFileReadStore implements the dojo.data.api.Read API and reads // data from JSON files that have contents in this format -- // { items: [ // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, // { name:'Fozzie Bear', wears:['hat', 'tie']}, // { name:'Miss Piggy', pets:'Foo-Foo'} // ]} // Note that it can also contain an 'identifer' property that specified which attribute on the items // in the array of items that acts as the unique identifier for that item. // _getItemsFromLoadedData: function(/* Object */ dataObject){ // summary: // Function to parse the loaded data into item format and build the internal items array. // description: // Function to parse the loaded data into item format and build the internal items array. // // dataObject: // The JS data object containing the raw data to convery into item format. // // returns: array // Array of items in store item format. // First, we define a couple little utility functions... function valueIsAnItem(/* anything */ aValue){ // summary: // Given any sort of value that could be in the raw json data, // return true if we should interpret the value as being an // item itself, rather than a literal value or a reference. // examples: // false == valueIsAnItem("Kermit"); // false == valueIsAnItem(42); // false == valueIsAnItem(new Date()); // false == valueIsAnItem({_type:'Date', _value:'May 14, 1802'}); // false == valueIsAnItem({_reference:'Kermit'}); // true == valueIsAnItem({name:'Kermit', color:'green'}); // true == valueIsAnItem({iggy:'pop'}); // true == valueIsAnItem({foo:42}); var isItem = ( (aValue != null) && (typeof aValue == "object") && (!dojo.isArray(aValue)) && (!dojo.isFunction(aValue)) && (aValue.constructor == Object) && (typeof aValue._reference == "undefined") && (typeof aValue._type == "undefined") && (typeof aValue._value == "undefined") ); return isItem; } var self = this; function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ self._arrayOfAllItems.push(anItem); for(var attribute in anItem){ var valueForAttribute = anItem[attribute]; if(valueForAttribute){ if(dojo.isArray(valueForAttribute)){ var valueArray = valueForAttribute; for(var k = 0; k < valueArray.length; ++k){ var singleValue = valueArray[k]; if(valueIsAnItem(singleValue)){ addItemAndSubItemsToArrayOfAllItems(singleValue); } } }else{ if(valueIsAnItem(valueForAttribute)){ addItemAndSubItemsToArrayOfAllItems(valueForAttribute); } } } } } var newDataObject = this.formatRawData(dataObject); //M.N. this._labelAttr = dataObject.label; // We need to do some transformations to convert the data structure // that we read from the file into a format that will be convenient // to work with in memory. // Step 1: Walk through the object hierarchy and build a list of all items var i; var item; this._arrayOfAllItems = []; this._arrayOfTopLevelItems = newDataObject.items; //M.N. for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ item = this._arrayOfTopLevelItems[i]; addItemAndSubItemsToArrayOfAllItems(item); item[this._rootItemPropName]=true; } // Step 2: Walk through all the attribute values of all the items, // and replace single values with arrays. For example, we change this: // { name:'Miss Piggy', pets:'Foo-Foo'} // into this: // { name:['Miss Piggy'], pets:['Foo-Foo']} // // We also store the attribute names so we can validate our store // reference and item id special properties for the O(1) isItem var allAttributeNames = {}; var key; for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; for(key in item){ if (key !== this._rootItemPropName) { var value = item[key]; if(value !== null){ if(!dojo.isArray(value)){ item[key] = [value]; } }else{ item[key] = [null]; } } allAttributeNames[key]=key; } } // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName // This should go really fast, it will generally never even run the loop. while(allAttributeNames[this._storeRefPropName]){ this._storeRefPropName += "_"; } while(allAttributeNames[this._itemNumPropName]){ this._itemNumPropName += "_"; } // Step 4: Some data files specify an optional 'identifier', which is // the name of an attribute that holds the identity of each item. // If this data file specified an identifier attribute, then build a // hash table of items keyed by the identity of the items. var arrayOfValues; var identifier = dataObject.identifier; this._itemsByIdentity = {}; if(identifier){ this._features['dojo.data.api.Identity'] = identifier; for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; arrayOfValues = item[identifier]; var identity = arrayOfValues[0]; if(!this._itemsByIdentity[identity]){ this._itemsByIdentity[identity] = item; }else{ if(this._jsonFileUrl){ throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); }else if(this._jsonData){ throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); } } } }else{ this._features['dojo.data.api.Identity'] = Number; } // Step 5: Walk through all the items, and set each item's properties // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; item[this._storeRefPropName] = this; item[this._itemNumPropName] = i; } // Step 6: We walk through all the attribute values of all the items, // looking for type/value literals and item-references. // // We replace item-references with pointers to items. For example, we change: // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } // into this: // { name:['Kermit'], friends:[miss_piggy] } // (where miss_piggy is the object representing the 'Miss Piggy' item). // // We replace type/value pairs with typed-literals. For example, we change: // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'July 18, 1918'}] } // into this: // { name:['Kermit'], born:(new Date('July 18, 1918')) } // // We also generate the associate map for all items for the O(1) isItem function. for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } for(key in item){ arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] for(var j = 0; j < arrayOfValues.length; ++j) { value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} if(value !== null && typeof value == "object"){ if(value._type && value._value){ var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} if(!mappingObj){ throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); }else if(dojo.isFunction(mappingObj)){ arrayOfValues[j] = new mappingObj(value._value); }else if(dojo.isFunction(mappingObj.deserialize)){ arrayOfValues[j] = mappingObj.deserialize(value._value); }else{ throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); } } if(value._reference){ var referenceDescription = value._reference; // example: {name:'Miss Piggy'} if(dojo.isString(referenceDescription)){ // example: 'Miss Piggy' // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} arrayOfValues[j] = this._itemsByIdentity[referenceDescription]; }else{ // example: {name:'Miss Piggy'} // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } for(var k = 0; k < this._arrayOfAllItems.length; ++k){ var candidateItem = this._arrayOfAllItems[k]; var found = true; for(var refKey in referenceDescription){ if(candidateItem[refKey] != referenceDescription[refKey]){ found = false; } } if(found){ arrayOfValues[j] = candidateItem; } } } } } } } } }, formatRawData: function(dataObject_in) { //M.N. // // Override this to reformat data into DataStore format!!! // return dataObject_in; //M.N. } //M.N. });Using new version of "ItemFileReadStore":
dojo.require("dojo.parser"); dojo.require("mn.dojo.data.ItemFileReadStore"); var myFormatRawData = function(jsonData) { return {items:jsonData.ResultSet.Result}; } var Body_onload = function() { coursesStore = new mn.dojo.data.ItemFileReadStore({url:"myurl"}); coursesStore.formatRawData = myFormatRawData; //Override!!! coursesStore.fetch( {onComplete: gotList, onError: gotError} ); }"myFormatRawData" transforms:
{"ResultSet": {"ResultLength":5, "Result":[ {"courseid":1500,"course":"BLS for the Healthcare Provider"}, {"courseid":1359,"course":"CHCS & AHLTA Provider"}, {"courseid":196,"course":"iSTAT 1 Analyzer"}, {"courseid":2177,"course":"M.E.D.I.C. Training, Skills Set 4"}, {"courseid":374,"course":"Pain Management and Patient Controlled Analgesia"} ]} }To:
{"items":[ {"courseid":1500,"course":"BLS for the Healthcare Provider"}, {"courseid":1359,"course":"CHCS & AHLTA Provider"}, {"courseid":196,"course":"iSTAT 1 Analyzer"}, {"courseid":2177,"course":"M.E.D.I.C. Training, Skills Set 4"}, {"courseid":374,"course":"Pain Management and Patient Controlled Analgesia"} ]}In case someone is actully experimenting with this...
This version of my modified "dojo.data.ItemFileReadStore" should work properly with dijit. It has been tested with FilteringSelect. All lines that I modified have bee flagged with //M.N.
dojo.provide("mn.dojo.data.ItemFileReadStore"); //M.N. dojo.require("dojo.data.ItemFileReadStore"); //M.N. dojo.declare("mn.dojo.data.ItemFileReadStore", dojo.data.ItemFileReadStore,{ //M.N. // summary: // The ItemFileReadStore implements the dojo.data.api.Read API and reads // data from JSON files that have contents in this format -- // { items: [ // { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, // { name:'Fozzie Bear', wears:['hat', 'tie']}, // { name:'Miss Piggy', pets:'Foo-Foo'} // ]} // Note that it can also contain an 'identifer' property that specified which attribute on the items // in the array of items that acts as the unique identifier for that item. // _getItemsFromLoadedData: function(/* Object */ dataObject){ // summary: // Function to parse the loaded data into item format and build the internal items array. // description: // Function to parse the loaded data into item format and build the internal items array. // // dataObject: // The JS data object containing the raw data to convery into item format. // // returns: array // Array of items in store item format. // First, we define a couple little utility functions... function valueIsAnItem(/* anything */ aValue){ // summary: // Given any sort of value that could be in the raw json data, // return true if we should interpret the value as being an // item itself, rather than a literal value or a reference. // examples: // false == valueIsAnItem("Kermit"); // false == valueIsAnItem(42); // false == valueIsAnItem(new Date()); // false == valueIsAnItem({_type:'Date', _value:'May 14, 1802'}); // false == valueIsAnItem({_reference:'Kermit'}); // true == valueIsAnItem({name:'Kermit', color:'green'}); // true == valueIsAnItem({iggy:'pop'}); // true == valueIsAnItem({foo:42}); var isItem = ( (aValue != null) && (typeof aValue == "object") && (!dojo.isArray(aValue)) && (!dojo.isFunction(aValue)) && (aValue.constructor == Object) && (typeof aValue._reference == "undefined") && (typeof aValue._type == "undefined") && (typeof aValue._value == "undefined") ); return isItem; } var self = this; function addItemAndSubItemsToArrayOfAllItems(/* Item */ anItem){ self._arrayOfAllItems.push(anItem); for(var attribute in anItem){ var valueForAttribute = anItem[attribute]; if(valueForAttribute){ if(dojo.isArray(valueForAttribute)){ var valueArray = valueForAttribute; for(var k = 0; k < valueArray.length; ++k){ var singleValue = valueArray[k]; if(valueIsAnItem(singleValue)){ addItemAndSubItemsToArrayOfAllItems(singleValue); } } }else{ if(valueIsAnItem(valueForAttribute)){ addItemAndSubItemsToArrayOfAllItems(valueForAttribute); } } } } } var newDataObject = this.formatRawData(dataObject); //M.N. this._labelAttr = newDataObject.label; //M.N. // We need to do some transformations to convert the data structure // that we read from the file into a format that will be convenient // to work with in memory. // Step 1: Walk through the object hierarchy and build a list of all items var i; var item; this._arrayOfAllItems = []; this._arrayOfTopLevelItems = newDataObject.items; //M.N. for(i = 0; i < this._arrayOfTopLevelItems.length; ++i){ item = this._arrayOfTopLevelItems[i]; addItemAndSubItemsToArrayOfAllItems(item); item[this._rootItemPropName]=true; } // Step 2: Walk through all the attribute values of all the items, // and replace single values with arrays. For example, we change this: // { name:'Miss Piggy', pets:'Foo-Foo'} // into this: // { name:['Miss Piggy'], pets:['Foo-Foo']} // // We also store the attribute names so we can validate our store // reference and item id special properties for the O(1) isItem var allAttributeNames = {}; var key; for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; for(key in item){ if (key !== this._rootItemPropName) { var value = item[key]; if(value !== null){ if(!dojo.isArray(value)){ item[key] = [value]; } }else{ item[key] = [null]; } } allAttributeNames[key]=key; } } // Step 3: Build unique property names to use for the _storeRefPropName and _itemNumPropName // This should go really fast, it will generally never even run the loop. while(allAttributeNames[this._storeRefPropName]){ this._storeRefPropName += "_"; } while(allAttributeNames[this._itemNumPropName]){ this._itemNumPropName += "_"; } // Step 4: Some data files specify an optional 'identifier', which is // the name of an attribute that holds the identity of each item. // If this data file specified an identifier attribute, then build a // hash table of items keyed by the identity of the items. var arrayOfValues; var identifier = newDataObject.identifier; //M.N. this._itemsByIdentity = {}; if(identifier){ this._features['dojo.data.api.Identity'] = identifier; for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; arrayOfValues = item[identifier]; var identity = arrayOfValues[0]; if(!this._itemsByIdentity[identity]){ this._itemsByIdentity[identity] = item; }else{ if(this._jsonFileUrl){ throw new Error("dojo.data.ItemFileReadStore: The json data as specified by: [" + this._jsonFileUrl + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); }else if(this._jsonData){ throw new Error("dojo.data.ItemFileReadStore: The json data provided by the creation arguments is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); } } } }else{ this._features['dojo.data.api.Identity'] = Number; } // Step 5: Walk through all the items, and set each item's properties // for _storeRefPropName and _itemNumPropName, so that store.isItem() will return true. for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; item[this._storeRefPropName] = this; item[this._itemNumPropName] = i; } // Step 6: We walk through all the attribute values of all the items, // looking for type/value literals and item-references. // // We replace item-references with pointers to items. For example, we change: // { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } // into this: // { name:['Kermit'], friends:[miss_piggy] } // (where miss_piggy is the object representing the 'Miss Piggy' item). // // We replace type/value pairs with typed-literals. For example, we change: // { name:['Nelson Mandela'], born:[{_type:'Date', _value:'July 18, 1918'}] } // into this: // { name:['Kermit'], born:(new Date('July 18, 1918')) } // // We also generate the associate map for all items for the O(1) isItem function. for(i = 0; i < this._arrayOfAllItems.length; ++i){ item = this._arrayOfAllItems[i]; // example: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } for(key in item){ arrayOfValues = item[key]; // example: [{_reference:{name:'Miss Piggy'}}] for(var j = 0; j < arrayOfValues.length; ++j) { value = arrayOfValues[j]; // example: {_reference:{name:'Miss Piggy'}} if(value !== null && typeof value == "object"){ if(value._type && value._value){ var type = value._type; // examples: 'Date', 'Color', or 'ComplexNumber' var mappingObj = this._datatypeMap[type]; // examples: Date, dojo.Color, foo.math.ComplexNumber, {type: dojo.Color, deserialize(value){ return new dojo.Color(value)}} if(!mappingObj){ throw new Error("dojo.data.ItemFileReadStore: in the typeMap constructor arg, no object class was specified for the datatype '" + type + "'"); }else if(dojo.isFunction(mappingObj)){ arrayOfValues[j] = new mappingObj(value._value); }else if(dojo.isFunction(mappingObj.deserialize)){ arrayOfValues[j] = mappingObj.deserialize(value._value); }else{ throw new Error("dojo.data.ItemFileReadStore: Value provided in typeMap was neither a constructor, nor a an object with a deserialize function"); } } if(value._reference){ var referenceDescription = value._reference; // example: {name:'Miss Piggy'} if(dojo.isString(referenceDescription)){ // example: 'Miss Piggy' // from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]} arrayOfValues[j] = this._itemsByIdentity[referenceDescription]; }else{ // example: {name:'Miss Piggy'} // from an item like: { name:['Kermit'], friends:[{_reference:{name:'Miss Piggy'}}] } for(var k = 0; k < this._arrayOfAllItems.length; ++k){ var candidateItem = this._arrayOfAllItems[k]; var found = true; for(var refKey in referenceDescription){ if(candidateItem[refKey] != referenceDescription[refKey]){ found = false; } } if(found){ arrayOfValues[j] = candidateItem; } } } } } } } } }, formatRawData: function(dataObject_in) { //M.N. // // Override this to reformat data into DataStore format!!! // return dataObject_in; //M.N. } //M.N. });Not working
Hello. I tried replacing the contents of ItemFileReadStore.js with your code, but I get an error in the test web page test_SlideShow.html "Could not load 'dojo.data.ItemFileReadStore'; last tried './data/ItemFileReadStore.js'" in dojo.js line 20.
How to implement?
Hello MN. Thanks for posting, this is what I'm looking for to convert Ruby JSON output to use with dojo.
However, I'm a newbie to dojo. Can you tell me where exactly I should place your code?
Thanks
David
Follow-up
there is now a method in QueryReadStore which is intended for the above-mentioned:
_filterResponse: function(data){ // summary: // If the data from servers needs to be processed before it can be processed by this // store, then this function should be re-implemented in subclass. This default // implementation just return the data unchanged. // data: // The data received from server return data; },