Update JSON API
[src/app-framework-demo.git] / afm-client / bower_components / angular-ui-router / src / resolve.js
1 /**
2  * @ngdoc object
3  * @name ui.router.util.$resolve
4  *
5  * @requires $q
6  * @requires $injector
7  *
8  * @description
9  * Manages resolution of (acyclic) graphs of promises.
10  */
11 $Resolve.$inject = ['$q', '$injector'];
12 function $Resolve(  $q,    $injector) {
13   
14   var VISIT_IN_PROGRESS = 1,
15       VISIT_DONE = 2,
16       NOTHING = {},
17       NO_DEPENDENCIES = [],
18       NO_LOCALS = NOTHING,
19       NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });
20   
21
22   /**
23    * @ngdoc function
24    * @name ui.router.util.$resolve#study
25    * @methodOf ui.router.util.$resolve
26    *
27    * @description
28    * Studies a set of invocables that are likely to be used multiple times.
29    * <pre>
30    * $resolve.study(invocables)(locals, parent, self)
31    * </pre>
32    * is equivalent to
33    * <pre>
34    * $resolve.resolve(invocables, locals, parent, self)
35    * </pre>
36    * but the former is more efficient (in fact `resolve` just calls `study` 
37    * internally).
38    *
39    * @param {object} invocables Invocable objects
40    * @return {function} a function to pass in locals, parent and self
41    */
42   this.study = function (invocables) {
43     if (!isObject(invocables)) throw new Error("'invocables' must be an object");
44     var invocableKeys = objectKeys(invocables || {});
45     
46     // Perform a topological sort of invocables to build an ordered plan
47     var plan = [], cycle = [], visited = {};
48     function visit(value, key) {
49       if (visited[key] === VISIT_DONE) return;
50       
51       cycle.push(key);
52       if (visited[key] === VISIT_IN_PROGRESS) {
53         cycle.splice(0, indexOf(cycle, key));
54         throw new Error("Cyclic dependency: " + cycle.join(" -> "));
55       }
56       visited[key] = VISIT_IN_PROGRESS;
57       
58       if (isString(value)) {
59         plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
60       } else {
61         var params = $injector.annotate(value);
62         forEach(params, function (param) {
63           if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
64         });
65         plan.push(key, value, params);
66       }
67       
68       cycle.pop();
69       visited[key] = VISIT_DONE;
70     }
71     forEach(invocables, visit);
72     invocables = cycle = visited = null; // plan is all that's required
73     
74     function isResolve(value) {
75       return isObject(value) && value.then && value.$$promises;
76     }
77     
78     return function (locals, parent, self) {
79       if (isResolve(locals) && self === undefined) {
80         self = parent; parent = locals; locals = null;
81       }
82       if (!locals) locals = NO_LOCALS;
83       else if (!isObject(locals)) {
84         throw new Error("'locals' must be an object");
85       }       
86       if (!parent) parent = NO_PARENT;
87       else if (!isResolve(parent)) {
88         throw new Error("'parent' must be a promise returned by $resolve.resolve()");
89       }
90       
91       // To complete the overall resolution, we have to wait for the parent
92       // promise and for the promise for each invokable in our plan.
93       var resolution = $q.defer(),
94           result = resolution.promise,
95           promises = result.$$promises = {},
96           values = extend({}, locals),
97           wait = 1 + plan.length/3,
98           merged = false;
99           
100       function done() {
101         // Merge parent values we haven't got yet and publish our own $$values
102         if (!--wait) {
103           if (!merged) merge(values, parent.$$values); 
104           result.$$values = values;
105           result.$$promises = result.$$promises || true; // keep for isResolve()
106           delete result.$$inheritedValues;
107           resolution.resolve(values);
108         }
109       }
110       
111       function fail(reason) {
112         result.$$failure = reason;
113         resolution.reject(reason);
114       }
115
116       // Short-circuit if parent has already failed
117       if (isDefined(parent.$$failure)) {
118         fail(parent.$$failure);
119         return result;
120       }
121       
122       if (parent.$$inheritedValues) {
123         merge(values, omit(parent.$$inheritedValues, invocableKeys));
124       }
125
126       // Merge parent values if the parent has already resolved, or merge
127       // parent promises and wait if the parent resolve is still in progress.
128       extend(promises, parent.$$promises);
129       if (parent.$$values) {
130         merged = merge(values, omit(parent.$$values, invocableKeys));
131         result.$$inheritedValues = omit(parent.$$values, invocableKeys);
132         done();
133       } else {
134         if (parent.$$inheritedValues) {
135           result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
136         }        
137         parent.then(done, fail);
138       }
139       
140       // Process each invocable in the plan, but ignore any where a local of the same name exists.
141       for (var i=0, ii=plan.length; i<ii; i+=3) {
142         if (locals.hasOwnProperty(plan[i])) done();
143         else invoke(plan[i], plan[i+1], plan[i+2]);
144       }
145       
146       function invoke(key, invocable, params) {
147         // Create a deferred for this invocation. Failures will propagate to the resolution as well.
148         var invocation = $q.defer(), waitParams = 0;
149         function onfailure(reason) {
150           invocation.reject(reason);
151           fail(reason);
152         }
153         // Wait for any parameter that we have a promise for (either from parent or from this
154         // resolve; in that case study() will have made sure it's ordered before us in the plan).
155         forEach(params, function (dep) {
156           if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
157             waitParams++;
158             promises[dep].then(function (result) {
159               values[dep] = result;
160               if (!(--waitParams)) proceed();
161             }, onfailure);
162           }
163         });
164         if (!waitParams) proceed();
165         function proceed() {
166           if (isDefined(result.$$failure)) return;
167           try {
168             invocation.resolve($injector.invoke(invocable, self, values));
169             invocation.promise.then(function (result) {
170               values[key] = result;
171               done();
172             }, onfailure);
173           } catch (e) {
174             onfailure(e);
175           }
176         }
177         // Publish promise synchronously; invocations further down in the plan may depend on it.
178         promises[key] = invocation.promise;
179       }
180       
181       return result;
182     };
183   };
184   
185   /**
186    * @ngdoc function
187    * @name ui.router.util.$resolve#resolve
188    * @methodOf ui.router.util.$resolve
189    *
190    * @description
191    * Resolves a set of invocables. An invocable is a function to be invoked via 
192    * `$injector.invoke()`, and can have an arbitrary number of dependencies. 
193    * An invocable can either return a value directly,
194    * or a `$q` promise. If a promise is returned it will be resolved and the 
195    * resulting value will be used instead. Dependencies of invocables are resolved 
196    * (in this order of precedence)
197    *
198    * - from the specified `locals`
199    * - from another invocable that is part of this `$resolve` call
200    * - from an invocable that is inherited from a `parent` call to `$resolve` 
201    *   (or recursively
202    * - from any ancestor `$resolve` of that parent).
203    *
204    * The return value of `$resolve` is a promise for an object that contains 
205    * (in this order of precedence)
206    *
207    * - any `locals` (if specified)
208    * - the resolved return values of all injectables
209    * - any values inherited from a `parent` call to `$resolve` (if specified)
210    *
211    * The promise will resolve after the `parent` promise (if any) and all promises 
212    * returned by injectables have been resolved. If any invocable 
213    * (or `$injector.invoke`) throws an exception, or if a promise returned by an 
214    * invocable is rejected, the `$resolve` promise is immediately rejected with the 
215    * same error. A rejection of a `parent` promise (if specified) will likewise be 
216    * propagated immediately. Once the `$resolve` promise has been rejected, no 
217    * further invocables will be called.
218    * 
219    * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
220    * to throw an error. As a special case, an injectable can depend on a parameter 
221    * with the same name as the injectable, which will be fulfilled from the `parent` 
222    * injectable of the same name. This allows inherited values to be decorated. 
223    * Note that in this case any other injectable in the same `$resolve` with the same
224    * dependency would see the decorated value, not the inherited value.
225    *
226    * Note that missing dependencies -- unlike cyclic dependencies -- will cause an 
227    * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous) 
228    * exception.
229    *
230    * Invocables are invoked eagerly as soon as all dependencies are available. 
231    * This is true even for dependencies inherited from a `parent` call to `$resolve`.
232    *
233    * As a special case, an invocable can be a string, in which case it is taken to 
234    * be a service name to be passed to `$injector.get()`. This is supported primarily 
235    * for backwards-compatibility with the `resolve` property of `$routeProvider` 
236    * routes.
237    *
238    * @param {object} invocables functions to invoke or 
239    * `$injector` services to fetch.
240    * @param {object} locals  values to make available to the injectables
241    * @param {object} parent  a promise returned by another call to `$resolve`.
242    * @param {object} self  the `this` for the invoked methods
243    * @return {object} Promise for an object that contains the resolved return value
244    * of all invocables, as well as any inherited and local values.
245    */
246   this.resolve = function (invocables, locals, parent, self) {
247     return this.study(invocables)(locals, parent, self);
248   };
249 }
250
251 angular.module('ui.router.util').service('$resolve', $Resolve);
252