]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/store/Observable.js.uncompressed.js
make precache_headlines_idle() start slower
[tt-rss.git] / lib / dojo / store / Observable.js.uncompressed.js
1 define("dojo/store/Observable", ["../_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
8 var ds = lang.getObject("dojo.store", true);
9
10 return 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 });