Ticket #4761: dojoQueryReadStore-head-fix4761.patch

File dojoQueryReadStore-head-fix4761.patch, 15.4 kB (added by BlueFire, 15 months ago)

This includes wolfram's fix and support for the Identity API, thus allowing FilteringSelect? to use the store

  • tests/stores/QueryReadStore.js

     
    239239                        //      description: 
    240240                        var store = dojox.data.tests.stores.QueryReadStore.getStore(); 
    241241 
    242                         var lastRequestTimestamp = null; 
     242                        var lastRequestHash = null; 
    243243                        var firstItems = []; 
    244244                        var d = new doh.Deferred(); 
    245245                        function onComplete(items, request) { 
    246246                                t.assertEqual(5, items.length); 
    247                                 lastRequestTimestamp = store.lastRequestTimestamp; 
     247                                lastRequestHash = store.lastRequestHash; 
    248248                                firstItems = items; 
    249249                                 
    250250                                // Do the next request AFTER the previous one, so we are sure its sequential. 
    251251                                // We need to be sure so we can compare to the data from the first request. 
    252252                                function onComplete1(items, request) { 
    253253                                        t.assertEqual(5, items.length); 
    254                                         t.assertEqual(lastRequestTimestamp, store.lastRequestTimestamp); 
     254                                        t.assertEqual(lastRequestHash, store.lastRequestHash); 
    255255                                        t.assertEqual(firstItems[1], items[0]); 
    256256                                        d.callback(true); 
    257257                                } 
     
    268268                        return d; //Object 
    269269                }, 
    270270                 
    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 
    274312                }, 
    275313                 
    276314                function testReadApi_getFeatures(t) { 
  • tests/module.js

     
    77        dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.XmlStore"); 
    88        dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.FlickrStore"); 
    99        dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.FlickrRestStore"); 
     10        dojo.requireIf(dojo.isBrowser, "dojox.data.tests.stores.QueryReadStore"); 
    1011        dojo.requireIf(dojo.isBrowser, "dojox.data.tests.dom"); 
    1112}catch(e){ 
    1213        doh.debug(e); 
  • tests/QueryReadStore.html

     
    1515        <script type="text/javascript" src="../../../dojox/data/QueryReadStore.js"></script> 
    1616        <script type="text/javascript"> 
    1717                dojo.require("dijit.form.ComboBox"); 
     18                dojo.require("dijit.form.FilteringSelect"); 
    1819                dojo.require("dojox.data.QueryReadStore"); 
    1920 
    2021                dojo.provide("ComboBoxReadStore"); 
     
    6263                        } 
    6364                }); 
    6465 
    65                 var testStore = new ComboBoxReadStore({url:'stores/QueryReadStore.php'});; 
     66                var testStore = new dojox.data.QueryReadStore({url:'stores/QueryReadStore.php'}); 
    6667                function doSearch() { 
    6768                        var queryOptions = {}; 
    6869                        if (dojo.byId("ignoreCaseEnabled").checked) { 
     
    7374                        } 
    7475                     
    7576                        var query = {}; 
    76                         query.name = dojo.byId("searchText").value; 
     77                        query.q = dojo.byId("searchText").value; 
    7778                        var request = {query:query, queryOptions:queryOptions}; 
    7879                        request.start = dojo.query("#fetchForm")[0].pagingStart.value; 
    7980                        request.count = dojo.query("#fetchForm")[0].pagingCount.value; 
     
    8485                                if (radioButtons[i].checked) { 
    8586                                        requestMethod = radioButtons[i].value; 
    8687                                } 
    87                         }        
    88                  
     88                        } 
     89                         
    8990                        testStore.requestMethod = requestMethod; 
    9091                        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) { 
    92100                                var s = "number of items: "+items.length+"<br /><br />"; 
    93101                                for (var i=0; i<items.length; i++) { 
    94102                                        s += i+": name: '"+testStore.getValue(items[i], "name")+"'<br />"; 
     
    96104                                //s += "<pre>"+dojo.toJson(items)+"</pre>"; 
    97105                                dojo.byId("fetchOutput").innerHTML = s; 
    98106                        }; 
    99                         request.onComplete = onComplete; 
     107 
    100108                        testStore.fetch(request); 
    101109                } 
    102110        </script> 
    103111</head> 
    104112<body class="tundra" style="margin:20px;"> 
    105113        <div dojoType="ComboBoxReadStore" jsId="store" url="stores/QueryReadStore.php" requestMethod="get"></div> 
    106         <input dojoType="dijit.form.ComboBox" store="store" pageSize="5" /> 
     114        This is a ComboBox: <input id="cb" dojoType="dijit.form.ComboBox" store="store" pageSize="5" /> 
    107115        <br /><br /><hr /> 
    108116 
     117        This is a FilteringSelect: <input id="fs" dojoType="dijit.form.FilteringSelect" store="store" pageSize="5" /> 
     118        <br /><br /><hr /> 
     119 
    109120        This ComboBox uses a customized QueryReadStore, it prepares the query-string for the URL that 
    110121        way that the paging parameters "start" and "count" are also send.<br /> 
    111122        <div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"></div> 
     
    119130&lt;div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"&gt;&lt;/div&gt; 
    120131&lt;input dojoType="dijit.form.ComboBox" store="serverPagingStore" pageSize="10" /&gt; 
    121132        </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> 
    131143</div> 
    132144        <br /><br /> 
    133145         
  • QueryReadStore.js

     
    6161        _lastServerQuery:null, 
    6262         
    6363         
    64         // Store a timestamp of the last server request. Actually I introduced this 
     64        // Store a hash of the last server request. Actually I introduced this 
    6565        // 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         lastRequestTimestamp:null, 
     66        // client-side-paging. 
     67        lastRequestHash:null, 
    6868         
    6969        // If this is false, every request is sent to the server. 
    7070        // If it's true a second request with the same query will not issue another 
    7171        // request, but use the already returned data. This assumes that the server 
    7272        // does not do the paging. 
    7373        doClientPaging:true, 
     74 
     75        // Items by identify for Identify API 
     76        _itemsByIdentity:null, 
    7477         
     78        // Identifier used 
     79        _identifier:null, 
     80 
     81        _features: {'dojo.data.api.Read':true, 'dojo.data.api.Identity':true}, 
     82         
    7583        constructor: function(/* Object */ params){ 
    7684                this.url = params.url; 
    7785                this.requestMethod = typeof params.requestMethod=="undefined" ? this.requestMethod : params.requestMethod; 
     
    7987                //this.useCache = typeof params.useCache=="undefined" ? this.useCache : params.useCache; 
    8088        }, 
    8189         
    82         getValue: function(     /* item */ item, /* attribute-name-string */ attribute, /* value? */ defaultValue){ 
     90        getValue: function(/* item */ item, /* attribute-name-string */ attribute, /* value? */ defaultValue){ 
    8391                this._assertIsItem(item); 
    8492                if(!this.hasAttribute(item, attribute)){ 
    8593                        // read api says: return defaultValue "only if *item* does not have a value for *attribute*."  
     
    162170                // Actually we have nothing to do here, or at least I dont know what to do here ... 
    163171        }, 
    164172 
     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 
    165241        getFeatures: function(){ 
    166242                return { 
    167243                        'dojo.data.api.Read': true 
     
    220296                }else{ 
    221297                        var xhrFunc = this.requestMethod.toLowerCase()=="post" ? dojo.xhrPost : dojo.xhrGet; 
    222298                        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 = []; 
    226301                                // Store a ref to "this" in each item, so we can simply check if an item 
    227302                                // really origins form here (idea is from ItemFileReadStore, I just don't know 
    228303                                // how efficient the real storage use, garbage collection effort, etc. is). 
    229304                                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}); 
    231306                                } 
    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                        })); 
    236332                        xhrHandler.addErrback(function(error){ 
    237333                                errorHandler(error, request); 
    238334                        }); 
    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); 
    240339                        this._lastServerQuery = serverQuery; 
    241340                } 
    242341        }, 
     
    260359                if(typeof attribute !== "string"){  
    261360                        throw new dojox.data.QueryReadStore.InvalidAttributeError(this._className+": '"+attribute+"' is not a valid attribute identifier."); 
    262361                } 
     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]; 
    263392        } 
    264393}); 
    265 dojo.extend(dojox.data.QueryReadStore, dojo.data.util.simpleFetch); 
    266394 
    267395dojo.declare("dojox.data.QueryReadStore.InvalidItemError", Error, {}); 
    268396dojo.declare("dojox.data.QueryReadStore.InvalidAttributeError", Error, {});