KeyValueStore.js 12 KB

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