source: Dev/branches/rest-dojo-ui/client/dojo/store/Observable.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 6.4 KB
Line 
1define(["../_base/kernel", "../_base/lang", "../_base/Deferred", "../_base/array"
2], function(kernel, lang, Deferred, array) {
3        // module:
4        //              dojo/store/Observable
5        // summary:
6        //              TODOC
7
8var ds = lang.getObject("dojo.store", true);
9
10return ds.Observable = function(store){
11        // summary:
12        //              The Observable store wrapper takes a store and sets an observe method on query()
13        //              results that can be used to monitor results for changes.
14        //
15        // description:
16        //              Observable wraps an existing store so that notifications can be made when a query
17        //              is performed.
18        //
19        // example:
20        //              Create a Memory store that returns an observable query, and then log some
21        //              information about that query.
22        //
23        //      |       var store = dojo.store.Observable(new dojo.store.Memory({
24        //      |               data: [
25        //      |                       {id: 1, name: "one", prime: false},
26        //      |                       {id: 2, name: "two", even: true, prime: true},
27        //      |                       {id: 3, name: "three", prime: true},
28        //      |                       {id: 4, name: "four", even: true, prime: false},
29        //      |                       {id: 5, name: "five", prime: true}
30        //      |               ]
31        //      |       }));
32        //      |       var changes = [], results = store.query({ prime: true });
33        //      |       var observer = results.observe(function(object, previousIndex, newIndex){
34        //      |               changes.push({previousIndex:previousIndex, newIndex:newIndex, object:object});
35        //      |       });
36        //
37        //              See the Observable tests for more information.
38
39        var undef, queryUpdaters = [], revision = 0;
40        // a Comet driven store could directly call notify to notify observers when data has
41        // changed on the backend
42        store.notify = function(object, existingId){
43                revision++;
44                var updaters = queryUpdaters.slice();
45                for(var i = 0, l = updaters.length; i < l; i++){
46                        updaters[i](object, existingId);
47                }
48        };
49        var originalQuery = store.query;
50        store.query = function(query, options){
51                options = options || {};
52                var results = originalQuery.apply(this, arguments);
53                if(results && results.forEach){
54                        var nonPagedOptions = lang.mixin({}, options);
55                        delete nonPagedOptions.start;
56                        delete nonPagedOptions.count;
57
58                        var queryExecutor = store.queryEngine && store.queryEngine(query, nonPagedOptions);
59                        var queryRevision = revision;
60                        var listeners = [], queryUpdater;
61                        results.observe = function(listener, includeObjectUpdates){
62                                if(listeners.push(listener) == 1){
63                                        // first listener was added, create the query checker and updater
64                                        queryUpdaters.push(queryUpdater = function(changed, existingId){
65                                                Deferred.when(results, function(resultsArray){
66                                                        var atEnd = resultsArray.length != options.count;
67                                                        var i, l, listener;
68                                                        if(++queryRevision != revision){
69                                                                throw new Error("Query is out of date, you must observe() the query prior to any data modifications");
70                                                        }
71                                                        var removedObject, removedFrom = -1, insertedInto = -1;
72                                                        if(existingId !== undef){
73                                                                // remove the old one
74                                                                for(i = 0, l = resultsArray.length; i < l; i++){
75                                                                        var object = resultsArray[i];
76                                                                        if(store.getIdentity(object) == existingId){
77                                                                                removedObject = object;
78                                                                                removedFrom = i;
79                                                                                if(queryExecutor || !changed){// if it was changed and we don't have a queryExecutor, we shouldn't remove it because updated objects would be eliminated
80                                                                                        resultsArray.splice(i, 1);
81                                                                                }
82                                                                                break;
83                                                                        }
84                                                                }
85                                                        }
86                                                        if(queryExecutor){
87                                                                // add the new one
88                                                                if(changed &&
89                                                                                // if a matches function exists, use that (probably more efficient)
90                                                                                (queryExecutor.matches ? queryExecutor.matches(changed) : queryExecutor([changed]).length)){
91
92                                                                        var firstInsertedInto = removedFrom > -1 ?
93                                                                                removedFrom : // put back in the original slot so it doesn't move unless it needs to (relying on a stable sort below)
94                                                                                resultsArray.length;
95                                                                        resultsArray.splice(firstInsertedInto, 0, changed); // add the new item
96                                                                        insertedInto = array.indexOf(queryExecutor(resultsArray), changed); // sort it
97                                                                        // we now need to push the chagne back into the original results array
98                                                                        resultsArray.splice(firstInsertedInto, 1); // remove the inserted item from the previous index
99                                                                       
100                                                                        if((options.start && insertedInto == 0) ||
101                                                                                (!atEnd && insertedInto == resultsArray.length)){
102                                                                                // if it is at the end of the page, assume it goes into the prev or next page
103                                                                                insertedInto = -1;
104                                                                        }else{
105                                                                                resultsArray.splice(insertedInto, 0, changed); // and insert into the results array with the correct index
106                                                                        }
107                                                                }
108                                                        }else if(changed && !options.start){
109                                                                // we don't have a queryEngine, so we can't provide any information
110                                                                // about where it was inserted, but we can at least indicate a new object
111                                                                insertedInto = removedFrom >= 0 ? removedFrom : (store.defaultIndex || 0);
112                                                        }
113                                                        if((removedFrom > -1 || insertedInto > -1) &&
114                                                                        (includeObjectUpdates || !queryExecutor || (removedFrom != insertedInto))){
115                                                                var copyListeners = listeners.slice();
116                                                                for(i = 0;listener = copyListeners[i]; i++){
117                                                                        listener(changed || removedObject, removedFrom, insertedInto);
118                                                                }
119                                                        }
120                                                });
121                                        });
122                                }
123                                return {
124                                        cancel: function(){
125                                                // remove this listener
126                                                var index = array.indexOf(listeners, listener);
127                                                if(index > -1){ // check to make sure we haven't already called cancel
128                                                        listeners.splice(index, 1);
129                                                        if(!listeners.length){
130                                                                // no more listeners, remove the query updater too
131                                                                queryUpdaters.splice(array.indexOf(queryUpdaters, queryUpdater), 1);
132                                                        }
133                                                }                                               
134                                        }
135                                };
136                        };
137                }
138                return results;
139        };
140        var inMethod;
141        function whenFinished(method, action){
142                var original = store[method];
143                if(original){
144                        store[method] = function(value){
145                                if(inMethod){
146                                        // if one method calls another (like add() calling put()) we don't want two events
147                                        return original.apply(this, arguments);
148                                }
149                                inMethod = true;
150                                try{
151                                        var results = original.apply(this, arguments);
152                                        Deferred.when(results, function(results){
153                                                action((typeof results == "object" && results) || value);
154                                        });
155                                        return results;
156                                }finally{
157                                        inMethod = false;
158                                }
159                        };
160                }
161        }
162        // monitor for updates by listening to these methods
163        whenFinished("put", function(object){
164                store.notify(object, store.getIdentity(object));
165        });
166        whenFinished("add", function(object){
167                store.notify(object);
168        });
169        whenFinished("remove", function(id){
170                store.notify(undefined, id);
171        });
172
173        return store;
174};
175});
Note: See TracBrowser for help on using the repository browser.