Ticket #4761: dojox.data.QueryReadStore_20071026.patch
| File dojox.data.QueryReadStore_20071026.patch, 17.6 kB (added by BlueFire, 15 months ago) |
|---|
-
data/tests/stores/QueryReadStore.js
239 239 // description: 240 240 var store = dojox.data.tests.stores.QueryReadStore.getStore(); 241 241 242 var lastRequest Timestamp= null;242 var lastRequestHash = null; 243 243 var firstItems = []; 244 244 var d = new doh.Deferred(); 245 245 function onComplete(items, request) { 246 246 t.assertEqual(5, items.length); 247 lastRequest Timestamp = store.lastRequestTimestamp;247 lastRequestHash = store.lastRequestHash; 248 248 firstItems = items; 249 249 250 250 // Do the next request AFTER the previous one, so we are sure its sequential. 251 251 // We need to be sure so we can compare to the data from the first request. 252 252 function onComplete1(items, request) { 253 253 t.assertEqual(5, items.length); 254 t.assertEqual(lastRequest Timestamp, store.lastRequestTimestamp);254 t.assertEqual(lastRequestHash, store.lastRequestHash); 255 255 t.assertEqual(firstItems[1], items[0]); 256 256 d.callback(true); 257 257 } … … 268 268 return d; //Object 269 269 }, 270 270 271 function testReadApi_fetch_serverQuery(t) { 272 // TODO verify serverQuery vs. query 273 t.assertTrue(true); 271 function testReadApi_fetch_server_paging(t) { 272 // Verify that the paging on the server side does work. 273 // This is the test for http://trac.dojotoolkit.org/ticket/4761 274 // 275 // How? We request 10 items from the server, start=0, count=10. 276 // The second request requests 5 items: start=5, count=5 and those 277 // 5 items should have the same values as the last 5 of the first 278 // request. 279 // This tests if the server side paging does work. 280 var store = dojox.data.tests.stores.QueryReadStore.getStore(); 281 282 var lastRequestHash = null; 283 var d = new doh.Deferred(); 284 function onComplete(items, request) { 285 t.assertEqual(10, items.length); 286 lastRequestHash = store.lastRequestHash; 287 firstItems = items; 288 289 // Do the next request AFTER the previous one, so we are sure its sequential. 290 // We need to be sure so we can compare to the data from the first request. 291 function onComplete1(items, request) { 292 t.assertEqual(5, items.length); 293 // Compare the timestamp of the last request, they must be different, 294 // since another server request was issued. 295 t.assertTrue(lastRequestHash!=store.lastRequestHash); 296 t.assertEqual(store.getValue(firstItems[5], "name"), store.getValue(items[0], "name")); 297 t.assertEqual(store.getValue(firstItems[6], "name"), store.getValue(items[1], "name")); 298 t.assertEqual(store.getValue(firstItems[7], "name"), store.getValue(items[2], "name")); 299 t.assertEqual(store.getValue(firstItems[8], "name"), store.getValue(items[3], "name")); 300 t.assertEqual(store.getValue(firstItems[9], "name"), store.getValue(items[4], "name")); 301 d.callback(true); 302 } 303 // Init a new store, or it will use the old data, since the query has not changed. 304 store.doClientPaging = false; 305 store.fetch({start:5, count:5, onComplete: onComplete1, onError: onError}); 306 } 307 function onError(error, request) { 308 d.errback(error); 309 } 310 store.fetch({query:{}, start:0, count:10, onComplete: onComplete, onError: onError}); 311 return d; //Object 274 312 }, 275 313 276 314 function testReadApi_getFeatures(t) { -
data/tests/module.js
7 7 dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.XmlStore"); 8 8 dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.FlickrStore"); 9 9 dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.FlickrRestStore"); 10 dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.QueryReadStore"); 10 11 dojo.requireIf(dojo.isBrowser, "dojox.data.tests.dom"); 11 12 }catch(e){ 12 13 doh.debug(e); -
data/tests/QueryReadStore.html
15 15 <script type="text/javascript" src="../../../dojox/data/QueryReadStore.js"></script> 16 16 <script type="text/javascript"> 17 17 dojo.require("dijit.form.ComboBox"); 18 dojo.require("dijit.form.FilteringSelect"); 18 19 dojo.require("dojox.data.QueryReadStore"); 19 20 20 21 dojo.provide("ComboBoxReadStore"); … … 62 63 } 63 64 }); 64 65 65 var testStore = new ComboBoxReadStore({url:'stores/QueryReadStore.php'});;66 var testStore = new dojox.data.QueryReadStore({url:'stores/QueryReadStore.php'});; 66 67 function doSearch() { 67 68 var queryOptions = {}; 68 69 if (dojo.byId("ignoreCaseEnabled").checked) { … … 73 74 } 74 75 75 76 var query = {}; 76 query. name= dojo.byId("searchText").value;77 query.q = dojo.byId("searchText").value; 77 78 var request = {query:query, queryOptions:queryOptions}; 78 79 request.start = dojo.query("#fetchForm")[0].pagingStart.value; 79 80 request.count = dojo.query("#fetchForm")[0].pagingCount.value; … … 84 85 if (radioButtons[i].checked) { 85 86 requestMethod = radioButtons[i].value; 86 87 } 87 } 88 88 } 89 89 90 testStore.requestMethod = requestMethod; 90 91 testStore.doClientPaging = dojo.query("#fetchForm")[0].doClientPaging.checked; 91 function onComplete(items) { 92 93 if (!testStore.doClientPaging) { 94 // We have to fill the serverQuery, since we also want to send the 95 // paging data "start" and "count" along with what is in query. 96 request.serverQuery = {q:request.query.name, start:request.start, count:request.count}; 97 } 98 99 request.onComplete = function (items) { 92 100 var s = "number of items: "+items.length+"<br /><br />"; 93 101 for (var i=0; i<items.length; i++) { 94 102 s += i+": name: '"+testStore.getValue(items[i], "name")+"'<br />"; … … 96 104 //s += "<pre>"+dojo.toJson(items)+"</pre>"; 97 105 dojo.byId("fetchOutput").innerHTML = s; 98 106 }; 99 request.onComplete = onComplete; 107 100 108 testStore.fetch(request); 101 109 } 102 110 </script> 103 111 </head> 104 112 <body class="tundra" style="margin:20px;"> 105 113 <div dojoType="ComboBoxReadStore" jsId="store" url="stores/QueryReadStore.php" requestMethod="get"></div> 106 <inputdojoType="dijit.form.ComboBox" store="store" pageSize="5" />114 This is a ComboBox: <input id="cb" dojoType="dijit.form.ComboBox" store="store" pageSize="5" /> 107 115 <br /><br /><hr /> 108 116 117 This is a FilteringSelect: <input id="fs" dojoType="dijit.form.FilteringSelect" store="store" pageSize="5" /> 118 <br /> 119 <form id="fetchForm"> 120 <input id="selectById" value="0" size="3" /> 121 <input type="button" value="set by id" onclick="dijit.byId('fs').setValue(dojo.byId('selectById').value)" /> 122 </form> 123 124 <br /><br /><hr /> 125 109 126 This ComboBox uses a customized QueryReadStore, it prepares the query-string for the URL that 110 127 way that the paging parameters "start" and "count" are also send.<br /> 111 128 <div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"></div> … … 119 136 <div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"></div> 120 137 <input dojoType="dijit.form.ComboBox" store="serverPagingStore" pageSize="10" /> 121 138 </pre> 122 <pre>dojo.require("dojox.data.QueryReadStore"); 123 124 dojo.provide("ServerPagingReadStore"); 125 dojo.declare("ServerPagingReadStore", dojox.data.QueryReadStore, { 126 fetch:function(request) { 127 request.query = {q:request.query.name, start:request.start, count:request.count}; 128 return this.inherited("fetch", arguments); 129 } 130 });</pre> 139 <pre> 140 dojo.require("dojox.data.QueryReadStore"); 141 dojo.provide("ServerPagingReadStore"); 142 dojo.declare("ServerPagingReadStore", dojox.data.QueryReadStore, { 143 fetch:function(request) { 144 request.serverQuery = {q:request.query.name, start:request.start, count:request.count}; 145 return this.inherited("fetch", arguments); 146 } 147 }); 148 </pre> 131 149 </div> 132 150 <br /><br /> 133 151 -
data/QueryReadStore.js
61 61 _lastServerQuery:null, 62 62 63 63 64 // Store a timestampof the last server request. Actually I introduced this64 // Store a hash of the last server request. Actually I introduced this 65 65 // for testing, so I can check if no unnecessary requests were issued for 66 // client-side-paging. But I am sure people will find uses for it.67 lastRequest Timestamp:null,66 // client-side-paging. 67 lastRequestHash:null, 68 68 69 69 // If this is false, every request is sent to the server. 70 70 // If it's true a second request with the same query will not issue another 71 71 // request, but use the already returned data. This assumes that the server 72 72 // does not do the paging. 73 73 doClientPaging:true, 74 75 // Items by identify for Identify API 76 _itemsByIdentity:null, 74 77 78 // Identifier used 79 _identifier:null, 80 81 _features: {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}, 82 75 83 constructor: function(/* Object */ params){ 76 84 this.url = params.url; 77 85 this.requestMethod = typeof params.requestMethod=="undefined" ? this.requestMethod : params.requestMethod; … … 79 87 //this.useCache = typeof params.useCache=="undefined" ? this.useCache : params.useCache; 80 88 }, 81 89 82 getValue: function( /* item */ item, /* attribute-name-string */ attribute, /* value? */ defaultValue){90 getValue: function(/* item */ item, /* attribute-name-string */ attribute, /* value? */ defaultValue){ 83 91 this._assertIsItem(item); 84 92 if(!this.hasAttribute(item, attribute)){ 85 93 // read api says: return defaultValue "only if *item* does not have a value for *attribute*." … … 162 170 // Actually we have nothing to do here, or at least I dont know what to do here ... 163 171 }, 164 172 173 fetch:function(/* Object? */ request){ 174 // summary: 175 // See dojo.data.util.simpleFetch.fetch() this is just a copy and I adjusted 176 // only the paging, since it happens on the server if doClientPaging is 177 // false, thx to http://trac.dojotoolkit.org/ticket/4761 reporting this. 178 // Would be nice to be able to use simpleFetch() to reduce copied code, 179 // but i dont know how yet. Ideas please! 180 request = request || {}; 181 if(!request.store){ 182 request.store = this; 183 } 184 var self = this; 185 186 var _errorHandler = function(errorData, requestObject){ 187 if(requestObject.onError){ 188 var scope = requestObject.scope || dojo.global; 189 requestObject.onError.call(scope, errorData, requestObject); 190 } 191 }; 192 193 var _fetchHandler = function(items, requestObject){ 194 var oldAbortFunction = requestObject.abort || null; 195 var aborted = false; 196 197 var startIndex = requestObject.start?requestObject.start:0; 198 if (self.doClientPaging==false) { 199 // For client paging we dont need no slicing of the result. 200 startIndex = 0; 201 } 202 var endIndex = requestObject.count?(startIndex + requestObject.count):items.length; 203 204 requestObject.abort = function(){ 205 aborted = true; 206 if(oldAbortFunction){ 207 oldAbortFunction.call(requestObject); 208 } 209 }; 210 211 var scope = requestObject.scope || dojo.global; 212 if(!requestObject.store){ 213 requestObject.store = self; 214 } 215 if(requestObject.onBegin){ 216 requestObject.onBegin.call(scope, items.length, requestObject); 217 } 218 if(requestObject.sort){ 219 items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self)); 220 } 221 if(requestObject.onItem){ 222 for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){ 223 var item = items[i]; 224 if(!aborted){ 225 requestObject.onItem.call(scope, item, requestObject); 226 } 227 } 228 } 229 if(requestObject.onComplete && !aborted){ 230 var subset = null; 231 if (!requestObject.onItem) { 232 subset = items.slice(startIndex, endIndex); 233 } 234 requestObject.onComplete.call(scope, subset, requestObject); 235 } 236 }; 237 this._fetchItems(request, _fetchHandler, _errorHandler); 238 return request; // Object 239 }, 240 165 241 getFeatures: function(){ 166 242 return { 167 243 'dojo.data.api.Read': true … … 211 287 // 212 288 213 289 var serverQuery = typeof request["serverQuery"]=="undefined" ? request.query : request.serverQuery; 290 request = request || {}; 291 if(!request.store){ 292 request.store = this; 293 } 294 295 //Need to add start and count 296 if(!request.store.doClientPaging){ 297 serverQuery = serverQuery||{}; 298 serverQuery.start = request.start?request.start:0; 299 // Count might not be sent if not given. 300 if (request.count) { 301 serverQuery.count = request.count; 302 } 303 } 214 304 // Compare the last query and the current query by simply json-encoding them, 215 305 // so we dont have to do any deep object compare ... is there some dojo.areObjectsEqual()??? 216 306 if(this.doClientPaging && this._lastServerQuery!==null && … … 220 310 }else{ 221 311 var xhrFunc = this.requestMethod.toLowerCase()=="post" ? dojo.xhrPost : dojo.xhrGet; 222 312 var xhrHandler = xhrFunc({url:this.url, handleAs:"json-comment-optional", content:serverQuery}); 223 var self = this; 224 xhrHandler.addCallback(function(data){ 225 self._items = []; 313 xhrHandler.addCallback(dojo.hitch(this, function(data){ 314 this._items = []; 226 315 // Store a ref to "this" in each item, so we can simply check if an item 227 316 // really origins form here (idea is from ItemFileReadStore, I just don't know 228 317 // how efficient the real storage use, garbage collection effort, etc. is). 229 318 for(var i in data.items){ 230 self._items.push({i:data.items[i], r:self});319 this._items.push({i:data.items[i], r:this}); 231 320 } 232 // TODO actually we should do the same as dojo.data.ItemFileReadStore._getItemsFromLoadedData() to sanitize 233 // (does it really sanititze them) and store the data optimal. should we? for security reasons??? 234 fetchHandler(self._items, request); 235 }); 321 322 var identifier = data.identifier; 323 this._itemsByIdentity = {}; 324 if(identifier){ 325 this._identifier = identifier; 326 for(i = 0; i < this._items.length; ++i){ 327 var item = this._items[i].i; 328 var identity = item[identifier]; 329 if(!this._itemsByIdentity[identity]){ 330 this._itemsByIdentity[identity] = item; 331 }else{ 332 throw new Error("dojo.data.QueryReadStore: The json data as specified by: [" + this._url + "] is malformed. Items within the list have identifier: [" + identifier + "]. Value collided: [" + identity + "]"); 333 } 334 } 335 }else{ 336 this._identifier = Number; 337 for(i = 0; i < this._items.length; ++i){ 338 this._items[i].n = i; 339 } 340 } 341 342 // TODO actually we should do the same as dojo.data.ItemFileReadStore._getItemsFromLoadedData() to sanitize 343 // (does it really sanititze them) and store the data optimal. should we? for security reasons??? 344 fetchHandler(this._items, request); 345 })); 236 346 xhrHandler.addErrback(function(error){ 237 347 errorHandler(error, request); 238 348 }); 239 this.lastRequestTimestamp = new Date().getTime(); 349 // Generate the hash using the time in milliseconds and a randon number. 350 // Since Math.randon() returns something like: 0.23453463, we just remove the "0." 351 // probably just for esthetic reasons :-). 352 this.lastRequestHash = new Date().getTime()+"-"+String(Math.random()).substring(2); 240 353 this._lastServerQuery = serverQuery; 241 354 } 242 355 }, … … 260 373 if(typeof attribute !== "string"){ 261 374 throw new dojox.data.QueryReadStore.InvalidAttributeError(this._className+": '"+attribute+"' is not a valid attribute identifier."); 262 375 } 376 }, 377 378 fetchItemByIdentity: function(/* Object */ keywordArgs){ 379 // summary: 380 // See dojo.data.api.Identity.fetchItemByIdentity() 381 382 // See if we have already loaded the item with that id 383 if(this._itemsByIdentity){ 384 item = this._itemsByIdentity[keywordArgs.identity]; 385 if(item === undefined){ 386 item = null; 387 } 388 if(item != null){ 389 if(keywordArgs.onItem){ 390 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; 391 keywordArgs.onItem.call(scope, item); 392 } 393 return; 394 } 395 } 396 397 // Otherwise we need to go remote 398 // Set up error handler 399 var _errorHandler = function(errorData, requestObject){ 400 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; 401 if(keywordArgs.onError){ 402 keywordArgs.onError.call(scope, error); 403 } 404 }; 405 406 // Set up fetch handler 407 var _fetchHandler = function(items, requestObject){ 408 var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; 409 try{ 410 // There is supposed to be only one result 411 var item = null; 412 if(items && items.length == 1){ 413 item = items[0]; 414 } 415 416 if(keywordArgs.onItem){ 417 keywordArgs.onItem.call(scope, item); 418 } 419 }catch(error){ 420 if(keywordArgs.onError){ 421 keywordArgs.onError.call(scope, error); 422 } 423 } 424 }; 425 426 // Construct query 427 var request = {serverQuery:{id:keywordArgs.identity}}; 428 429 // Dispatch query 430 this._fetchItems(request, _fetchHandler, _errorHandler); 431 }, 432 433 getIdentity: function(/* item */ item){ 434 // summary: 435 // See dojo.data.api.Identity.getIdentity() 436 var identifier = null; 437 if(this._identifier === Number){ 438 identifier = item.n; // Number 439 }else{ 440 identifier = item.i[this._identifier]; 441 } 442 return identifier; 443 }, 444 445 getIdentityAttributes: function(/* item */ item){ 446 // summary: 447 // See dojo.data.api.Identity.getIdentityAttributes() 448 return [this._identifier]; 263 449 } 264 450 }); 265 dojo.extend(dojox.data.QueryReadStore, dojo.data.util.simpleFetch);266 451 267 452 dojo.declare("dojox.data.QueryReadStore.InvalidItemError", Error, {}); 268 453 dojo.declare("dojox.data.QueryReadStore.InvalidAttributeError", Error, {});