2010-11-15 08:39:52 +01:00
/ *
2011-11-08 17:40:44 +01:00
Copyright ( c ) 2004 - 2011 , The Dojo Foundation All Rights Reserved .
2010-11-15 08:39:52 +01:00
Available via Academic Free License >= 2.1 OR the modified BSD license .
see : http : //dojotoolkit.org/license for details
* /
2011-03-04 17:02:28 +01:00
if ( ! dojo . _hasResource [ "dojo.data.ItemFileReadStore" ] ) { //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo . _hasResource [ "dojo.data.ItemFileReadStore" ] = true ;
2010-11-15 08:39:52 +01:00
dojo . provide ( "dojo.data.ItemFileReadStore" ) ;
dojo . require ( "dojo.data.util.filter" ) ;
dojo . require ( "dojo.data.util.simpleFetch" ) ;
dojo . require ( "dojo.date.stamp" ) ;
2011-03-04 17:02:28 +01:00
2011-11-08 17:40:44 +01:00
2011-03-04 17:02:28 +01:00
dojo . declare ( "dojo.data.ItemFileReadStore" , null , {
// 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'}
// ]}
2011-11-08 17:40:44 +01:00
// Note that it can also contain an 'identifer' property that specified which attribute on the items
2011-03-04 17:02:28 +01:00
// 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
// }
2011-11-08 17:40:44 +01:00
// Where if it is a function, it is assumed to be an object constructor that takes the
2011-03-04 17:02:28 +01:00
// 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 . _ccUrl = keywordParameters . url ;
this . url = keywordParameters . url ;
this . _jsonData = keywordParameters . data ;
this . data = null ;
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 . _reverseRefMap = "_RRM" ; // Default attribute for constructing a reverse reference map for use with reference integrity
this . _loadInProgress = false ; //Got to track the initial load to prevent duelling loads of the dataset.
this . _queuedFetches = [ ] ;
if ( keywordParameters . urlPreventCache !== undefined ) {
this . urlPreventCache = keywordParameters . urlPreventCache ? true : false ;
}
if ( keywordParameters . hierarchical !== undefined ) {
this . hierarchical = keywordParameters . hierarchical ? true : false ;
}
if ( keywordParameters . clearOnClose ) {
this . clearOnClose = true ;
}
if ( "failOk" in keywordParameters ) {
this . failOk = keywordParameters . failOk ? true : false ;
}
} ,
url : "" , // use "" rather than undefined for the benefit of the parser (#3539)
//Internal var, crossCheckUrl. Used so that setting either url or _jsonFileUrl, can still trigger a reload
//when clearOnClose and close is used.
_ccUrl : "" ,
data : null , // define this so that the parser can populate it
typeMap : null , //Define so parser can populate.
//Parameter to allow users to specify if a close call should force a reload or not.
//By default, it retains the old behavior of not clearing if close is called. But
//if set true, the store will be reset to default state. Note that by doing this,
//all item handles will become invalid and a new fetch must be issued.
clearOnClose : false ,
2011-11-08 17:40:44 +01:00
//Parameter to allow specifying if preventCache should be passed to the xhrGet call or not when loading data from a url.
2011-03-04 17:02:28 +01:00
//Note this does not mean the store calls the server on each fetch, only that the data load has preventCache set as an option.
//Added for tracker: #6072
urlPreventCache : false ,
//Parameter for specifying that it is OK for the xhrGet call to fail silently.
failOk : false ,
2011-11-08 17:40:44 +01:00
//Parameter to indicate to process data from the url as hierarchical
//(data items can contain other data items in js form). Default is true
//for backwards compatibility. False means only root items are processed
//as items, all child objects outside of type-mapped objects and those in
2011-03-04 17:02:28 +01:00
//specific reference format, are left straight JS data objects.
hierarchical : true ,
_assertIsItem : function ( /* item */ item ) {
// summary:
// This function tests whether the item passed in is indeed an item in the store.
2011-11-08 17:40:44 +01:00
// item:
2011-03-04 17:02:28 +01:00
// The item to test for being contained by the store.
2011-11-08 17:40:44 +01:00
if ( ! this . isItem ( item ) ) {
2011-03-04 17:02:28 +01:00
throw new Error ( "dojo.data.ItemFileReadStore: Invalid item argument." ) ;
}
} ,
_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.
2011-11-08 17:40:44 +01:00
// attribute:
2011-03-04 17:02:28 +01:00
// The attribute to test for being contained by the store.
2011-11-08 17:40:44 +01:00
if ( typeof attribute !== "string" ) {
2011-03-04 17:02:28 +01:00
throw new Error ( "dojo.data.ItemFileReadStore: Invalid attribute argument." ) ;
}
} ,
2011-11-08 17:40:44 +01:00
getValue : function ( /* item */ item ,
/* attribute-name-string */ attribute ,
2011-03-04 17:02:28 +01:00
/* value? */ defaultValue ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.getValue()
var values = this . getValues ( item , attribute ) ;
return ( values . length > 0 ) ? values [ 0 ] : defaultValue ; // mixed
} ,
2011-11-08 17:40:44 +01:00
getValues : function ( /* item */ item ,
2011-03-04 17:02:28 +01:00
/* attribute-name-string */ attribute ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.getValues()
this . _assertIsItem ( item ) ;
this . _assertIsAttribute ( attribute ) ;
// Clone it before returning. refs: #10474
return ( item [ attribute ] || [ ] ) . slice ( 0 ) ; // Array
} ,
getAttributes : function ( /* item */ item ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// 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 ) && ( key !== this . _reverseRefMap ) ) {
attributes . push ( key ) ;
}
}
return attributes ; // Array
} ,
hasAttribute : function ( /* item */ item ,
/* attribute-name-string */ attribute ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.hasAttribute()
this . _assertIsItem ( item ) ;
this . _assertIsAttribute ( attribute ) ;
return ( attribute in item ) ;
} ,
2011-11-08 17:40:44 +01:00
containsValue : function ( /* item */ item ,
/* attribute-name-string */ attribute ,
2011-03-04 17:02:28 +01:00
/* anything */ value ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// 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.
} ,
2011-11-08 17:40:44 +01:00
_containsValue : function ( /* item */ item ,
/* attribute-name-string */ attribute ,
2011-03-04 17:02:28 +01:00
/* anything */ value ,
/* RegExp?*/ regexp ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// Internal function for looking at the values contained by the item.
2011-11-08 17:40:44 +01:00
// description:
// Internal function for looking at the values contained by the item. This
2011-03-04 17:02:28 +01:00
// 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)
2011-11-08 17:40:44 +01:00
//
2011-03-04 17:02:28 +01:00
// item:
// The data item to examine for attribute values.
// attribute:
// The attribute to inspect.
2011-11-08 17:40:44 +01:00
// value:
2011-03-04 17:02:28 +01:00
// 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'
return dojo . some ( this . getValues ( item , attribute ) , function ( possibleValue ) {
if ( possibleValue !== null && ! dojo . isObject ( possibleValue ) && regexp ) {
if ( possibleValue . toString ( ) . match ( regexp ) ) {
return true ; // Boolean
}
} else if ( value === possibleValue ) {
return true ; // Boolean
}
} ) ;
} ,
isItem : function ( /* anything */ something ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// 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 ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.isItemLoaded()
return this . isItem ( something ) ; //boolean
} ,
loadItem : function ( /* object */ keywordArgs ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.loadItem()
this . _assertIsItem ( keywordArgs . item ) ;
} ,
getFeatures : function ( ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.getFeatures()
return this . _features ; //Object
} ,
getLabel : function ( /* item */ item ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// 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 ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.getLabelAttributes()
if ( this . _labelAttr ) {
return [ this . _labelAttr ] ; //array
}
return null ; //null
} ,
2011-11-08 17:40:44 +01:00
_fetchItems : function ( /* Object */ keywordArgs ,
/* Function */ findCallback ,
2011-03-04 17:02:28 +01:00
/* Function */ errorCallback ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.util.simpleFetch.fetch()
var self = this ,
filter = function ( requestArgs , arrayOfItems ) {
var items = [ ] ,
i , key ;
if ( requestArgs . query ) {
var value ,
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 ( key in requestArgs . query ) {
value = requestArgs . query [ key ] ;
if ( typeof value === "string" ) {
regexpList [ key ] = dojo . data . util . filter . patternToRegExp ( value , ignoreCase ) ;
} else if ( value instanceof RegExp ) {
regexpList [ key ] = value ;
}
}
for ( i = 0 ; i < arrayOfItems . length ; ++ i ) {
var match = true ;
var candidateItem = arrayOfItems [ i ] ;
if ( candidateItem === null ) {
match = false ;
} else {
for ( key in requestArgs . query ) {
value = requestArgs . query [ key ] ;
if ( ! self . _containsValue ( candidateItem , key , value , regexpList [ key ] ) ) {
match = false ;
}
}
}
if ( match ) {
items . push ( candidateItem ) ;
}
}
findCallback ( items , requestArgs ) ;
} else {
2011-11-08 17:40:44 +01:00
// 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
2011-03-04 17:02:28 +01:00
// 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 ( 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 {
//Do a check on the JsonFileUrl and crosscheck it.
//If it doesn't match the cross-check, it needs to be updated
//This allows for either url or _jsonFileUrl to he changed to
2011-11-08 17:40:44 +01:00
//reset the store load location. Done this way for backwards
2011-03-04 17:02:28 +01:00
//compatibility. People use _jsonFileUrl (even though officially
//private.
if ( this . _jsonFileUrl !== this . _ccUrl ) {
2011-11-08 17:40:44 +01:00
dojo . deprecated ( "dojo.data.ItemFileReadStore: " ,
2011-03-04 17:02:28 +01:00
"To change the url, set the url property of the store," +
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0" ) ;
this . _ccUrl = this . _jsonFileUrl ;
this . url = this . _jsonFileUrl ;
} else if ( this . url !== this . _ccUrl ) {
this . _jsonFileUrl = this . url ;
this . _ccUrl = this . url ;
}
//See if there was any forced reset of data.
2011-11-08 17:40:44 +01:00
if ( this . data != null ) {
2011-03-04 17:02:28 +01:00
this . _jsonData = this . data ;
this . data = null ;
}
if ( this . _jsonFileUrl ) {
//If fetches come in before the loading has finished, but while
2011-11-08 17:40:44 +01:00
//a load is in progress, we have to defer the fetching to be
2011-03-04 17:02:28 +01:00
//invoked in the callback.
if ( this . _loadInProgress ) {
this . _queuedFetches . push ( { args : keywordArgs , filter : filter } ) ;
} else {
this . _loadInProgress = true ;
var getArgs = {
2011-11-08 17:40:44 +01:00
url : self . _jsonFileUrl ,
2011-03-04 17:02:28 +01:00
handleAs : "json-comment-optional" ,
preventCache : this . urlPreventCache ,
failOk : this . failOk
} ;
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 ) ;
} ) ;
//Wire up the cancel to abort of the request
//This call cancel on the deferred if it hasn't been called
//yet and then will chain to the simple abort of the
//simpleFetch keywordArgs
var oldAbort = null ;
if ( keywordArgs . abort ) {
oldAbort = keywordArgs . abort ;
}
keywordArgs . abort = function ( ) {
var df = getHandler ;
if ( df && df . fired === - 1 ) {
df . cancel ( ) ;
df = null ;
}
if ( oldAbort ) {
oldAbort . call ( 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 ( ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// 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 ] ,
delayedQuery = fData . args ,
delayedFilter = fData . filter ;
if ( delayedFilter ) {
2011-11-08 17:40:44 +01:00
delayedFilter ( delayedQuery , this . _getItemsArray ( delayedQuery . queryOptions ) ) ;
2011-03-04 17:02:28 +01:00
} else {
this . fetchItemByIdentity ( delayedQuery ) ;
}
}
this . _queuedFetches = [ ] ;
}
} ,
_getItemsArray : function ( /*object?*/ queryOptions ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// Internal function to determine which list of items to search over.
// queryOptions: The query options parameter, if any.
if ( queryOptions && queryOptions . deep ) {
2011-11-08 17:40:44 +01:00
return this . _arrayOfAllItems ;
2011-03-04 17:02:28 +01:00
}
return this . _arrayOfTopLevelItems ;
} ,
close : function ( /*dojo.data.api.Request || keywordArgs || null */ request ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Read.close()
2011-11-08 17:40:44 +01:00
if ( this . clearOnClose &&
this . _loadFinished &&
2011-03-04 17:02:28 +01:00
! this . _loadInProgress ) {
//Reset all internalsback to default state. This will force a reload
2011-11-08 17:40:44 +01:00
//on next fetch. This also checks that the data or url param was set
2011-03-04 17:02:28 +01:00
//so that the store knows it can get data. Without one of those being set,
//the next fetch will trigger an error.
2011-11-08 17:40:44 +01:00
if ( ( ( this . _jsonFileUrl == "" || this . _jsonFileUrl == null ) &&
2011-03-04 17:02:28 +01:00
( this . url == "" || this . url == null )
) && this . data == null ) {
console . debug ( "dojo.data.ItemFileReadStore: WARNING! Data reload " +
2011-11-08 17:40:44 +01:00
" information has not been provided." +
2011-03-04 17:02:28 +01:00
" Please set 'url' or 'data' to the appropriate value before" +
" the next fetch" ) ;
}
this . _arrayOfAllItems = [ ] ;
this . _arrayOfTopLevelItems = [ ] ;
this . _loadFinished = false ;
this . _itemsByIdentity = null ;
this . _loadInProgress = false ;
this . _queuedFetches = [ ] ;
}
} ,
_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...
var addingArrays = false ,
self = this ;
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.
// example:
// | false == valueIsAnItem("Kermit");
// | false == valueIsAnItem(42);
// | false == valueIsAnItem(new Date());
2011-11-08 17:40:44 +01:00
// | false == valueIsAnItem({_type:'Date', _value:'1802-05-14'});
2011-03-04 17:02:28 +01:00
// | 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 ) || addingArrays ) &&
( ! dojo . isFunction ( aValue ) ) &&
( aValue . constructor == Object || dojo . isArray ( aValue ) ) &&
2011-11-08 17:40:44 +01:00
( typeof aValue . _reference === "undefined" ) &&
( typeof aValue . _type === "undefined" ) &&
2011-03-04 17:02:28 +01:00
( typeof aValue . _value === "undefined" ) &&
self . hierarchical
) ;
return isItem ;
}
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 ) ;
}
}
}
}
}
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 ,
item ;
this . _arrayOfAllItems = [ ] ;
this . _arrayOfTopLevelItems = dataObject . items ;
for ( i = 0 ; i < this . _arrayOfTopLevelItems . length ; ++ i ) {
item = this . _arrayOfTopLevelItems [ i ] ;
if ( dojo . isArray ( item ) ) {
addingArrays = true ;
}
addItemAndSubItemsToArrayOfAllItems ( item ) ;
item [ this . _rootItemPropName ] = true ;
}
2011-11-08 17:40:44 +01:00
// Step 2: Walk through all the attribute values of all the items,
2011-03-04 17:02:28 +01:00
// 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']}
2011-11-08 17:40:44 +01:00
//
// We also store the attribute names so we can validate our store
2011-03-04 17:02:28 +01:00
// reference and item id special properties for the O(1) isItem
var allAttributeNames = { } ,
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 += "_" ;
}
while ( allAttributeNames [ this . _reverseRefMap ] ) {
this . _reverseRefMap += "_" ;
}
2011-11-08 17:40:44 +01:00
// 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
2011-03-04 17:02:28 +01:00
// hash table of items keyed by the identity of the items.
var arrayOfValues ;
var identifier = dataObject . identifier ;
if ( identifier ) {
this . _itemsByIdentity = { } ;
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 ] ;
2011-11-08 17:40:44 +01:00
if ( ! Object . hasOwnProperty . call ( this . _itemsByIdentity , identity ) ) {
2011-03-04 17:02:28 +01:00
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 ;
}
2011-11-08 17:40:44 +01:00
// Step 5: Walk through all the items, and set each item's properties
2011-03-04 17:02:28 +01:00
// 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:
2011-11-08 17:40:44 +01:00
// { name:['Kermit'], friends:[miss_piggy] }
2011-03-04 17:02:28 +01:00
// (where miss_piggy is the object representing the 'Miss Piggy' item).
//
// We replace type/value pairs with typed-literals. For example, we change:
2011-11-08 17:40:44 +01:00
// { name:['Nelson Mandela'], born:[{_type:'Date', _value:'1918-07-18'}] }
2011-03-04 17:02:28 +01:00
// into this:
2011-11-08 17:40:44 +01:00
// { name:['Kermit'], born:(new Date(1918, 6, 18)) }
2011-03-04 17:02:28 +01:00
//
// 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 ( ( "_type" in value ) && ( "_value" in 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)}}
2011-11-08 17:40:44 +01:00
if ( ! mappingObj ) {
2011-03-04 17:02:28 +01:00
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 . isObject ( referenceDescription ) ) {
// example: 'Miss Piggy'
// from an item like: { name:['Kermit'], friends:[{_reference:'Miss Piggy'}]}
arrayOfValues [ j ] = this . _getItemByIdentity ( 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 ] ,
found = true ;
for ( var refKey in referenceDescription ) {
2011-11-08 17:40:44 +01:00
if ( candidateItem [ refKey ] != referenceDescription [ refKey ] ) {
found = false ;
2011-03-04 17:02:28 +01:00
}
}
2011-11-08 17:40:44 +01:00
if ( found ) {
arrayOfValues [ j ] = candidateItem ;
2011-03-04 17:02:28 +01:00
}
}
}
if ( this . referenceIntegrity ) {
var refItem = arrayOfValues [ j ] ;
if ( this . isItem ( refItem ) ) {
this . _addReferenceToMap ( refItem , item , key ) ;
}
}
} else if ( this . isItem ( value ) ) {
2011-11-08 17:40:44 +01:00
//It's a child item (not one referenced through _reference).
2011-03-04 17:02:28 +01:00
//We need to treat this as a referenced item, so it can be cleaned up
//in a write store easily.
if ( this . referenceIntegrity ) {
this . _addReferenceToMap ( value , item , key ) ;
}
}
}
}
}
}
} ,
_addReferenceToMap : function ( /*item*/ refItem , /*item*/ parentItem , /*string*/ attribute ) {
// summary:
// Method to add an reference map entry for an item and attribute.
// description:
// Method to add an reference map entry for an item and attribute. //
// refItem:
// The item that is referenced.
// parentItem:
// The item that holds the new reference to refItem.
// attribute:
// The attribute on parentItem that contains the new reference.
//Stub function, does nothing. Real processing is in ItemFileWriteStore.
} ,
getIdentity : function ( /* item */ item ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// 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 ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// See dojo.data.api.Identity.fetchItemByIdentity()
// Hasn't loaded yet, we have to trigger the load.
var item ,
scope ;
if ( ! this . _loadFinished ) {
var self = this ;
//Do a check on the JsonFileUrl and crosscheck it.
//If it doesn't match the cross-check, it needs to be updated
//This allows for either url or _jsonFileUrl to he changed to
2011-11-08 17:40:44 +01:00
//reset the store load location. Done this way for backwards
2011-03-04 17:02:28 +01:00
//compatibility. People use _jsonFileUrl (even though officially
//private.
if ( this . _jsonFileUrl !== this . _ccUrl ) {
2011-11-08 17:40:44 +01:00
dojo . deprecated ( "dojo.data.ItemFileReadStore: " ,
2011-03-04 17:02:28 +01:00
"To change the url, set the url property of the store," +
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0" ) ;
this . _ccUrl = this . _jsonFileUrl ;
this . url = this . _jsonFileUrl ;
} else if ( this . url !== this . _ccUrl ) {
this . _jsonFileUrl = this . url ;
this . _ccUrl = this . url ;
}
//See if there was any forced reset of data.
if ( this . data != null && this . _jsonData == null ) {
this . _jsonData = this . data ;
this . data = null ;
}
if ( this . _jsonFileUrl ) {
if ( this . _loadInProgress ) {
this . _queuedFetches . push ( { args : keywordArgs } ) ;
} else {
this . _loadInProgress = true ;
var getArgs = {
2011-11-08 17:40:44 +01:00
url : self . _jsonFileUrl ,
2011-03-04 17:02:28 +01:00
handleAs : "json-comment-optional" ,
preventCache : this . urlPreventCache ,
failOk : this . failOk
} ;
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 ;
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 ;
item = self . _getItemByIdentity ( keywordArgs . identity ) ;
if ( keywordArgs . onItem ) {
scope = keywordArgs . scope ? keywordArgs . scope : dojo . global ;
keywordArgs . onItem . call ( scope , item ) ;
}
2011-11-08 17:40:44 +01:00
}
2011-03-04 17:02:28 +01:00
} else {
// Already loaded. We can just look it up and call back.
item = this . _getItemByIdentity ( keywordArgs . identity ) ;
if ( keywordArgs . onItem ) {
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 ;
2011-11-08 17:40:44 +01:00
if ( this . _itemsByIdentity &&
Object . hasOwnProperty . call ( this . _itemsByIdentity , identity ) ) {
2011-03-04 17:02:28 +01:00
item = this . _itemsByIdentity [ identity ] ;
2011-11-08 17:40:44 +01:00
} else if ( Object . hasOwnProperty . call ( this . _arrayOfAllItems , identity ) ) {
2011-03-04 17:02:28 +01:00
item = this . _arrayOfAllItems [ identity ] ;
}
if ( item === undefined ) {
item = null ;
}
return item ; // Object
} ,
getIdentityAttributes : function ( /* item */ item ) {
2011-11-08 17:40:44 +01:00
// summary:
// See dojo.data.api.Identity.getIdentityAttributes()
2011-03-04 17:02:28 +01:00
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
2011-11-08 17:40:44 +01:00
// spec says we need to return null if the identity is not composed
// of attributes
2011-03-04 17:02:28 +01:00
return null ; // null
} else {
return [ identifier ] ; // Array
}
} ,
_forceLoad : function ( ) {
2011-11-08 17:40:44 +01:00
// summary:
2011-03-04 17:02:28 +01:00
// Internal function to force a load of the store if it hasn't occurred yet. This is required
2011-11-08 17:40:44 +01:00
// for specific functions to work properly.
2011-03-04 17:02:28 +01:00
var self = this ;
//Do a check on the JsonFileUrl and crosscheck it.
//If it doesn't match the cross-check, it needs to be updated
//This allows for either url or _jsonFileUrl to he changed to
2011-11-08 17:40:44 +01:00
//reset the store load location. Done this way for backwards
2011-03-04 17:02:28 +01:00
//compatibility. People use _jsonFileUrl (even though officially
//private.
if ( this . _jsonFileUrl !== this . _ccUrl ) {
2011-11-08 17:40:44 +01:00
dojo . deprecated ( "dojo.data.ItemFileReadStore: " ,
2011-03-04 17:02:28 +01:00
"To change the url, set the url property of the store," +
" not _jsonFileUrl. _jsonFileUrl support will be removed in 2.0" ) ;
this . _ccUrl = this . _jsonFileUrl ;
this . url = this . _jsonFileUrl ;
} else if ( this . url !== this . _ccUrl ) {
this . _jsonFileUrl = this . url ;
this . _ccUrl = this . url ;
}
//See if there was any forced reset of data.
2011-11-08 17:40:44 +01:00
if ( this . data != null ) {
2011-03-04 17:02:28 +01:00
this . _jsonData = this . data ;
this . data = null ;
}
if ( this . _jsonFileUrl ) {
var getArgs = {
2011-11-08 17:40:44 +01:00
url : this . _jsonFileUrl ,
2011-03-04 17:02:28 +01:00
handleAs : "json-comment-optional" ,
preventCache : this . urlPreventCache ,
failOk : this . failOk ,
sync : true
} ;
var getHandler = dojo . xhrGet ( getArgs ) ;
getHandler . addCallback ( function ( data ) {
try {
2011-11-08 17:40:44 +01:00
//Check to be sure there wasn't another load going on concurrently
2011-03-04 17:02:28 +01:00
//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 ;
} else if ( self . _loadInProgress ) {
//Okay, we hit an error state we can't recover from. A forced load occurred
//while an async load was occurring. Since we cannot block at this point, the best
//that can be managed is to throw an error.
2011-11-08 17:40:44 +01:00
throw new Error ( "dojo.data.ItemFileReadStore: Unable to perform a synchronous load, an async load is in progress." ) ;
2011-03-04 17:02:28 +01:00
}
} 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 ;
2011-11-08 17:40:44 +01:00
}
2011-03-04 17:02:28 +01:00
}
2010-11-15 08:39:52 +01:00
} ) ;
2011-03-04 17:02:28 +01:00
//Mix in the simple fetch implementation to this class.
2010-11-15 08:39:52 +01:00
dojo . extend ( dojo . data . ItemFileReadStore , dojo . data . util . simpleFetch ) ;
2011-03-04 17:02:28 +01:00
2010-11-15 08:39:52 +01:00
}