KeyValueStore.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.data.KeyValueStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.data.KeyValueStore"] = true;
  8. dojo.provide("dojox.data.KeyValueStore");
  9. dojo.require("dojo.data.util.simpleFetch");
  10. dojo.require("dojo.data.util.filter");
  11. dojo.declare("dojox.data.KeyValueStore", null, {
  12. // summary:
  13. // This is a dojo.data store implementation. It can take in either a Javascript
  14. // array, JSON string, or URL as the data source. Data is expected to be in the
  15. // following format:
  16. // [
  17. // { "key1": "value1" },
  18. // { "key2": "value2" }
  19. // ]
  20. // This is to mimic the Java Properties file format. Each 'item' from this store
  21. // is a JS object representing a key-value pair. If an item in the above array has
  22. // more than one key/value pair, only the first will be used/accessed.
  23. constructor: function(/* Object */ keywordParameters){
  24. // summary: constructor
  25. // keywordParameters: {url: String}
  26. // keywordParameters: {data: string}
  27. // keywordParameters: {dataVar: jsonObject}
  28. if(keywordParameters.url){
  29. this.url = keywordParameters.url;
  30. }
  31. this._keyValueString = keywordParameters.data;
  32. this._keyValueVar = keywordParameters.dataVar;
  33. this._keyAttribute = "key";
  34. this._valueAttribute = "value";
  35. this._storeProp = "_keyValueStore";
  36. this._features = {
  37. 'dojo.data.api.Read': true,
  38. 'dojo.data.api.Identity': true
  39. };
  40. this._loadInProgress = false; //Got to track the initial load to prevent duelling loads of the dataset.
  41. this._queuedFetches = [];
  42. if(keywordParameters && "urlPreventCache" in keywordParameters){
  43. this.urlPreventCache = keywordParameters.urlPreventCache?true:false;
  44. }
  45. },
  46. url: "",
  47. data: "",
  48. //urlPreventCache: boolean
  49. //Controls if urlPreventCache should be used with underlying xhrGet.
  50. urlPreventCache: false,
  51. _assertIsItem: function(/* item */ item){
  52. // summary:
  53. // This function tests whether the item passed in is indeed an item in the store.
  54. // item:
  55. // The item to test for being contained by the store.
  56. if(!this.isItem(item)){
  57. throw new Error("dojox.data.KeyValueStore: a function was passed an item argument that was not an item");
  58. }
  59. },
  60. _assertIsAttribute: function(/* item */ item, /* String */ attribute){
  61. // summary:
  62. // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
  63. // attribute:
  64. // The attribute to test for being contained by the store.
  65. if(!dojo.isString(attribute)){
  66. throw new Error("dojox.data.KeyValueStore: a function was passed an attribute argument that was not an attribute object nor an attribute name string");
  67. }
  68. },
  69. /***************************************
  70. dojo.data.api.Read API
  71. ***************************************/
  72. getValue: function( /* item */ item,
  73. /* attribute-name-string */ attribute,
  74. /* value? */ defaultValue){
  75. // summary:
  76. // See dojo.data.api.Read.getValue()
  77. this._assertIsItem(item);
  78. this._assertIsAttribute(item, attribute);
  79. var value;
  80. if(attribute == this._keyAttribute){ // Looking for key
  81. value = item[this._keyAttribute];
  82. }else{
  83. value = item[this._valueAttribute]; // Otherwise, attribute == ('value' || the actual key )
  84. }
  85. if(value === undefined){
  86. value = defaultValue;
  87. }
  88. return value;
  89. },
  90. getValues: function(/* item */ item,
  91. /* attribute-name-string */ attribute){
  92. // summary:
  93. // See dojo.data.api.Read.getValues()
  94. // Key/Value syntax does not support multi-valued attributes, so this is just a
  95. // wrapper function for getValue().
  96. var value = this.getValue(item, attribute);
  97. return (value ? [value] : []); //Array
  98. },
  99. getAttributes: function(/* item */ item){
  100. // summary:
  101. // See dojo.data.api.Read.getAttributes()
  102. return [this._keyAttribute, this._valueAttribute, item[this._keyAttribute]];
  103. },
  104. hasAttribute: function( /* item */ item,
  105. /* attribute-name-string */ attribute){
  106. // summary:
  107. // See dojo.data.api.Read.hasAttribute()
  108. this._assertIsItem(item);
  109. this._assertIsAttribute(item, attribute);
  110. return (attribute == this._keyAttribute || attribute == this._valueAttribute || attribute == item[this._keyAttribute]);
  111. },
  112. containsValue: function(/* item */ item,
  113. /* attribute-name-string */ attribute,
  114. /* anything */ value){
  115. // summary:
  116. // See dojo.data.api.Read.containsValue()
  117. var regexp = undefined;
  118. if(typeof value === "string"){
  119. regexp = dojo.data.util.filter.patternToRegExp(value, false);
  120. }
  121. return this._containsValue(item, attribute, value, regexp); //boolean.
  122. },
  123. _containsValue: function( /* item */ item,
  124. /* attribute || attribute-name-string */ attribute,
  125. /* anything */ value,
  126. /* RegExp?*/ regexp){
  127. // summary:
  128. // Internal function for looking at the values contained by the item.
  129. // description:
  130. // Internal function for looking at the values contained by the item. This
  131. // function allows for denoting if the comparison should be case sensitive for
  132. // strings or not (for handling filtering cases where string case should not matter)
  133. //
  134. // item:
  135. // The data item to examine for attribute values.
  136. // attribute:
  137. // The attribute to inspect.
  138. // value:
  139. // The value to match.
  140. // regexp:
  141. // Optional regular expression generated off value if value was of string type to handle wildcarding.
  142. // If present and attribute values are string, then it can be used for comparison instead of 'value'
  143. var values = this.getValues(item, attribute);
  144. for(var i = 0; i < values.length; ++i){
  145. var possibleValue = values[i];
  146. if(typeof possibleValue === "string" && regexp){
  147. return (possibleValue.match(regexp) !== null);
  148. }else{
  149. //Non-string matching.
  150. if(value === possibleValue){
  151. return true; // Boolean
  152. }
  153. }
  154. }
  155. return false; // Boolean
  156. },
  157. isItem: function(/* anything */ something){
  158. // summary:
  159. // See dojo.data.api.Read.isItem()
  160. if(something && something[this._storeProp] === this){
  161. return true; //Boolean
  162. }
  163. return false; //Boolean
  164. },
  165. isItemLoaded: function(/* anything */ something){
  166. // summary:
  167. // See dojo.data.api.Read.isItemLoaded()
  168. // The KeyValueStore always loads all items, so if it's an item, then it's loaded.
  169. return this.isItem(something); //Boolean
  170. },
  171. loadItem: function(/* object */ keywordArgs){
  172. // summary:
  173. // See dojo.data.api.Read.loadItem()
  174. // description:
  175. // The KeyValueStore always loads all items, so if it's an item, then it's loaded.
  176. // From the dojo.data.api.Read.loadItem docs:
  177. // If a call to isItemLoaded() returns true before loadItem() is even called,
  178. // then loadItem() need not do any work at all and will not even invoke
  179. // the callback handlers.
  180. },
  181. getFeatures: function(){
  182. // summary:
  183. // See dojo.data.api.Read.getFeatures()
  184. return this._features; //Object
  185. },
  186. close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
  187. // summary:
  188. // See dojo.data.api.Read.close()
  189. },
  190. getLabel: function(/* item */ item){
  191. // summary:
  192. // See dojo.data.api.Read.getLabel()
  193. return item[this._keyAttribute];
  194. },
  195. getLabelAttributes: function(/* item */ item){
  196. // summary:
  197. // See dojo.data.api.Read.getLabelAttributes()
  198. return [this._keyAttribute];
  199. },
  200. // The dojo.data.api.Read.fetch() function is implemented as
  201. // a mixin from dojo.data.util.simpleFetch.
  202. // That mixin requires us to define _fetchItems().
  203. _fetchItems: function( /* Object */ keywordArgs,
  204. /* Function */ findCallback,
  205. /* Function */ errorCallback){
  206. // summary:
  207. // See dojo.data.util.simpleFetch.fetch()
  208. var self = this;
  209. var filter = function(requestArgs, arrayOfAllItems){
  210. var items = null;
  211. if(requestArgs.query){
  212. items = [];
  213. var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false;
  214. //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
  215. //same value for each item examined. Much more efficient.
  216. var regexpList = {};
  217. for(var key in requestArgs.query){
  218. var value = requestArgs.query[key];
  219. if(typeof value === "string"){
  220. regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase);
  221. }
  222. }
  223. for(var i = 0; i < arrayOfAllItems.length; ++i){
  224. var match = true;
  225. var candidateItem = arrayOfAllItems[i];
  226. for(var key in requestArgs.query){
  227. var value = requestArgs.query[key];
  228. if(!self._containsValue(candidateItem, key, value, regexpList[key])){
  229. match = false;
  230. }
  231. }
  232. if(match){
  233. items.push(candidateItem);
  234. }
  235. }
  236. }else if(requestArgs.identity){
  237. items = [];
  238. var item;
  239. for(var key in arrayOfAllItems){
  240. item = arrayOfAllItems[key];
  241. if(item[self._keyAttribute] == requestArgs.identity){
  242. items.push(item);
  243. break;
  244. }
  245. }
  246. }else{
  247. // We want a copy to pass back in case the parent wishes to sort the array. We shouldn't allow resort
  248. // of the internal list so that multiple callers can get lists and sort without affecting each other.
  249. if(arrayOfAllItems.length> 0){
  250. items = arrayOfAllItems.slice(0,arrayOfAllItems.length);
  251. }
  252. }
  253. findCallback(items, requestArgs);
  254. };
  255. if(this._loadFinished){
  256. filter(keywordArgs, this._arrayOfAllItems);
  257. }else{
  258. if(this.url !== ""){
  259. //If fetches come in before the loading has finished, but while
  260. //a load is in progress, we have to defer the fetching to be
  261. //invoked in the callback.
  262. if(this._loadInProgress){
  263. this._queuedFetches.push({args: keywordArgs, filter: filter});
  264. }else{
  265. this._loadInProgress = true;
  266. var getArgs = {
  267. url: self.url,
  268. handleAs: "json-comment-filtered",
  269. preventCache: this.urlPreventCache
  270. };
  271. var getHandler = dojo.xhrGet(getArgs);
  272. getHandler.addCallback(function(data){
  273. self._processData(data);
  274. filter(keywordArgs, self._arrayOfAllItems);
  275. self._handleQueuedFetches();
  276. });
  277. getHandler.addErrback(function(error){
  278. self._loadInProgress = false;
  279. throw error;
  280. });
  281. }
  282. }else if(this._keyValueString){
  283. this._processData(eval(this._keyValueString));
  284. this._keyValueString = null;
  285. filter(keywordArgs, this._arrayOfAllItems);
  286. }else if(this._keyValueVar){
  287. this._processData(this._keyValueVar);
  288. this._keyValueVar = null;
  289. filter(keywordArgs, this._arrayOfAllItems);
  290. }else{
  291. throw new Error("dojox.data.KeyValueStore: No source data was provided as either URL, String, or Javascript variable data input.");
  292. }
  293. }
  294. },
  295. _handleQueuedFetches: function(){
  296. // summary:
  297. // Internal function to execute delayed request in the store.
  298. //Execute any deferred fetches now.
  299. if(this._queuedFetches.length > 0){
  300. for(var i = 0; i < this._queuedFetches.length; i++){
  301. var fData = this._queuedFetches[i];
  302. var delayedFilter = fData.filter;
  303. var delayedQuery = fData.args;
  304. if(delayedFilter){
  305. delayedFilter(delayedQuery, this._arrayOfAllItems);
  306. }else{
  307. this.fetchItemByIdentity(fData.args);
  308. }
  309. }
  310. this._queuedFetches = [];
  311. }
  312. },
  313. _processData: function(/* Array */ data){
  314. this._arrayOfAllItems = [];
  315. for(var i=0; i<data.length; i++){
  316. this._arrayOfAllItems.push(this._createItem(data[i]));
  317. }
  318. this._loadFinished = true;
  319. this._loadInProgress = false;
  320. },
  321. _createItem: function(/* Object */ something){
  322. var item = {};
  323. item[this._storeProp] = this;
  324. for(var i in something){
  325. item[this._keyAttribute] = i;
  326. item[this._valueAttribute] = something[i];
  327. break;
  328. }
  329. return item; //Object
  330. },
  331. /***************************************
  332. dojo.data.api.Identity API
  333. ***************************************/
  334. getIdentity: function(/* item */ item){
  335. // summary:
  336. // See dojo.data.api.Identity.getIdentity()
  337. if(this.isItem(item)){
  338. return item[this._keyAttribute]; //String
  339. }
  340. return null; //null
  341. },
  342. getIdentityAttributes: function(/* item */ item){
  343. // summary:
  344. // See dojo.data.api.Identity.getIdentifierAttributes()
  345. return [this._keyAttribute];
  346. },
  347. fetchItemByIdentity: function(/* object */ keywordArgs){
  348. // summary:
  349. // See dojo.data.api.Identity.fetchItemByIdentity()
  350. keywordArgs.oldOnItem = keywordArgs.onItem;
  351. keywordArgs.onItem = null;
  352. keywordArgs.onComplete = this._finishFetchItemByIdentity ;
  353. this.fetch(keywordArgs);
  354. },
  355. _finishFetchItemByIdentity: function(/* Array */ items, /* object */ request){
  356. var scope = request.scope || dojo.global;
  357. if(items.length){
  358. request.oldOnItem.call(scope, items[0]);
  359. }else{
  360. request.oldOnItem.call(scope, null);
  361. }
  362. }
  363. });
  364. //Mix in the simple fetch implementation to this class.
  365. dojo.extend(dojox.data.KeyValueStore,dojo.data.util.simpleFetch);
  366. }