commonroutesApp/www/lib/angular-ui-router/src/urlRouter.js
www/lib/angular-ui-router/src/urlRouter.jsRaw
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'];
18function $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 {object} 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|object} 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. if handle is a string, it is
126 * treated as a redirect, and is interpolated according to the syntax of match
127 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
128 *
129 * If the handler is a function, it is injectable. It gets invoked if `$location`
130 * matches. You have the option of inject the match object as `$match`.
131 *
132 * The handler can return
133 *
134 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
135 * will continue trying to find another one that matches.
136 * - **string** which is treated as a redirect and passed to `$location.url()`
137 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
138 *
139 * @example
140 * <pre>
141 * var app = angular.module('app', ['ui.router.router']);
142 *
143 * app.config(function ($urlRouterProvider) {
144 * $urlRouterProvider.when($state.url, function ($match, $stateParams) {
145 * if ($state.$current.navigable !== state ||
146 * !equalForKeys($match, $stateParams) {
147 * $state.transitionTo(state, $match, false);
148 * }
149 * });
150 * });
151 * </pre>
152 *
153 * @param {string|object} what The incoming path that you want to redirect.
154 * @param {string|object} handler The path you want to redirect your user to.
155 */
156 this.when = function (what, handler) {
157 var redirect, handlerIsString = isString(handler);
158 if (isString(what)) what = $urlMatcherFactory.compile(what);
159
160 if (!handlerIsString && !isFunction(handler) && !isArray(handler))
161 throw new Error("invalid 'handler' in when()");
162
163 var strategies = {
164 matcher: function (what, handler) {
165 if (handlerIsString) {
166 redirect = $urlMatcherFactory.compile(handler);
167 handler = ['$match', function ($match) { return redirect.format($match); }];
168 }
169 return extend(function ($injector, $location) {
170 return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
171 }, {
172 prefix: isString(what.prefix) ? what.prefix : ''
173 });
174 },
175 regex: function (what, handler) {
176 if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
177
178 if (handlerIsString) {
179 redirect = handler;
180 handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
181 }
182 return extend(function ($injector, $location) {
183 return handleIfMatch($injector, handler, what.exec($location.path()));
184 }, {
185 prefix: regExpPrefix(what)
186 });
187 }
188 };
189
190 var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
191
192 for (var n in check) {
193 if (check[n]) return this.rule(strategies[n](what, handler));
194 }
195
196 throw new Error("invalid 'what' in when()");
197 };
198
199 /**
200 * @ngdoc function
201 * @name ui.router.router.$urlRouterProvider#deferIntercept
202 * @methodOf ui.router.router.$urlRouterProvider
203 *
204 * @description
205 * Disables (or enables) deferring location change interception.
206 *
207 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
208 * defer a transition but maintain the current URL), call this method at configuration time.
209 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
210 * `$locationChangeSuccess` event handler.
211 *
212 * @example
213 * <pre>
214 * var app = angular.module('app', ['ui.router.router']);
215 *
216 * app.config(function ($urlRouterProvider) {
217 *
218 * // Prevent $urlRouter from automatically intercepting URL changes;
219 * // this allows you to configure custom behavior in between
220 * // location changes and route synchronization:
221 * $urlRouterProvider.deferIntercept();
222 *
223 * }).run(function ($rootScope, $urlRouter, UserService) {
224 *
225 * $rootScope.$on('$locationChangeSuccess', function(e) {
226 * // UserService is an example service for managing user state
227 * if (UserService.isLoggedIn()) return;
228 *
229 * // Prevent $urlRouter's default handler from firing
230 * e.preventDefault();
231 *
232 * UserService.handleLogin().then(function() {
233 * // Once the user has logged in, sync the current URL
234 * // to the router:
235 * $urlRouter.sync();
236 * });
237 * });
238 *
239 * // Configures $urlRouter's listener *after* your custom listener
240 * $urlRouter.listen();
241 * });
242 * </pre>
243 *
244 * @param {boolean} defer Indicates whether to defer location change interception. Passing
245 no parameter is equivalent to `true`.
246 */
247 this.deferIntercept = function (defer) {
248 if (defer === undefined) defer = true;
249 interceptDeferred = defer;
250 };
251
252 /**
253 * @ngdoc object
254 * @name ui.router.router.$urlRouter
255 *
256 * @requires $location
257 * @requires $rootScope
258 * @requires $injector
259 * @requires $browser
260 *
261 * @description
262 *
263 */
264 this.$get = $get;
265 $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
266 function $get( $location, $rootScope, $injector, $browser) {
267
268 var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
269
270 function appendBasePath(url, isHtml5, absolute) {
271 if (baseHref === '/') return url;
272 if (isHtml5) return baseHref.slice(0, -1) + url;
273 if (absolute) return baseHref.slice(1) + url;
274 return url;
275 }
276
277 // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
278 function update(evt) {
279 if (evt && evt.defaultPrevented) return;
280 var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
281 lastPushedUrl = undefined;
282 if (ignoreUpdate) return true;
283
284 function check(rule) {
285 var handled = rule($injector, $location);
286
287 if (!handled) return false;
288 if (isString(handled)) $location.replace().url(handled);
289 return true;
290 }
291 var n = rules.length, i;
292
293 for (i = 0; i < n; i++) {
294 if (check(rules[i])) return;
295 }
296 // always check otherwise last to allow dynamic updates to the set of rules
297 if (otherwise) check(otherwise);
298 }
299
300 function listen() {
301 listener = listener || $rootScope.$on('$locationChangeSuccess', update);
302 return listener;
303 }
304
305 if (!interceptDeferred) listen();
306
307 return {
308 /**
309 * @ngdoc function
310 * @name ui.router.router.$urlRouter#sync
311 * @methodOf ui.router.router.$urlRouter
312 *
313 * @description
314 * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
315 * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
316 * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
317 * with the transition by calling `$urlRouter.sync()`.
318 *
319 * @example
320 * <pre>
321 * angular.module('app', ['ui.router'])
322 * .run(function($rootScope, $urlRouter) {
323 * $rootScope.$on('$locationChangeSuccess', function(evt) {
324 * // Halt state change from even starting
325 * evt.preventDefault();
326 * // Perform custom logic
327 * var meetsRequirement = ...
328 * // Continue with the update and state transition if logic allows
329 * if (meetsRequirement) $urlRouter.sync();
330 * });
331 * });
332 * </pre>
333 */
334 sync: function() {
335 update();
336 },
337
338 listen: function() {
339 return listen();
340 },
341
342 update: function(read) {
343 if (read) {
344 location = $location.url();
345 return;
346 }
347 if ($location.url() === location) return;
348
349 $location.url(location);
350 $location.replace();
351 },
352
353 push: function(urlMatcher, params, options) {
354 $location.url(urlMatcher.format(params || {}));
355 lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
356 if (options && options.replace) $location.replace();
357 },
358
359 /**
360 * @ngdoc function
361 * @name ui.router.router.$urlRouter#href
362 * @methodOf ui.router.router.$urlRouter
363 *
364 * @description
365 * A URL generation method that returns the compiled URL for a given
366 * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
367 *
368 * @example
369 * <pre>
370 * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
371 * person: "bob"
372 * });
373 * // $bob == "/about/bob";
374 * </pre>
375 *
376 * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
377 * @param {object=} params An object of parameter values to fill the matcher's required parameters.
378 * @param {object=} options Options object. The options are:
379 *
380 * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
381 *
382 * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
383 */
384 href: function(urlMatcher, params, options) {
385 if (!urlMatcher.validates(params)) return null;
386
387 var isHtml5 = $locationProvider.html5Mode();
388 if (angular.isObject(isHtml5)) {
389 isHtml5 = isHtml5.enabled;
390 }
391
392 var url = urlMatcher.format(params);
393 options = options || {};
394
395 if (!isHtml5 && url !== null) {
396 url = "#" + $locationProvider.hashPrefix() + url;
397 }
398 url = appendBasePath(url, isHtml5, options.absolute);
399
400 if (!options.absolute || !url) {
401 return url;
402 }
403
404 var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
405 port = (port === 80 || port === 443 ? '' : ':' + port);
406
407 return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
408 }
409 };
410 }
411}
412
413angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);