/** * @ngdoc object * @name ui.router.router.$urlRouterProvider * * @requires ui.router.util.$urlMatcherFactoryProvider * @requires $locationProvider * * @description * `$urlRouterProvider` has the responsibility of watching `$location`. * When `$location` changes it runs through a list of rules one by one until a * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify * a url in a state configuration. All urls are compiled into a UrlMatcher object. * * There are several methods on `$urlRouterProvider` that make it useful to use directly * in your module config. */ $UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider']; function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { var rules = [], otherwise = null, interceptDeferred = false, listener; // Returns a string that is a prefix of all strings matching the RegExp function regExpPrefix(re) { var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; } // Interpolates matched values into a String.replace()-style pattern function interpolate(pattern, match) { return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { return match[what === '$' ? 0 : Number(what)]; }); } /** * @ngdoc function * @name ui.router.router.$urlRouterProvider#rule * @methodOf ui.router.router.$urlRouterProvider * * @description * Defines rules that are used by `$urlRouterProvider` to find matches for * specific URLs. * * @example *
* var app = angular.module('app', ['ui.router.router']); * * app.config(function ($urlRouterProvider) { * // Here's an example of how you might allow case insensitive urls * $urlRouterProvider.rule(function ($injector, $location) { * var path = $location.path(), * normalized = path.toLowerCase(); * * if (path !== normalized) { * return normalized; * } * }); * }); ** * @param {function} rule Handler function that takes `$injector` and `$location` * services as arguments. You can use them to return a valid path as a string. * * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance */ this.rule = function (rule) { if (!isFunction(rule)) throw new Error("'rule' must be a function"); rules.push(rule); return this; }; /** * @ngdoc object * @name ui.router.router.$urlRouterProvider#otherwise * @methodOf ui.router.router.$urlRouterProvider * * @description * Defines a path that is used when an invalid route is requested. * * @example *
* var app = angular.module('app', ['ui.router.router']); * * app.config(function ($urlRouterProvider) { * // if the path doesn't match any of the urls you configured * // otherwise will take care of routing the user to the * // specified url * $urlRouterProvider.otherwise('/index'); * * // Example of using function rule as param * $urlRouterProvider.otherwise(function ($injector, $location) { * return '/a/valid/url'; * }); * }); ** * @param {string|function} rule The url path you want to redirect to or a function * rule that returns the url path. The function version is passed two params: * `$injector` and `$location` services, and must return a url string. * * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance */ this.otherwise = function (rule) { if (isString(rule)) { var redirect = rule; rule = function () { return redirect; }; } else if (!isFunction(rule)) throw new Error("'rule' must be a function"); otherwise = rule; return this; }; function handleIfMatch($injector, handler, match) { if (!match) return false; var result = $injector.invoke(handler, handler, { $match: match }); return isDefined(result) ? result : true; } /** * @ngdoc function * @name ui.router.router.$urlRouterProvider#when * @methodOf ui.router.router.$urlRouterProvider * * @description * Registers a handler for a given url matching. * * If the handler is a string, it is * treated as a redirect, and is interpolated according to the syntax of match * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). * * If the handler is a function, it is injectable. It gets invoked if `$location` * matches. You have the option of inject the match object as `$match`. * * The handler can return * * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` * will continue trying to find another one that matches. * - **string** which is treated as a redirect and passed to `$location.url()` * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. * * @example *
* var app = angular.module('app', ['ui.router.router']); * * app.config(function ($urlRouterProvider) { * $urlRouterProvider.when($state.url, function ($match, $stateParams) { * if ($state.$current.navigable !== state || * !equalForKeys($match, $stateParams) { * $state.transitionTo(state, $match, false); * } * }); * }); ** * @param {string|object} what The incoming path that you want to redirect. * @param {string|function} handler The path you want to redirect your user to. */ this.when = function (what, handler) { var redirect, handlerIsString = isString(handler); if (isString(what)) what = $urlMatcherFactory.compile(what); if (!handlerIsString && !isFunction(handler) && !isArray(handler)) throw new Error("invalid 'handler' in when()"); var strategies = { matcher: function (what, handler) { if (handlerIsString) { redirect = $urlMatcherFactory.compile(handler); handler = ['$match', function ($match) { return redirect.format($match); }]; } return extend(function ($injector, $location) { return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); }, { prefix: isString(what.prefix) ? what.prefix : '' }); }, regex: function (what, handler) { if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); if (handlerIsString) { redirect = handler; handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; } return extend(function ($injector, $location) { return handleIfMatch($injector, handler, what.exec($location.path())); }, { prefix: regExpPrefix(what) }); } }; var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; for (var n in check) { if (check[n]) return this.rule(strategies[n](what, handler)); } throw new Error("invalid 'what' in when()"); }; /** * @ngdoc function * @name ui.router.router.$urlRouterProvider#deferIntercept * @methodOf ui.router.router.$urlRouterProvider * * @description * Disables (or enables) deferring location change interception. * * If you wish to customize the behavior of syncing the URL (for example, if you wish to * defer a transition but maintain the current URL), call this method at configuration time. * Then, at run time, call `$urlRouter.listen()` after you have configured your own * `$locationChangeSuccess` event handler. * * @example *
* var app = angular.module('app', ['ui.router.router']); * * app.config(function ($urlRouterProvider) { * * // Prevent $urlRouter from automatically intercepting URL changes; * // this allows you to configure custom behavior in between * // location changes and route synchronization: * $urlRouterProvider.deferIntercept(); * * }).run(function ($rootScope, $urlRouter, UserService) { * * $rootScope.$on('$locationChangeSuccess', function(e) { * // UserService is an example service for managing user state * if (UserService.isLoggedIn()) return; * * // Prevent $urlRouter's default handler from firing * e.preventDefault(); * * UserService.handleLogin().then(function() { * // Once the user has logged in, sync the current URL * // to the router: * $urlRouter.sync(); * }); * }); * * // Configures $urlRouter's listener *after* your custom listener * $urlRouter.listen(); * }); ** * @param {boolean} defer Indicates whether to defer location change interception. Passing no parameter is equivalent to `true`. */ this.deferIntercept = function (defer) { if (defer === undefined) defer = true; interceptDeferred = defer; }; /** * @ngdoc object * @name ui.router.router.$urlRouter * * @requires $location * @requires $rootScope * @requires $injector * @requires $browser * * @description * */ this.$get = $get; $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer']; function $get( $location, $rootScope, $injector, $browser, $sniffer) { var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl; function appendBasePath(url, isHtml5, absolute) { if (baseHref === '/') return url; if (isHtml5) return baseHref.slice(0, -1) + url; if (absolute) return baseHref.slice(1) + url; return url; } // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree function update(evt) { if (evt && evt.defaultPrevented) return; var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; lastPushedUrl = undefined; // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 //if (ignoreUpdate) return true; function check(rule) { var handled = rule($injector, $location); if (!handled) return false; if (isString(handled)) $location.replace().url(handled); return true; } var n = rules.length, i; for (i = 0; i < n; i++) { if (check(rules[i])) return; } // always check otherwise last to allow dynamic updates to the set of rules if (otherwise) check(otherwise); } function listen() { listener = listener || $rootScope.$on('$locationChangeSuccess', update); return listener; } if (!interceptDeferred) listen(); return { /** * @ngdoc function * @name ui.router.router.$urlRouter#sync * @methodOf ui.router.router.$urlRouter * * @description * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed * with the transition by calling `$urlRouter.sync()`. * * @example *
* angular.module('app', ['ui.router']) * .run(function($rootScope, $urlRouter) { * $rootScope.$on('$locationChangeSuccess', function(evt) { * // Halt state change from even starting * evt.preventDefault(); * // Perform custom logic * var meetsRequirement = ... * // Continue with the update and state transition if logic allows * if (meetsRequirement) $urlRouter.sync(); * }); * }); **/ sync: function() { update(); }, listen: function() { return listen(); }, update: function(read) { if (read) { location = $location.url(); return; } if ($location.url() === location) return; $location.url(location); $location.replace(); }, push: function(urlMatcher, params, options) { var url = urlMatcher.format(params || {}); // Handle the special hash param, if needed if (url !== null && params && params['#']) { url += '#' + params['#']; } $location.url(url); lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; if (options && options.replace) $location.replace(); }, /** * @ngdoc function * @name ui.router.router.$urlRouter#href * @methodOf ui.router.router.$urlRouter * * @description * A URL generation method that returns the compiled URL for a given * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. * * @example *
* $bob = $urlRouter.href(new UrlMatcher("/about/:person"), { * person: "bob" * }); * // $bob == "/about/bob"; ** * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. * @param {object=} params An object of parameter values to fill the matcher's required parameters. * @param {object=} options Options object. The options are: * * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". * * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` */ href: function(urlMatcher, params, options) { if (!urlMatcher.validates(params)) return null; var isHtml5 = $locationProvider.html5Mode(); if (angular.isObject(isHtml5)) { isHtml5 = isHtml5.enabled; } isHtml5 = isHtml5 && $sniffer.history; var url = urlMatcher.format(params); options = options || {}; if (!isHtml5 && url !== null) { url = "#" + $locationProvider.hashPrefix() + url; } // Handle special hash param, if needed if (url !== null && params && params['#']) { url += '#' + params['#']; } url = appendBasePath(url, isHtml5, options.absolute); if (!options.absolute || !url) { return url; } var slash = (!isHtml5 && url ? '/' : ''), port = $location.port(); port = (port === 80 || port === 443 ? '' : ':' + port); return [$location.protocol(), '://', $location.host(), port, slash, url].join(''); } }; } } angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);