angular-cached-resource
Version:
An AngularJS module to interact with RESTful resources, even when browser is offline
274 lines (256 loc) • 9.47 kB
JavaScript
/**
* @ngdoc object
* @name ui.router.router.$urlRouterProvider
*
* @requires ui.router.util.$urlMatcherFactoryProvider
*
* @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 = ['$urlMatcherFactoryProvider'];
function $UrlRouterProvider( $urlMatcherFactory) {
var rules = [],
otherwise = null;
// 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 invalied 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) {
* ...
* });
* });
* </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.
*
* @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 syyntax 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 object
* @name ui.router.router.$urlRouter
*
* @requires $location
* @requires $rootScope
* @requires $injector
*
* @description
*
*/
this.$get =
[ '$location', '$rootScope', '$injector',
function ($location, $rootScope, $injector) {
// TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
function update(evt) {
if (evt && evt.defaultPrevented) return;
function check(rule) {
var handled = rule($injector, $location);
if (handled) {
if (isString(handled)) $location.replace().url(handled);
return true;
}
return false;
}
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);
}
$rootScope.$on('$locationChangeSuccess', update);
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();
}
};
}];
}
angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);