Ticket #4761: dojoQueryReadStore-head-fix4761.patch
| File dojoQueryReadStore-head-fix4761.patch, 15.4 kB (added by BlueFire, 15 months ago) |
|---|
-
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) { -
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); -
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 /><br /><hr /> 119 109 120 This ComboBox uses a customized QueryReadStore, it prepares the query-string for the URL that 110 121 way that the paging parameters "start" and "count" are also send.<br /> 111 122 <div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"></div> … … 119 130 <div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"></div> 120 131 <input dojoType="dijit.form.ComboBox" store="serverPagingStore" pageSize="10" /> 121 132 </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> 133 <pre> 134 dojo.require("dojox.data.QueryReadStore"); 135 dojo.provide("ServerPagingReadStore"); 136 dojo.declare("ServerPagingReadStore", dojox.data.QueryReadStore, { 137 fetch:function(request) { 138 request.serverQuery = {q:request.query.name, start:request.start, count:request.count}; 139 return this.inherited("fetch", arguments); 140 } 141 }); 142 </pre> 131 143 </div> 132 144 <br /><br /> 133 145 -
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 … … 220 296 }else{ 221 297 var xhrFunc = this.requestMethod.toLowerCase()=="post" ? dojo.xhrPost : dojo.xhrGet; 222 298 var xhrHandler = xhrFunc({url:this.url, handleAs:"json-comment-optional", content:serverQuery}); 223 var self = this; 224 xhrHandler.addCallback(function(data){ 225 self._items = []; 299 xhrHandler.addCallback(dojo.hitch(this, function(data){ 300 this._items = []; 226 301 // Store a ref to "this" in each item, so we can simply check if an item 227 302 // really origins form here (idea is from ItemFileReadStore, I just don't know 228 303 // how efficient the real storage use, garbage collection effort, etc. is). 229 304 for(var i in data.items){ 230 self._items.push({i:data.items[i], r:self});305 this._items.push({i:data.items[i], r:this}); 231 306 } 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 }); 307 308 var identifier = data.identifier; 309 this._itemsByIdentity = {}; 310 if(identifier){ 311 this._identifier = identifier; 312 for(i = 0; i < this._items.length; ++i){ 313 var item = this._items[i].i; 314 var identity = item[identifier]; 315 if(!this._itemsByIdentity[identity]){ 316 this._itemsByIdentity[identity] = item; 317 }else{ 318 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 + "]"); 319 } 320 } 321 }else{ 322 this._identifier = Number; 323 for(i = 0; i < this._items.length; ++i){ 324 this._items[i].n = i; 325 } 326 } 327 328 // TODO actually we should do the same as dojo.data.ItemFileReadStore._getItemsFromLoadedData() to sanitize 329 // (does it really sanititze them) and store the data optimal. should we? for security reasons??? 330 fetchHandler(this._items, request); 331 })); 236 332 xhrHandler.addErrback(function(error){ 237 333 errorHandler(error, request); 238 334 }); 239 this.lastRequestTimestamp = new Date().getTime(); 335 // Generate the hash using the time in milliseconds and a randon number. 336 // Since Math.randon() returns something like: 0.23453463, we just remove the "0." 337 // probably just for esthetic reasons :-). 338 this.lastRequestHash = new Date().getTime()+"-"+String(Math.random()).substring(2); 240 339 this._lastServerQuery = serverQuery; 241 340 } 242 341 }, … … 260 359 if(typeof attribute !== "string"){ 261 360 throw new dojox.data.QueryReadStore.InvalidAttributeError(this._className+": '"+attribute+"' is not a valid attribute identifier."); 262 361 } 362 }, 363 364 fetchItemByIdentity: function(/* Object */ identity){ 365 // summary: 366 // Internal function to look an item up by its identity map. 367 var item = null; 368 if(this._itemsByIdentity){ 369 item = this._itemsByIdentity[identity]; 370 if(item === undefined){ 371 item = null; 372 } 373 } 374 return item; // Object 375 }, 376 377 getIdentity: function(/* item */ item){ 378 // summary: 379 // See dojo.data.api.Identity.getIdentity() 380 if(this._identifier === Number){ 381 return item.n; // Number 382 }else{ 383 return item.i[this._identifier]; 384 } 385 return null; // null 386 }, 387 388 getIdentityAttributes: function(/* item */ item){ 389 // summary: 390 // See dojo.data.api.Identity.getIdentityAttributes() 391 return [this._identifier]; 263 392 } 264 393 }); 265 dojo.extend(dojox.data.QueryReadStore, dojo.data.util.simpleFetch);266 394 267 395 dojo.declare("dojox.data.QueryReadStore.InvalidItemError", Error, {}); 268 396 dojo.declare("dojox.data.QueryReadStore.InvalidAttributeError", Error, {});