Ticket #4761: dojox.data.QueryReadStore_20071026.patch

File dojox.data.QueryReadStore_20071026.patch, 17.6 kB (added by BlueFire, 15 months ago)

Fixed fetchItemByIdentity

  • data/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) { 
  • data/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); 
  • data/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 /> 
     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 
    109126        This ComboBox uses a customized QueryReadStore, it prepares the query-string for the URL that 
    110127        way that the paging parameters "start" and "count" are also send.<br /> 
    111128        <div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"></div> 
     
    119136&lt;div dojoType="ServerPagingReadStore" jsId="serverPagingStore" url="stores/QueryReadStore.php" requestMethod="get" doClientPaging="false"&gt;&lt;/div&gt; 
    120137&lt;input dojoType="dijit.form.ComboBox" store="serverPagingStore" pageSize="10" /&gt; 
    121138        </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> 
    131149</div> 
    132150        <br /><br /> 
    133151         
  • data/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 
     
    211287                // 
    212288 
    213289                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                } 
    214304                // Compare the last query and the current query by simply json-encoding them, 
    215305                // so we dont have to do any deep object compare ... is there some dojo.areObjectsEqual()??? 
    216306                if(this.doClientPaging && this._lastServerQuery!==null && 
     
    220310                }else{ 
    221311                        var xhrFunc = this.requestMethod.toLowerCase()=="post" ? dojo.xhrPost : dojo.xhrGet; 
    222312                        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 = []; 
    226315                                // Store a ref to "this" in each item, so we can simply check if an item 
    227316                                // really origins form here (idea is from ItemFileReadStore, I just don't know 
    228317                                // how efficient the real storage use, garbage collection effort, etc. is). 
    229318                                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}); 
    231320                                } 
    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                        })); 
    236346                        xhrHandler.addErrback(function(error){ 
    237347                                errorHandler(error, request); 
    238348                        }); 
    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); 
    240353                        this._lastServerQuery = serverQuery; 
    241354                } 
    242355        }, 
     
    260373                if(typeof attribute !== "string"){  
    261374                        throw new dojox.data.QueryReadStore.InvalidAttributeError(this._className+": '"+attribute+"' is not a valid attribute identifier."); 
    262375                } 
     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]; 
    263449        } 
    264450}); 
    265 dojo.extend(dojox.data.QueryReadStore, dojo.data.util.simpleFetch); 
    266451 
    267452dojo.declare("dojox.data.QueryReadStore.InvalidItemError", Error, {}); 
    268453dojo.declare("dojox.data.QueryReadStore.InvalidAttributeError", Error, {});