428 lines
14 KiB
JavaScript
428 lines
14 KiB
JavaScript
/**
|
|
* @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
|
|
* <pre>
|
|
* 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;
|
|
* }
|
|
* });
|
|
* });
|
|
* </pre>
|
|
*
|
|
* @param {object} 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
|
|
* <pre>
|
|
* 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';
|
|
* });
|
|
* });
|
|
* </pre>
|
|
*
|
|
* @param {string|object} 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 handle 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
|
|
* <pre>
|
|
* 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);
|
|
* }
|
|
* });
|
|
* });
|
|
* </pre>
|
|
*
|
|
* @param {string|object} what The incoming path that you want to redirect.
|
|
* @param {string|object} 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
|
|
* <pre>
|
|
* 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();
|
|
* });
|
|
* </pre>
|
|
*
|
|
* @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'];
|
|
function $get( $location, $rootScope, $injector, $browser) {
|
|
|
|
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
|
|
* <pre>
|
|
* 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();
|
|
* });
|
|
* });
|
|
* </pre>
|
|
*/
|
|
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
|
|
* <pre>
|
|
* $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
|
|
* person: "bob"
|
|
* });
|
|
* // $bob == "/about/bob";
|
|
* </pre>
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
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);
|