@uirouter/core
Version:
UI-Router Core: Framework agnostic, State-based routing for JavaScript Single Page Apps
343 lines • 13.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UrlRules = void 0;
var state_1 = require("../state");
var urlMatcher_1 = require("./urlMatcher");
var common_1 = require("../common");
var urlRule_1 = require("./urlRule");
var prioritySort = function (a, b) { return (b.priority || 0) - (a.priority || 0); };
var typeSort = function (a, b) {
var weights = { STATE: 4, URLMATCHER: 4, REGEXP: 3, RAW: 2, OTHER: 1 };
return (weights[a.type] || 0) - (weights[b.type] || 0);
};
var urlMatcherSort = function (a, b) {
return !a.urlMatcher || !b.urlMatcher ? 0 : urlMatcher_1.UrlMatcher.compare(a.urlMatcher, b.urlMatcher);
};
var idSort = function (a, b) {
// Identically sorted STATE and URLMATCHER best rule will be chosen by `matchPriority` after each rule matches the URL
var useMatchPriority = { STATE: true, URLMATCHER: true };
var equal = useMatchPriority[a.type] && useMatchPriority[b.type];
return equal ? 0 : (a.$id || 0) - (b.$id || 0);
};
/**
* Default rule priority sorting function.
*
* Sorts rules by:
*
* - Explicit priority (set rule priority using [[UrlRules.when]])
* - Rule type (STATE: 4, URLMATCHER: 4, REGEXP: 3, RAW: 2, OTHER: 1)
* - `UrlMatcher` specificity ([[UrlMatcher.compare]]): works for STATE and URLMATCHER types to pick the most specific rule.
* - Rule registration order (for rule types other than STATE and URLMATCHER)
* - Equally sorted State and UrlMatcher rules will each match the URL.
* Then, the *best* match is chosen based on how many parameter values were matched.
*/
var defaultRuleSortFn;
defaultRuleSortFn = function (a, b) {
var cmp = prioritySort(a, b);
if (cmp !== 0)
return cmp;
cmp = typeSort(a, b);
if (cmp !== 0)
return cmp;
cmp = urlMatcherSort(a, b);
if (cmp !== 0)
return cmp;
return idSort(a, b);
};
function getHandlerFn(handler) {
if (!common_1.isFunction(handler) && !common_1.isString(handler) && !common_1.is(state_1.TargetState)(handler) && !state_1.TargetState.isDef(handler)) {
throw new Error("'handler' must be a string, function, TargetState, or have a state: 'newtarget' property");
}
return common_1.isFunction(handler) ? handler : common_1.val(handler);
}
/**
* API for managing URL rules
*
* This API is used to create and manage URL rules.
* URL rules are a mechanism to respond to specific URL patterns.
*
* The most commonly used methods are [[otherwise]] and [[when]].
*
* This API is found at `router.urlService.rules` (see: [[UIRouter.urlService]], [[URLService.rules]])
*/
var UrlRules = /** @class */ (function () {
/** @internal */
function UrlRules(/** @internal */ router) {
this.router = router;
/** @internal */ this._sortFn = defaultRuleSortFn;
/** @internal */ this._rules = [];
/** @internal */ this._id = 0;
this.urlRuleFactory = new urlRule_1.UrlRuleFactory(router);
}
/** @internal */
UrlRules.prototype.dispose = function (router) {
this._rules = [];
delete this._otherwiseFn;
};
/**
* Defines the initial state, path, or behavior to use when the app starts.
*
* This rule defines the initial/starting state for the application.
*
* This rule is triggered the first time the URL is checked (when the app initially loads).
* The rule is triggered only when the url matches either `""` or `"/"`.
*
* Note: The rule is intended to be used when the root of the application is directly linked to.
* When the URL is *not* `""` or `"/"` and doesn't match other rules, the [[otherwise]] rule is triggered.
* This allows 404-like behavior when an unknown URL is deep-linked.
*
* #### Example:
* Start app at `home` state.
* ```js
* .initial({ state: 'home' });
* ```
*
* #### Example:
* Start app at `/home` (by url)
* ```js
* .initial('/home');
* ```
*
* #### Example:
* When no other url rule matches, go to `home` state
* ```js
* .initial((matchValue, url, router) => {
* console.log('initial state');
* return { state: 'home' };
* })
* ```
*
* @param handler The initial state or url path, or a function which returns the state or url path (or performs custom logic).
*/
UrlRules.prototype.initial = function (handler) {
var handlerFn = getHandlerFn(handler);
var matchFn = function (urlParts, router) {
return router.globals.transitionHistory.size() === 0 && !!/^\/?$/.exec(urlParts.path);
};
this.rule(this.urlRuleFactory.create(matchFn, handlerFn));
};
/**
* Defines the state, url, or behavior to use when no other rule matches the URL.
*
* This rule is matched when *no other rule* matches.
* It is generally used to handle unknown URLs (similar to "404" behavior, but on the client side).
*
* - If `handler` a string, it is treated as a url redirect
*
* #### Example:
* When no other url rule matches, redirect to `/index`
* ```js
* .otherwise('/index');
* ```
*
* - If `handler` is an object with a `state` property, the state is activated.
*
* #### Example:
* When no other url rule matches, redirect to `home` and provide a `dashboard` parameter value.
* ```js
* .otherwise({ state: 'home', params: { dashboard: 'default' } });
* ```
*
* - If `handler` is a function, the function receives the current url ([[UrlParts]]) and the [[UIRouter]] object.
* The function can perform actions, and/or return a value.
*
* #### Example:
* When no other url rule matches, manually trigger a transition to the `home` state
* ```js
* .otherwise((matchValue, urlParts, router) => {
* router.stateService.go('home');
* });
* ```
*
* #### Example:
* When no other url rule matches, go to `home` state
* ```js
* .otherwise((matchValue, urlParts, router) => {
* return { state: 'home' };
* });
* ```
*
* @param handler The url path to redirect to, or a function which returns the url path (or performs custom logic).
*/
UrlRules.prototype.otherwise = function (handler) {
var handlerFn = getHandlerFn(handler);
this._otherwiseFn = this.urlRuleFactory.create(common_1.val(true), handlerFn);
this._sorted = false;
};
/**
* Remove a rule previously registered
*
* @param rule the matcher rule that was previously registered using [[rule]]
*/
UrlRules.prototype.removeRule = function (rule) {
common_1.removeFrom(this._rules, rule);
};
/**
* Manually adds a URL Rule.
*
* Usually, a url rule is added using [[StateDeclaration.url]] or [[when]].
* This api can be used directly for more control (to register a [[BaseUrlRule]], for example).
* Rules can be created using [[urlRuleFactory]], or created manually as simple objects.
*
* A rule should have a `match` function which returns truthy if the rule matched.
* It should also have a `handler` function which is invoked if the rule is the best match.
*
* @return a function that deregisters the rule
*/
UrlRules.prototype.rule = function (rule) {
var _this = this;
if (!urlRule_1.UrlRuleFactory.isUrlRule(rule))
throw new Error('invalid rule');
rule.$id = this._id++;
rule.priority = rule.priority || 0;
this._rules.push(rule);
this._sorted = false;
return function () { return _this.removeRule(rule); };
};
/**
* Gets all registered rules
*
* @returns an array of all the registered rules
*/
UrlRules.prototype.rules = function () {
this.ensureSorted();
return this._rules.concat(this._otherwiseFn ? [this._otherwiseFn] : []);
};
/**
* Defines URL Rule priorities
*
* More than one rule ([[UrlRule]]) might match a given URL.
* This `compareFn` is used to sort the rules by priority.
* Higher priority rules should sort earlier.
*
* The [[defaultRuleSortFn]] is used by default.
*
* You only need to call this function once.
* The `compareFn` will be used to sort the rules as each is registered.
*
* If called without any parameter, it will re-sort the rules.
*
* ---
*
* Url rules may come from multiple sources: states's urls ([[StateDeclaration.url]]), [[when]], and [[rule]].
* Each rule has a (user-provided) [[UrlRule.priority]], a [[UrlRule.type]], and a [[UrlRule.$id]]
* The `$id` is is the order in which the rule was registered.
*
* The sort function should use these data, or data found on a specific type
* of [[UrlRule]] (such as [[StateRule.state]]), to order the rules as desired.
*
* #### Example:
* This compare function prioritizes rules by the order in which the rules were registered.
* A rule registered earlier has higher priority.
*
* ```js
* function compareFn(a, b) {
* return a.$id - b.$id;
* }
* ```
*
* @param compareFn a function that compares to [[UrlRule]] objects.
* The `compareFn` should abide by the `Array.sort` compare function rules.
* Given two rules, `a` and `b`, return a negative number if `a` should be higher priority.
* Return a positive number if `b` should be higher priority.
* Return `0` if the rules are identical.
*
* See the [mozilla reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Description)
* for details.
*/
UrlRules.prototype.sort = function (compareFn) {
var sorted = this.stableSort(this._rules, (this._sortFn = compareFn || this._sortFn));
// precompute _sortGroup values and apply to each rule
var group = 0;
for (var i = 0; i < sorted.length; i++) {
sorted[i]._group = group;
if (i < sorted.length - 1 && this._sortFn(sorted[i], sorted[i + 1]) !== 0) {
group++;
}
}
this._rules = sorted;
this._sorted = true;
};
/** @internal */
UrlRules.prototype.ensureSorted = function () {
this._sorted || this.sort();
};
/** @internal */
UrlRules.prototype.stableSort = function (arr, compareFn) {
var arrOfWrapper = arr.map(function (elem, idx) { return ({ elem: elem, idx: idx }); });
arrOfWrapper.sort(function (wrapperA, wrapperB) {
var cmpDiff = compareFn(wrapperA.elem, wrapperB.elem);
return cmpDiff === 0 ? wrapperA.idx - wrapperB.idx : cmpDiff;
});
return arrOfWrapper.map(function (wrapper) { return wrapper.elem; });
};
/**
* Registers a `matcher` and `handler` for custom URLs handling.
*
* The `matcher` can be:
*
* - a [[UrlMatcher]]: See: [[UrlMatcherFactory.compile]]
* - a `string`: The string is compiled to a [[UrlMatcher]]
* - a `RegExp`: The regexp is used to match the url.
*
* The `handler` can be:
*
* - a string: The url is redirected to the value of the string.
* - a function: The url is redirected to the return value of the function.
*
* ---
*
* When the `handler` is a `string` and the `matcher` is a `UrlMatcher` (or string), the redirect
* string is interpolated with parameter values.
*
* #### Example:
* When the URL is `/foo/123` the rule will redirect to `/bar/123`.
* ```js
* .when("/foo/:param1", "/bar/:param1")
* ```
*
* ---
*
* When the `handler` is a string and the `matcher` is a `RegExp`, the redirect string is
* interpolated with capture groups from the RegExp.
*
* #### Example:
* When the URL is `/foo/123` the rule will redirect to `/bar/123`.
* ```js
* .when(new RegExp("^/foo/(.*)$"), "/bar/$1");
* ```
*
* ---
*
* When the handler is a function, it receives the matched value, the current URL, and the `UIRouter` object (See [[UrlRuleHandlerFn]]).
* The "matched value" differs based on the `matcher`.
* For [[UrlMatcher]]s, it will be the matched state params.
* For `RegExp`, it will be the match array from `regexp.exec()`.
*
* If the handler returns a string, the URL is redirected to the string.
*
* #### Example:
* When the URL is `/foo/123` the rule will redirect to `/bar/123`.
* ```js
* .when(new RegExp("^/foo/(.*)$"), match => "/bar/" + match[1]);
* ```
*
* Note: the `handler` may also invoke arbitrary code, such as `$state.go()`
*
* @param matcher A pattern `string` to match, compiled as a [[UrlMatcher]], or a `RegExp`.
* @param handler The path to redirect to, or a function that returns the path.
* @param options `{ priority: number }`
*
* @return the registered [[UrlRule]]
*/
UrlRules.prototype.when = function (matcher, handler, options) {
var rule = this.urlRuleFactory.create(matcher, handler);
if (common_1.isDefined(options && options.priority))
rule.priority = options.priority;
this.rule(rule);
return rule;
};
return UrlRules;
}());
exports.UrlRules = UrlRules;
//# sourceMappingURL=urlRules.js.map