ui-router-core
Version:
UI-Router Core: Framework agnostic, State-based routing for JavaScript Single Page Apps
271 lines • 10.6 kB
JavaScript
/**
* @internalapi
* @module url
*/
/** for typedoc */
import { composeSort, createProxyFunctions, extend, inArray, removeFrom, sortBy } from '../common/common';
import { isDefined, isFunction, isString } from '../common/predicates';
import { UrlMatcher } from './urlMatcher';
import { is, pattern, pipe, prop, val } from '../common/hof';
import { UrlRuleFactory } from './urlRule';
import { TargetState } from '../state/targetState';
/** @hidden */
function appendBasePath(url, isHtml5, absolute, baseHref) {
if (baseHref === '/')
return url;
if (isHtml5)
return baseHref.slice(0, -1) + url;
if (absolute)
return baseHref.slice(1) + url;
return url;
}
/** @hidden */
var getMatcher = prop("urlMatcher");
/**
* Default rule priority sorting function.
*
* Sorts rules by:
*
* - Explicit priority (set rule priority using [[UrlRulesApi.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.
* - Registration order (for rule types other than STATE and URLMATCHER)
*
* @coreapi
*/
var defaultRuleSortFn;
defaultRuleSortFn = composeSort(sortBy(pipe(prop("priority"), function (x) { return -x; })), sortBy(pipe(prop("type"), function (type) { return ({ "STATE": 4, "URLMATCHER": 4, "REGEXP": 3, "RAW": 2, "OTHER": 1 })[type]; })), function (a, b) { return (getMatcher(a) && getMatcher(b)) ? UrlMatcher.compare(getMatcher(a), getMatcher(b)) : 0; }, sortBy(prop("$id"), inArray(["REGEXP", "RAW", "OTHER"])));
/**
* Updates URL and responds to URL changes
*
* ### Deprecation warning:
* This class is now considered to be an internal API
* Use the [[UrlService]] instead.
* For configuring URL rules, use the [[UrlRulesApi]] which can be found as [[UrlService.rules]].
*
* This class updates the URL when the state changes.
* It also responds to changes in the URL.
*/
var UrlRouter = (function () {
/** @hidden */
function UrlRouter(router) {
/** @hidden */ this._sortFn = defaultRuleSortFn;
/** @hidden */ this._rules = [];
/** @hidden */ this.interceptDeferred = false;
/** @hidden */ this._id = 0;
/** @hidden */ this._sorted = false;
this._router = router;
this.urlRuleFactory = new UrlRuleFactory(router);
createProxyFunctions(val(UrlRouter.prototype), this, val(this));
}
/** @internalapi */
UrlRouter.prototype.dispose = function () {
this.listen(false);
this._rules = [];
delete this._otherwiseFn;
};
/** @inheritdoc */
UrlRouter.prototype.sort = function (compareFn) {
this._rules.sort(this._sortFn = compareFn || this._sortFn);
this._sorted = true;
};
UrlRouter.prototype.ensureSorted = function () {
this._sorted || this.sort();
};
/**
* Given a URL, check all rules and return the best [[MatchResult]]
* @param url
* @returns {MatchResult}
*/
UrlRouter.prototype.match = function (url) {
var _this = this;
this.ensureSorted();
url = extend({ path: '', search: {}, hash: '' }, url);
var rules = this.rules();
if (this._otherwiseFn)
rules.push(this._otherwiseFn);
// Checks a single rule. Returns { rule: rule, match: match, weight: weight } if it matched, or undefined
var checkRule = function (rule) {
var match = rule.match(url, _this._router);
return match && { match: match, rule: rule, weight: rule.matchPriority(match) };
};
// The rules are pre-sorted.
// - Find the first matching rule.
// - Find any other matching rule that sorted *exactly the same*, according to `.sort()`.
// - Choose the rule with the highest match weight.
var best;
for (var i = 0; i < rules.length; i++) {
// Stop when there is a 'best' rule and the next rule sorts differently than it.
if (best && this._sortFn(rules[i], best.rule) !== 0)
break;
var current = checkRule(rules[i]);
// Pick the best MatchResult
best = (!best || current && current.weight > best.weight) ? current : best;
}
return best;
};
/** @inheritdoc */
UrlRouter.prototype.sync = function (evt) {
if (evt && evt.defaultPrevented)
return;
var router = this._router, $url = router.urlService, $state = router.stateService;
var url = {
path: $url.path(), search: $url.search(), hash: $url.hash(),
};
var best = this.match(url);
var applyResult = pattern([
[isString, function (newurl) { return $url.url(newurl, true); }],
[TargetState.isDef, function (def) { return $state.go(def.state, def.params, def.options); }],
[is(TargetState), function (target) { return $state.go(target.state(), target.params(), target.options()); }],
]);
applyResult(best && best.rule.handler(best.match, url, router));
};
/** @inheritdoc */
UrlRouter.prototype.listen = function (enabled) {
var _this = this;
if (enabled === false) {
this._stopFn && this._stopFn();
delete this._stopFn;
}
else {
return this._stopFn = this._stopFn || this._router.urlService.onChange(function (evt) { return _this.sync(evt); });
}
};
/**
* Internal API.
* @internalapi
*/
UrlRouter.prototype.update = function (read) {
var $url = this._router.locationService;
if (read) {
this.location = $url.path();
return;
}
if ($url.path() === this.location)
return;
$url.url(this.location, true);
};
/**
* Internal API.
*
* Pushes a new location to the browser history.
*
* @internalapi
* @param urlMatcher
* @param params
* @param options
*/
UrlRouter.prototype.push = function (urlMatcher, params, options) {
var replace = options && !!options.replace;
this._router.urlService.url(urlMatcher.format(params || {}), replace);
};
/**
* Builds and returns a URL with interpolated parameters
*
* #### Example:
* ```js
* matcher = $umf.compile("/about/:person");
* params = { person: "bob" };
* $bob = $urlRouter.href(matcher, params);
* // $bob == "/about/bob";
* ```
*
* @param urlMatcher The [[UrlMatcher]] object which is used as the template of the URL to generate.
* @param params An object of parameter values to fill the matcher's required parameters.
* @param options Options object. The options are:
*
* - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
*
* @returns Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
*/
UrlRouter.prototype.href = function (urlMatcher, params, options) {
var url = urlMatcher.format(params);
if (url == null)
return null;
options = options || { absolute: false };
var cfg = this._router.urlService.config;
var isHtml5 = cfg.html5Mode();
if (!isHtml5 && url !== null) {
url = "#" + cfg.hashPrefix() + url;
}
url = appendBasePath(url, isHtml5, options.absolute, cfg.baseHref());
if (!options.absolute || !url) {
return url;
}
var slash = (!isHtml5 && url ? '/' : ''), port = cfg.port();
port = (port === 80 || port === 443 ? '' : ':' + port);
return [cfg.protocol(), '://', cfg.host(), port, slash, url].join('');
};
/**
* 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 [[UrlRouter.urlRuleFactory]], or create 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
*/
UrlRouter.prototype.rule = function (rule) {
var _this = this;
if (!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); };
};
/** @inheritdoc */
UrlRouter.prototype.removeRule = function (rule) {
removeFrom(this._rules, rule);
};
/** @inheritdoc */
UrlRouter.prototype.rules = function () {
this.ensureSorted();
return this._rules.slice();
};
/** @inheritdoc */
UrlRouter.prototype.otherwise = function (handler) {
var handlerFn = getHandlerFn(handler);
this._otherwiseFn = this.urlRuleFactory.create(val(true), handlerFn);
this._sorted = false;
};
;
/** @inheritdoc */
UrlRouter.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));
};
;
/** @inheritdoc */
UrlRouter.prototype.when = function (matcher, handler, options) {
var rule = this.urlRuleFactory.create(matcher, handler);
if (isDefined(options && options.priority))
rule.priority = options.priority;
this.rule(rule);
return rule;
};
;
/** @inheritdoc */
UrlRouter.prototype.deferIntercept = function (defer) {
if (defer === undefined)
defer = true;
this.interceptDeferred = defer;
};
;
return UrlRouter;
}());
export { UrlRouter };
function getHandlerFn(handler) {
if (!isFunction(handler) && !isString(handler) && !is(TargetState)(handler) && !TargetState.isDef(handler)) {
throw new Error("'handler' must be a string, function, TargetState, or have a state: 'newtarget' property");
}
return isFunction(handler) ? handler : val(handler);
}
//# sourceMappingURL=urlRouter.js.map