]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/store/Observable.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dojo / store / Observable.js.uncompressed.js
CommitLineData
f0cfe83e
AD
1define("dojo/store/Observable", ["../_base/kernel", "../_base/lang", "../_base/Deferred", "../_base/array" /*=====, "./api/Store" =====*/
2], function(kernel, lang, Deferred, 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 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){
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 Deferred.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});