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