source: Dev/trunk/src/client/dojo/store/Observable.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 6.7 KB
Line 
1define(["../_base/kernel", "../_base/lang", "../when", "../_base/array" /*=====, "./api/Store" =====*/
2], function(kernel, lang, when, array /*=====, Store =====*/){
3
4// module:
5//              dojo/store/Observable
6
7var Observable = function(/*Store*/ store){
8        // summary:
9        //              The Observable store wrapper takes a store and sets an observe method on query()
10        //              results that can be used to monitor results for changes.
11        //
12        // description:
13        //              Observable wraps an existing store so that notifications can be made when a query
14        //              is performed.
15        //
16        // example:
17        //              Create a Memory store that returns an observable query, and then log some
18        //              information about that query.
19        //
20        //      |       var store = Observable(new Memory({
21        //      |               data: [
22        //      |                       {id: 1, name: "one", prime: false},
23        //      |                       {id: 2, name: "two", even: true, prime: true},
24        //      |                       {id: 3, name: "three", prime: true},
25        //      |                       {id: 4, name: "four", even: true, prime: false},
26        //      |                       {id: 5, name: "five", prime: true}
27        //      |               ]
28        //      |       }));
29        //      |       var changes = [], results = store.query({ prime: true });
30        //      |       var observer = results.observe(function(object, previousIndex, newIndex){
31        //      |               changes.push({previousIndex:previousIndex, newIndex:newIndex, object:object});
32        //      |       });
33        //
34        //              See the Observable tests for more information.
35
36        var undef, queryUpdaters = [], revision = 0;
37        // a Comet driven store could directly call notify to notify observers when data has
38        // changed on the backend
39        // create a new instance
40        store = lang.delegate(store);
41       
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                                                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){
109                                                                // we don't have a queryEngine, so we can't provide any information
110                                                                // about where it was inserted or moved to. If it is an update, we leave it's position alone, other we at least indicate a new object
111                                                                if(existingId !== undef){
112                                                                        // an update, keep the index the same
113                                                                        insertedInto = removedFrom;
114                                                                }else if(!options.start){
115                                                                        // a new object
116                                                                        insertedInto = store.defaultIndex || 0;
117                                                                        resultsArray.splice(insertedInto, 0, changed);
118                                                                }
119                                                        }
120                                                        if((removedFrom > -1 || insertedInto > -1) &&
121                                                                        (includeObjectUpdates || !queryExecutor || (removedFrom != insertedInto))){
122                                                                var copyListeners = listeners.slice();
123                                                                for(i = 0;listener = copyListeners[i]; i++){
124                                                                        listener(changed || removedObject, removedFrom, insertedInto);
125                                                                }
126                                                        }
127                                                });
128                                        });
129                                }
130                                var handle = {};
131                                // TODO: Remove cancel in 2.0.
132                                handle.remove = handle.cancel = function(){
133                                        // remove this listener
134                                        var index = array.indexOf(listeners, listener);
135                                        if(index > -1){ // check to make sure we haven't already called cancel
136                                                listeners.splice(index, 1);
137                                                if(!listeners.length){
138                                                        // no more listeners, remove the query updater too
139                                                        queryUpdaters.splice(array.indexOf(queryUpdaters, queryUpdater), 1);
140                                                }
141                                        }
142                                };
143                                return handle;
144                        };
145                }
146                return results;
147        };
148        var inMethod;
149        function whenFinished(method, action){
150                var original = store[method];
151                if(original){
152                        store[method] = function(value){
153                                if(inMethod){
154                                        // if one method calls another (like add() calling put()) we don't want two events
155                                        return original.apply(this, arguments);
156                                }
157                                inMethod = true;
158                                try{
159                                        var results = original.apply(this, arguments);
160                                        when(results, function(results){
161                                                action((typeof results == "object" && results) || value);
162                                        });
163                                        return results;
164                                }finally{
165                                        inMethod = false;
166                                }
167                        };
168                }
169        }
170        // monitor for updates by listening to these methods
171        whenFinished("put", function(object){
172                store.notify(object, store.getIdentity(object));
173        });
174        whenFinished("add", function(object){
175                store.notify(object);
176        });
177        whenFinished("remove", function(id){
178                store.notify(undefined, id);
179        });
180
181        return store;
182};
183
184lang.setObject("dojo.store.Observable", Observable);
185
186return Observable;
187});
Note: See TracBrowser for help on using the repository browser.