Update JSON API
[src/app-framework-demo.git] / afm-client / bower_components / angular-ui-router / src / urlRouter.js
1 /**
2  * @ngdoc object
3  * @name ui.router.router.$urlRouterProvider
4  *
5  * @requires ui.router.util.$urlMatcherFactoryProvider
6  * @requires $locationProvider
7  *
8  * @description
9  * `$urlRouterProvider` has the responsibility of watching `$location`. 
10  * When `$location` changes it runs through a list of rules one by one until a 
11  * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 
12  * a url in a state configuration. All urls are compiled into a UrlMatcher object.
13  *
14  * There are several methods on `$urlRouterProvider` that make it useful to use directly
15  * in your module config.
16  */
17 $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
18 function $UrlRouterProvider(   $locationProvider,   $urlMatcherFactory) {
19   var rules = [], otherwise = null, interceptDeferred = false, listener;
20
21   // Returns a string that is a prefix of all strings matching the RegExp
22   function regExpPrefix(re) {
23     var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
24     return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
25   }
26
27   // Interpolates matched values into a String.replace()-style pattern
28   function interpolate(pattern, match) {
29     return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
30       return match[what === '$' ? 0 : Number(what)];
31     });
32   }
33
34   /**
35    * @ngdoc function
36    * @name ui.router.router.$urlRouterProvider#rule
37    * @methodOf ui.router.router.$urlRouterProvider
38    *
39    * @description
40    * Defines rules that are used by `$urlRouterProvider` to find matches for
41    * specific URLs.
42    *
43    * @example
44    * <pre>
45    * var app = angular.module('app', ['ui.router.router']);
46    *
47    * app.config(function ($urlRouterProvider) {
48    *   // Here's an example of how you might allow case insensitive urls
49    *   $urlRouterProvider.rule(function ($injector, $location) {
50    *     var path = $location.path(),
51    *         normalized = path.toLowerCase();
52    *
53    *     if (path !== normalized) {
54    *       return normalized;
55    *     }
56    *   });
57    * });
58    * </pre>
59    *
60    * @param {function} rule Handler function that takes `$injector` and `$location`
61    * services as arguments. You can use them to return a valid path as a string.
62    *
63    * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
64    */
65   this.rule = function (rule) {
66     if (!isFunction(rule)) throw new Error("'rule' must be a function");
67     rules.push(rule);
68     return this;
69   };
70
71   /**
72    * @ngdoc object
73    * @name ui.router.router.$urlRouterProvider#otherwise
74    * @methodOf ui.router.router.$urlRouterProvider
75    *
76    * @description
77    * Defines a path that is used when an invalid route is requested.
78    *
79    * @example
80    * <pre>
81    * var app = angular.module('app', ['ui.router.router']);
82    *
83    * app.config(function ($urlRouterProvider) {
84    *   // if the path doesn't match any of the urls you configured
85    *   // otherwise will take care of routing the user to the
86    *   // specified url
87    *   $urlRouterProvider.otherwise('/index');
88    *
89    *   // Example of using function rule as param
90    *   $urlRouterProvider.otherwise(function ($injector, $location) {
91    *     return '/a/valid/url';
92    *   });
93    * });
94    * </pre>
95    *
96    * @param {string|function} rule The url path you want to redirect to or a function 
97    * rule that returns the url path. The function version is passed two params: 
98    * `$injector` and `$location` services, and must return a url string.
99    *
100    * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
101    */
102   this.otherwise = function (rule) {
103     if (isString(rule)) {
104       var redirect = rule;
105       rule = function () { return redirect; };
106     }
107     else if (!isFunction(rule)) throw new Error("'rule' must be a function");
108     otherwise = rule;
109     return this;
110   };
111
112
113   function handleIfMatch($injector, handler, match) {
114     if (!match) return false;
115     var result = $injector.invoke(handler, handler, { $match: match });
116     return isDefined(result) ? result : true;
117   }
118
119   /**
120    * @ngdoc function
121    * @name ui.router.router.$urlRouterProvider#when
122    * @methodOf ui.router.router.$urlRouterProvider
123    *
124    * @description
125    * Registers a handler for a given url matching. 
126    * 
127    * If the handler is a string, it is
128    * treated as a redirect, and is interpolated according to the syntax of match
129    * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
130    *
131    * If the handler is a function, it is injectable. It gets invoked if `$location`
132    * matches. You have the option of inject the match object as `$match`.
133    *
134    * The handler can return
135    *
136    * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
137    *   will continue trying to find another one that matches.
138    * - **string** which is treated as a redirect and passed to `$location.url()`
139    * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
140    *
141    * @example
142    * <pre>
143    * var app = angular.module('app', ['ui.router.router']);
144    *
145    * app.config(function ($urlRouterProvider) {
146    *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
147    *     if ($state.$current.navigable !== state ||
148    *         !equalForKeys($match, $stateParams) {
149    *      $state.transitionTo(state, $match, false);
150    *     }
151    *   });
152    * });
153    * </pre>
154    *
155    * @param {string|object} what The incoming path that you want to redirect.
156    * @param {string|function} handler The path you want to redirect your user to.
157    */
158   this.when = function (what, handler) {
159     var redirect, handlerIsString = isString(handler);
160     if (isString(what)) what = $urlMatcherFactory.compile(what);
161
162     if (!handlerIsString && !isFunction(handler) && !isArray(handler))
163       throw new Error("invalid 'handler' in when()");
164
165     var strategies = {
166       matcher: function (what, handler) {
167         if (handlerIsString) {
168           redirect = $urlMatcherFactory.compile(handler);
169           handler = ['$match', function ($match) { return redirect.format($match); }];
170         }
171         return extend(function ($injector, $location) {
172           return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
173         }, {
174           prefix: isString(what.prefix) ? what.prefix : ''
175         });
176       },
177       regex: function (what, handler) {
178         if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
179
180         if (handlerIsString) {
181           redirect = handler;
182           handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
183         }
184         return extend(function ($injector, $location) {
185           return handleIfMatch($injector, handler, what.exec($location.path()));
186         }, {
187           prefix: regExpPrefix(what)
188         });
189       }
190     };
191
192     var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
193
194     for (var n in check) {
195       if (check[n]) return this.rule(strategies[n](what, handler));
196     }
197
198     throw new Error("invalid 'what' in when()");
199   };
200
201   /**
202    * @ngdoc function
203    * @name ui.router.router.$urlRouterProvider#deferIntercept
204    * @methodOf ui.router.router.$urlRouterProvider
205    *
206    * @description
207    * Disables (or enables) deferring location change interception.
208    *
209    * If you wish to customize the behavior of syncing the URL (for example, if you wish to
210    * defer a transition but maintain the current URL), call this method at configuration time.
211    * Then, at run time, call `$urlRouter.listen()` after you have configured your own
212    * `$locationChangeSuccess` event handler.
213    *
214    * @example
215    * <pre>
216    * var app = angular.module('app', ['ui.router.router']);
217    *
218    * app.config(function ($urlRouterProvider) {
219    *
220    *   // Prevent $urlRouter from automatically intercepting URL changes;
221    *   // this allows you to configure custom behavior in between
222    *   // location changes and route synchronization:
223    *   $urlRouterProvider.deferIntercept();
224    *
225    * }).run(function ($rootScope, $urlRouter, UserService) {
226    *
227    *   $rootScope.$on('$locationChangeSuccess', function(e) {
228    *     // UserService is an example service for managing user state
229    *     if (UserService.isLoggedIn()) return;
230    *
231    *     // Prevent $urlRouter's default handler from firing
232    *     e.preventDefault();
233    *
234    *     UserService.handleLogin().then(function() {
235    *       // Once the user has logged in, sync the current URL
236    *       // to the router:
237    *       $urlRouter.sync();
238    *     });
239    *   });
240    *
241    *   // Configures $urlRouter's listener *after* your custom listener
242    *   $urlRouter.listen();
243    * });
244    * </pre>
245    *
246    * @param {boolean} defer Indicates whether to defer location change interception. Passing
247             no parameter is equivalent to `true`.
248    */
249   this.deferIntercept = function (defer) {
250     if (defer === undefined) defer = true;
251     interceptDeferred = defer;
252   };
253
254   /**
255    * @ngdoc object
256    * @name ui.router.router.$urlRouter
257    *
258    * @requires $location
259    * @requires $rootScope
260    * @requires $injector
261    * @requires $browser
262    *
263    * @description
264    *
265    */
266   this.$get = $get;
267   $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer'];
268   function $get(   $location,   $rootScope,   $injector,   $browser,   $sniffer) {
269
270     var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
271
272     function appendBasePath(url, isHtml5, absolute) {
273       if (baseHref === '/') return url;
274       if (isHtml5) return baseHref.slice(0, -1) + url;
275       if (absolute) return baseHref.slice(1) + url;
276       return url;
277     }
278
279     // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
280     function update(evt) {
281       if (evt && evt.defaultPrevented) return;
282       var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
283       lastPushedUrl = undefined;
284       // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573
285       //if (ignoreUpdate) return true;
286
287       function check(rule) {
288         var handled = rule($injector, $location);
289
290         if (!handled) return false;
291         if (isString(handled)) $location.replace().url(handled);
292         return true;
293       }
294       var n = rules.length, i;
295
296       for (i = 0; i < n; i++) {
297         if (check(rules[i])) return;
298       }
299       // always check otherwise last to allow dynamic updates to the set of rules
300       if (otherwise) check(otherwise);
301     }
302
303     function listen() {
304       listener = listener || $rootScope.$on('$locationChangeSuccess', update);
305       return listener;
306     }
307
308     rules.sort(function(ruleA, ruleB) {
309       var aLength = ruleA.prefix ? ruleA.prefix.length : 0;
310       var bLength = ruleB.prefix ? ruleB.prefix.length : 0;
311       return bLength - aLength;
312     });
313
314     if (!interceptDeferred) listen();
315
316     return {
317       /**
318        * @ngdoc function
319        * @name ui.router.router.$urlRouter#sync
320        * @methodOf ui.router.router.$urlRouter
321        *
322        * @description
323        * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
324        * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
325        * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
326        * with the transition by calling `$urlRouter.sync()`.
327        *
328        * @example
329        * <pre>
330        * angular.module('app', ['ui.router'])
331        *   .run(function($rootScope, $urlRouter) {
332        *     $rootScope.$on('$locationChangeSuccess', function(evt) {
333        *       // Halt state change from even starting
334        *       evt.preventDefault();
335        *       // Perform custom logic
336        *       var meetsRequirement = ...
337        *       // Continue with the update and state transition if logic allows
338        *       if (meetsRequirement) $urlRouter.sync();
339        *     });
340        * });
341        * </pre>
342        */
343       sync: function() {
344         update();
345       },
346
347       listen: function() {
348         return listen();
349       },
350
351       update: function(read) {
352         if (read) {
353           location = $location.url();
354           return;
355         }
356         if ($location.url() === location) return;
357
358         $location.url(location);
359         $location.replace();
360       },
361
362       push: function(urlMatcher, params, options) {
363          var url = urlMatcher.format(params || {});
364
365         // Handle the special hash param, if needed
366         if (url !== null && params && params['#']) {
367             url += '#' + params['#'];
368         }
369
370         $location.url(url);
371         lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
372         if (options && options.replace) $location.replace();
373       },
374
375       /**
376        * @ngdoc function
377        * @name ui.router.router.$urlRouter#href
378        * @methodOf ui.router.router.$urlRouter
379        *
380        * @description
381        * A URL generation method that returns the compiled URL for a given
382        * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
383        *
384        * @example
385        * <pre>
386        * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
387        *   person: "bob"
388        * });
389        * // $bob == "/about/bob";
390        * </pre>
391        *
392        * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
393        * @param {object=} params An object of parameter values to fill the matcher's required parameters.
394        * @param {object=} options Options object. The options are:
395        *
396        * - **`absolute`** - {boolean=false},  If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
397        *
398        * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
399        */
400       href: function(urlMatcher, params, options) {
401         if (!urlMatcher.validates(params)) return null;
402
403         var isHtml5 = $locationProvider.html5Mode();
404         if (angular.isObject(isHtml5)) {
405           isHtml5 = isHtml5.enabled;
406         }
407
408         isHtml5 = isHtml5 && $sniffer.history;
409         
410         var url = urlMatcher.format(params);
411         options = options || {};
412
413         if (!isHtml5 && url !== null) {
414           url = "#" + $locationProvider.hashPrefix() + url;
415         }
416
417         // Handle special hash param, if needed
418         if (url !== null && params && params['#']) {
419           url += '#' + params['#'];
420         }
421
422         url = appendBasePath(url, isHtml5, options.absolute);
423
424         if (!options.absolute || !url) {
425           return url;
426         }
427
428         var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
429         port = (port === 80 || port === 443 ? '' : ':' + port);
430
431         return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
432       }
433     };
434   }
435 }
436
437 angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);