UNPKG

aurelia-router

Version:

A powerful client-side router.

1,257 lines (1,242 loc) 93.3 kB
import { getLogger } from 'aurelia-logging'; import { Container } from 'aurelia-dependency-injection'; import { History } from 'aurelia-history'; import { RouteRecognizer } from 'aurelia-route-recognizer'; import { EventAggregator } from 'aurelia-event-aggregator'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } /** * Class used to represent an instruction during a navigation. */ var NavigationInstruction = /** @class */ (function () { function NavigationInstruction(init) { /** * Current built viewport plan of this nav instruction */ this.plan = null; this.options = {}; Object.assign(this, init); this.params = this.params || {}; this.viewPortInstructions = {}; var ancestorParams = []; var current = this; do { var currentParams = Object.assign({}, current.params); if (current.config && current.config.hasChildRouter) { // remove the param for the injected child route segment delete currentParams[current.getWildCardName()]; } ancestorParams.unshift(currentParams); current = current.parentInstruction; } while (current); var allParams = Object.assign.apply(Object, [{}, this.queryParams].concat(ancestorParams)); this.lifecycleArgs = [allParams, this.config, this]; } /** * Gets an array containing this instruction and all child instructions for the current navigation. */ NavigationInstruction.prototype.getAllInstructions = function () { var instructions = [this]; var viewPortInstructions = this.viewPortInstructions; for (var key in viewPortInstructions) { var childInstruction = viewPortInstructions[key].childNavigationInstruction; if (childInstruction) { instructions.push.apply(instructions, childInstruction.getAllInstructions()); } } return instructions; }; /** * Gets an array containing the instruction and all child instructions for the previous navigation. * Previous instructions are no longer available after navigation completes. */ NavigationInstruction.prototype.getAllPreviousInstructions = function () { return this.getAllInstructions().map(function (c) { return c.previousInstruction; }).filter(function (c) { return c; }); }; NavigationInstruction.prototype.addViewPortInstruction = function (nameOrInitOptions, strategy, moduleId, component) { var viewPortInstruction; var viewPortName = typeof nameOrInitOptions === 'string' ? nameOrInitOptions : nameOrInitOptions.name; var lifecycleArgs = this.lifecycleArgs; var config = Object.assign({}, lifecycleArgs[1], { currentViewPort: viewPortName }); if (typeof nameOrInitOptions === 'string') { viewPortInstruction = { name: nameOrInitOptions, strategy: strategy, moduleId: moduleId, component: component, childRouter: component.childRouter, lifecycleArgs: [lifecycleArgs[0], config, lifecycleArgs[2]] }; } else { viewPortInstruction = { name: viewPortName, strategy: nameOrInitOptions.strategy, component: nameOrInitOptions.component, moduleId: nameOrInitOptions.moduleId, childRouter: nameOrInitOptions.component.childRouter, lifecycleArgs: [lifecycleArgs[0], config, lifecycleArgs[2]] }; } return this.viewPortInstructions[viewPortName] = viewPortInstruction; }; /** * Gets the name of the route pattern's wildcard parameter, if applicable. */ NavigationInstruction.prototype.getWildCardName = function () { // todo: potential issue, or at least unsafe typings var configRoute = this.config.route; var wildcardIndex = configRoute.lastIndexOf('*'); return configRoute.substr(wildcardIndex + 1); }; /** * Gets the path and query string created by filling the route * pattern's wildcard parameter with the matching param. */ NavigationInstruction.prototype.getWildcardPath = function () { var wildcardName = this.getWildCardName(); var path = this.params[wildcardName] || ''; var queryString = this.queryString; if (queryString) { path += '?' + queryString; } return path; }; /** * Gets the instruction's base URL, accounting for wildcard route parameters. */ NavigationInstruction.prototype.getBaseUrl = function () { var _this = this; var $encodeURI = encodeURI; var fragment = decodeURI(this.fragment); if (fragment === '') { var nonEmptyRoute = this.router.routes.find(function (route) { return route.name === _this.config.name && route.route !== ''; }); if (nonEmptyRoute) { fragment = nonEmptyRoute.route; } } if (!this.params) { return $encodeURI(fragment); } var wildcardName = this.getWildCardName(); var path = this.params[wildcardName] || ''; if (!path) { return $encodeURI(fragment); } return $encodeURI(fragment.substr(0, fragment.lastIndexOf(path))); }; /** * Finalize a viewport instruction * @internal */ NavigationInstruction.prototype._commitChanges = function (waitToSwap) { var _this = this; var router = this.router; router.currentInstruction = this; var previousInstruction = this.previousInstruction; if (previousInstruction) { previousInstruction.config.navModel.isActive = false; } this.config.navModel.isActive = true; router.refreshNavigation(); var loads = []; var delaySwaps = []; var viewPortInstructions = this.viewPortInstructions; var _loop_1 = function (viewPortName) { var viewPortInstruction = viewPortInstructions[viewPortName]; var viewPort = router.viewPorts[viewPortName]; if (!viewPort) { throw new Error("There was no router-view found in the view for " + viewPortInstruction.moduleId + "."); } var childNavInstruction = viewPortInstruction.childNavigationInstruction; if (viewPortInstruction.strategy === "replace" /* Replace */) { if (childNavInstruction && childNavInstruction.parentCatchHandler) { loads.push(childNavInstruction._commitChanges(waitToSwap)); } else { if (waitToSwap) { delaySwaps.push({ viewPort: viewPort, viewPortInstruction: viewPortInstruction }); } loads.push(viewPort .process(viewPortInstruction, waitToSwap) .then(function () { return childNavInstruction ? childNavInstruction._commitChanges(waitToSwap) : Promise.resolve(); })); } } else { if (childNavInstruction) { loads.push(childNavInstruction._commitChanges(waitToSwap)); } } }; for (var viewPortName in viewPortInstructions) { _loop_1(viewPortName); } return Promise .all(loads) .then(function () { delaySwaps.forEach(function (x) { return x.viewPort.swap(x.viewPortInstruction); }); return null; }) .then(function () { return prune(_this); }); }; /**@internal */ NavigationInstruction.prototype._updateTitle = function () { var router = this.router; var title = this._buildTitle(router.titleSeparator); if (title) { router.history.setTitle(title); } }; /**@internal */ NavigationInstruction.prototype._buildTitle = function (separator) { if (separator === void 0) { separator = ' | '; } var title = ''; var childTitles = []; var navModelTitle = this.config.navModel.title; var instructionRouter = this.router; var viewPortInstructions = this.viewPortInstructions; if (navModelTitle) { title = instructionRouter.transformTitle(navModelTitle); } for (var viewPortName in viewPortInstructions) { var viewPortInstruction = viewPortInstructions[viewPortName]; var child_nav_instruction = viewPortInstruction.childNavigationInstruction; if (child_nav_instruction) { var childTitle = child_nav_instruction._buildTitle(separator); if (childTitle) { childTitles.push(childTitle); } } } if (childTitles.length) { title = childTitles.join(separator) + (title ? separator : '') + title; } if (instructionRouter.title) { title += (title ? separator : '') + instructionRouter.transformTitle(instructionRouter.title); } return title; }; return NavigationInstruction; }()); var prune = function (instruction) { instruction.previousInstruction = null; instruction.plan = null; }; /** * Class for storing and interacting with a route's navigation settings. */ var NavModel = /** @class */ (function () { function NavModel(router, relativeHref) { /** * True if this nav item is currently active. */ this.isActive = false; /** * The title. */ this.title = null; /** * This nav item's absolute href. */ this.href = null; /** * This nav item's relative href. */ this.relativeHref = null; /** * Data attached to the route at configuration time. */ this.settings = {}; /** * The route config. */ this.config = null; this.router = router; this.relativeHref = relativeHref; } /** * Sets the route's title and updates document.title. * If the a navigation is in progress, the change will be applied * to document.title when the navigation completes. * * @param title The new title. */ NavModel.prototype.setTitle = function (title) { this.title = title; if (this.isActive) { this.router.updateTitle(); } }; return NavModel; }()); function _normalizeAbsolutePath(path, hasPushState, absolute) { if (absolute === void 0) { absolute = false; } if (!hasPushState && path[0] !== '#') { path = '#' + path; } if (hasPushState && absolute) { path = path.substring(1, path.length); } return path; } function _createRootedPath(fragment, baseUrl, hasPushState, absolute) { if (isAbsoluteUrl.test(fragment)) { return fragment; } var path = ''; if (baseUrl.length && baseUrl[0] !== '/') { path += '/'; } path += baseUrl; if ((!path.length || path[path.length - 1] !== '/') && fragment[0] !== '/') { path += '/'; } if (path.length && path[path.length - 1] === '/' && fragment[0] === '/') { path = path.substring(0, path.length - 1); } return _normalizeAbsolutePath(path + fragment, hasPushState, absolute); } function _resolveUrl(fragment, baseUrl, hasPushState) { if (isRootedPath.test(fragment)) { return _normalizeAbsolutePath(fragment, hasPushState); } return _createRootedPath(fragment, baseUrl, hasPushState); } function _ensureArrayWithSingleRoutePerConfig(config) { var routeConfigs = []; if (Array.isArray(config.route)) { for (var i = 0, ii = config.route.length; i < ii; ++i) { var current = Object.assign({}, config); current.route = config.route[i]; routeConfigs.push(current); } } else { routeConfigs.push(Object.assign({}, config)); } return routeConfigs; } var isRootedPath = /^#?\//; var isAbsoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; /** * Class used to configure a [[Router]] instance. * * @constructor */ var RouterConfiguration = /** @class */ (function () { function RouterConfiguration() { this.instructions = []; this.options = {}; this.pipelineSteps = []; } /** * Adds a step to be run during the [[Router]]'s navigation pipeline. * * @param name The name of the pipeline slot to insert the step into. * @param step The pipeline step. * @chainable */ RouterConfiguration.prototype.addPipelineStep = function (name, step) { if (step === null || step === undefined) { throw new Error('Pipeline step cannot be null or undefined.'); } this.pipelineSteps.push({ name: name, step: step }); return this; }; /** * Adds a step to be run during the [[Router]]'s authorize pipeline slot. * * @param step The pipeline step. * @chainable */ RouterConfiguration.prototype.addAuthorizeStep = function (step) { return this.addPipelineStep("authorize" /* Authorize */, step); }; /** * Adds a step to be run during the [[Router]]'s preActivate pipeline slot. * * @param step The pipeline step. * @chainable */ RouterConfiguration.prototype.addPreActivateStep = function (step) { return this.addPipelineStep("preActivate" /* PreActivate */, step); }; /** * Adds a step to be run during the [[Router]]'s preRender pipeline slot. * * @param step The pipeline step. * @chainable */ RouterConfiguration.prototype.addPreRenderStep = function (step) { return this.addPipelineStep("preRender" /* PreRender */, step); }; /** * Adds a step to be run during the [[Router]]'s postRender pipeline slot. * * @param step The pipeline step. * @chainable */ RouterConfiguration.prototype.addPostRenderStep = function (step) { return this.addPipelineStep("postRender" /* PostRender */, step); }; /** * Configures a route that will be used if there is no previous location available on navigation cancellation. * * @param fragment The URL fragment to use as the navigation destination. * @chainable */ RouterConfiguration.prototype.fallbackRoute = function (fragment) { this._fallbackRoute = fragment; return this; }; /** * Maps one or more routes to be registered with the router. * * @param route The [[RouteConfig]] to map, or an array of [[RouteConfig]] to map. * @chainable */ RouterConfiguration.prototype.map = function (route) { var _this = this; if (Array.isArray(route)) { route.forEach(function (r) { return _this.map(r); }); return this; } return this.mapRoute(route); }; /** * Configures defaults to use for any view ports. * * @param viewPortConfig a view port configuration object to use as a * default, of the form { viewPortName: { moduleId } }. * @chainable */ RouterConfiguration.prototype.useViewPortDefaults = function (viewPortConfig) { this.viewPortDefaults = viewPortConfig; return this; }; /** * Maps a single route to be registered with the router. * * @param route The [[RouteConfig]] to map. * @chainable */ RouterConfiguration.prototype.mapRoute = function (config) { this.instructions.push(function (router) { var routeConfigs = _ensureArrayWithSingleRoutePerConfig(config); var navModel; for (var i = 0, ii = routeConfigs.length; i < ii; ++i) { var routeConfig = routeConfigs[i]; routeConfig.settings = routeConfig.settings || {}; if (!navModel) { navModel = router.createNavModel(routeConfig); } router.addRoute(routeConfig, navModel); } }); return this; }; /** * Registers an unknown route handler to be run when the URL fragment doesn't match any registered routes. * * @param config A string containing a moduleId to load, or a [[RouteConfig]], or a function that takes the * [[NavigationInstruction]] and selects a moduleId to load. * @chainable */ RouterConfiguration.prototype.mapUnknownRoutes = function (config) { this.unknownRouteConfig = config; return this; }; /** * Applies the current configuration to the specified [[Router]]. * * @param router The [[Router]] to apply the configuration to. */ RouterConfiguration.prototype.exportToRouter = function (router) { var instructions = this.instructions; for (var i = 0, ii = instructions.length; i < ii; ++i) { instructions[i](router); } var _a = this, title = _a.title, titleSeparator = _a.titleSeparator, unknownRouteConfig = _a.unknownRouteConfig, _fallbackRoute = _a._fallbackRoute, viewPortDefaults = _a.viewPortDefaults; if (title) { router.title = title; } if (titleSeparator) { router.titleSeparator = titleSeparator; } if (unknownRouteConfig) { router.handleUnknownRoutes(unknownRouteConfig); } if (_fallbackRoute) { router.fallbackRoute = _fallbackRoute; } if (viewPortDefaults) { router.useViewPortDefaults(viewPortDefaults); } Object.assign(router.options, this.options); var pipelineSteps = this.pipelineSteps; var pipelineStepCount = pipelineSteps.length; if (pipelineStepCount) { if (!router.isRoot) { throw new Error('Pipeline steps can only be added to the root router'); } var pipelineProvider = router.pipelineProvider; for (var i = 0, ii = pipelineStepCount; i < ii; ++i) { var _b = pipelineSteps[i], name_1 = _b.name, step = _b.step; pipelineProvider.addStep(name_1, step); } } }; return RouterConfiguration; }()); /** * The primary class responsible for handling routing and navigation. */ var Router = /** @class */ (function () { /** * @param container The [[Container]] to use when child routers. * @param history The [[History]] implementation to delegate navigation requests to. */ function Router(container, history) { var _this = this; /** * The parent router, or null if this instance is not a child router. */ this.parent = null; this.options = {}; /** * The defaults used when a viewport lacks specified content */ this.viewPortDefaults = {}; /** * Extension point to transform the document title before it is built and displayed. * By default, child routers delegate to the parent router, and the app router * returns the title unchanged. */ this.transformTitle = function (title) { if (_this.parent) { return _this.parent.transformTitle(title); } return title; }; this.container = container; this.history = history; this.reset(); } /** * Fully resets the router's internal state. Primarily used internally by the framework when multiple calls to setRoot are made. * Use with caution (actually, avoid using this). Do not use this to simply change your navigation model. */ Router.prototype.reset = function () { var _this = this; this.viewPorts = {}; this.routes = []; this.baseUrl = ''; this.isConfigured = false; this.isNavigating = false; this.isExplicitNavigation = false; this.isExplicitNavigationBack = false; this.isNavigatingFirst = false; this.isNavigatingNew = false; this.isNavigatingRefresh = false; this.isNavigatingForward = false; this.isNavigatingBack = false; this.couldDeactivate = false; this.navigation = []; this.currentInstruction = null; this.viewPortDefaults = {}; this._fallbackOrder = 100; this._recognizer = new RouteRecognizer(); this._childRecognizer = new RouteRecognizer(); this._configuredPromise = new Promise(function (resolve) { _this._resolveConfiguredPromise = resolve; }); }; Object.defineProperty(Router.prototype, "isRoot", { /** * Gets a value indicating whether or not this [[Router]] is the root in the router tree. I.e., it has no parent. */ get: function () { return !this.parent; }, enumerable: true, configurable: true }); /** * Registers a viewPort to be used as a rendering target for activated routes. * * @param viewPort The viewPort. * @param name The name of the viewPort. 'default' if unspecified. */ Router.prototype.registerViewPort = function (viewPort, name) { name = name || 'default'; this.viewPorts[name] = viewPort; }; /** * Returns a Promise that resolves when the router is configured. */ Router.prototype.ensureConfigured = function () { return this._configuredPromise; }; /** * Configures the router. * * @param callbackOrConfig The [[RouterConfiguration]] or a callback that takes a [[RouterConfiguration]]. */ Router.prototype.configure = function (callbackOrConfig) { var _this = this; this.isConfigured = true; var result = callbackOrConfig; var config; if (typeof callbackOrConfig === 'function') { config = new RouterConfiguration(); result = callbackOrConfig(config); } return Promise .resolve(result) .then(function (c) { if (c && c.exportToRouter) { config = c; } config.exportToRouter(_this); _this.isConfigured = true; _this._resolveConfiguredPromise(); }); }; /** * Navigates to a new location. * * @param fragment The URL fragment to use as the navigation destination. * @param options The navigation options. */ Router.prototype.navigate = function (fragment, options) { if (!this.isConfigured && this.parent) { return this.parent.navigate(fragment, options); } this.isExplicitNavigation = true; return this.history.navigate(_resolveUrl(fragment, this.baseUrl, this.history._hasPushState), options); }; /** * Navigates to a new location corresponding to the route and params specified. Equivallent to [[Router.generate]] followed * by [[Router.navigate]]. * * @param route The name of the route to use when generating the navigation location. * @param params The route parameters to be used when populating the route pattern. * @param options The navigation options. */ Router.prototype.navigateToRoute = function (route, params, options) { var path = this.generate(route, params); return this.navigate(path, options); }; /** * Navigates back to the most recent location in history. */ Router.prototype.navigateBack = function () { this.isExplicitNavigationBack = true; this.history.navigateBack(); }; /** * Creates a child router of the current router. * * @param container The [[Container]] to provide to the child router. Uses the current [[Router]]'s [[Container]] if unspecified. * @returns {Router} The new child Router. */ Router.prototype.createChild = function (container) { var childRouter = new Router(container || this.container.createChild(), this.history); childRouter.parent = this; return childRouter; }; /** * Generates a URL fragment matching the specified route pattern. * * @param name The name of the route whose pattern should be used to generate the fragment. * @param params The route params to be used to populate the route pattern. * @param options If options.absolute = true, then absolute url will be generated; otherwise, it will be relative url. * @returns {string} A string containing the generated URL fragment. */ Router.prototype.generate = function (nameOrRoute, params, options) { if (params === void 0) { params = {}; } if (options === void 0) { options = {}; } // A child recognizer generates routes for potential child routes. Any potential child route is added // to the childRoute property of params for the childRouter to recognize. When generating routes, we // use the childRecognizer when childRoute params are available to generate a child router enabled route. var recognizer = 'childRoute' in params ? this._childRecognizer : this._recognizer; var hasRoute = recognizer.hasRoute(nameOrRoute); if (!hasRoute) { if (this.parent) { return this.parent.generate(nameOrRoute, params, options); } throw new Error("A route with name '" + nameOrRoute + "' could not be found. Check that `name: '" + nameOrRoute + "'` was specified in the route's config."); } var path = recognizer.generate(nameOrRoute, params); var rootedPath = _createRootedPath(path, this.baseUrl, this.history._hasPushState, options.absolute); return options.absolute ? "" + this.history.getAbsoluteRoot() + rootedPath : rootedPath; }; /** * Creates a [[NavModel]] for the specified route config. * * @param config The route config. */ Router.prototype.createNavModel = function (config) { var navModel = new NavModel(this, 'href' in config ? config.href // potential error when config.route is a string[] ? : config.route); navModel.title = config.title; navModel.order = config.nav; navModel.href = config.href; navModel.settings = config.settings; navModel.config = config; return navModel; }; /** * Registers a new route with the router. * * @param config The [[RouteConfig]]. * @param navModel The [[NavModel]] to use for the route. May be omitted for single-pattern routes. */ Router.prototype.addRoute = function (config, navModel) { if (Array.isArray(config.route)) { var routeConfigs = _ensureArrayWithSingleRoutePerConfig(config); // the following is wrong. todo: fix this after TS refactoring release routeConfigs.forEach(this.addRoute.bind(this)); return; } validateRouteConfig(config); if (!('viewPorts' in config) && !config.navigationStrategy) { config.viewPorts = { 'default': { moduleId: config.moduleId, view: config.view } }; } if (!navModel) { navModel = this.createNavModel(config); } this.routes.push(config); var path = config.route; if (path.charAt(0) === '/') { path = path.substr(1); } var caseSensitive = config.caseSensitive === true; var state = this._recognizer.add({ path: path, handler: config, caseSensitive: caseSensitive }); if (path) { var settings = config.settings; delete config.settings; var withChild = JSON.parse(JSON.stringify(config)); config.settings = settings; withChild.route = path + "/*childRoute"; withChild.hasChildRouter = true; this._childRecognizer.add({ path: withChild.route, handler: withChild, caseSensitive: caseSensitive }); withChild.navModel = navModel; withChild.settings = config.settings; withChild.navigationStrategy = config.navigationStrategy; } config.navModel = navModel; var navigation = this.navigation; if ((navModel.order || navModel.order === 0) && navigation.indexOf(navModel) === -1) { if ((!navModel.href && navModel.href !== '') && (state.types.dynamics || state.types.stars)) { throw new Error('Invalid route config for "' + config.route + '" : dynamic routes must specify an "href:" to be included in the navigation model.'); } if (typeof navModel.order !== 'number') { navModel.order = ++this._fallbackOrder; } navigation.push(navModel); // this is a potential error / inconsistency between browsers // // MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort // If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, // but sorted with respect to all different elements. // Note: the ECMAscript standard does not guarantee this behaviour, // and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this. navigation.sort(function (a, b) { return a.order - b.order; }); } }; /** * Gets a value indicating whether or not this [[Router]] or one of its ancestors has a route registered with the specified name. * * @param name The name of the route to check. */ Router.prototype.hasRoute = function (name) { return !!(this._recognizer.hasRoute(name) || this.parent && this.parent.hasRoute(name)); }; /** * Gets a value indicating whether or not this [[Router]] has a route registered with the specified name. * * @param name The name of the route to check. */ Router.prototype.hasOwnRoute = function (name) { return this._recognizer.hasRoute(name); }; /** * Register a handler to use when the incoming URL fragment doesn't match any registered routes. * * @param config The moduleId, or a function that selects the moduleId, or a [[RouteConfig]]. */ Router.prototype.handleUnknownRoutes = function (config) { var _this = this; if (!config) { throw new Error('Invalid unknown route handler'); } this.catchAllHandler = function (instruction) { return _this ._createRouteConfig(config, instruction) .then(function (c) { instruction.config = c; return instruction; }); }; }; /** * Updates the document title using the current navigation instruction. */ Router.prototype.updateTitle = function () { var parentRouter = this.parent; if (parentRouter) { return parentRouter.updateTitle(); } var currentInstruction = this.currentInstruction; if (currentInstruction) { currentInstruction._updateTitle(); } return undefined; }; /** * Updates the navigation routes with hrefs relative to the current location. * Note: This method will likely move to a plugin in a future release. */ Router.prototype.refreshNavigation = function () { var nav = this.navigation; for (var i = 0, length_1 = nav.length; i < length_1; i++) { var current = nav[i]; if (!current.config.href) { current.href = _createRootedPath(current.relativeHref, this.baseUrl, this.history._hasPushState); } else { current.href = _normalizeAbsolutePath(current.config.href, this.history._hasPushState); } } }; /** * Sets the default configuration for the view ports. This specifies how to * populate a view port for which no module is specified. The default is * an empty view/view-model pair. */ Router.prototype.useViewPortDefaults = function ($viewPortDefaults) { // a workaround to have strong typings while not requiring to expose interface ViewPortInstruction var viewPortDefaults = $viewPortDefaults; for (var viewPortName in viewPortDefaults) { var viewPortConfig = viewPortDefaults[viewPortName]; this.viewPortDefaults[viewPortName] = { moduleId: viewPortConfig.moduleId }; } }; /**@internal */ Router.prototype._refreshBaseUrl = function () { var parentRouter = this.parent; if (parentRouter) { this.baseUrl = generateBaseUrl(parentRouter, parentRouter.currentInstruction); } }; /**@internal */ Router.prototype._createNavigationInstruction = function (url, parentInstruction) { if (url === void 0) { url = ''; } if (parentInstruction === void 0) { parentInstruction = null; } var fragment = url; var queryString = ''; var queryIndex = url.indexOf('?'); if (queryIndex !== -1) { fragment = url.substr(0, queryIndex); queryString = url.substr(queryIndex + 1); } var urlRecognizationResults = this._recognizer.recognize(url); if (!urlRecognizationResults || !urlRecognizationResults.length) { urlRecognizationResults = this._childRecognizer.recognize(url); } var instructionInit = { fragment: fragment, queryString: queryString, config: null, parentInstruction: parentInstruction, previousInstruction: this.currentInstruction, router: this, options: { compareQueryParams: this.options.compareQueryParams } }; var result; if (urlRecognizationResults && urlRecognizationResults.length) { var first = urlRecognizationResults[0]; var instruction = new NavigationInstruction(Object.assign({}, instructionInit, { params: first.params, queryParams: first.queryParams || urlRecognizationResults.queryParams, config: first.config || first.handler })); if (typeof first.handler === 'function') { result = evaluateNavigationStrategy(instruction, first.handler, first); } else if (first.handler && typeof first.handler.navigationStrategy === 'function') { result = evaluateNavigationStrategy(instruction, first.handler.navigationStrategy, first.handler); } else { result = Promise.resolve(instruction); } } else if (this.catchAllHandler) { var instruction = new NavigationInstruction(Object.assign({}, instructionInit, { params: { path: fragment }, queryParams: urlRecognizationResults ? urlRecognizationResults.queryParams : {}, config: null // config will be created by the catchAllHandler })); result = evaluateNavigationStrategy(instruction, this.catchAllHandler); } else if (this.parent) { var router = this._parentCatchAllHandler(this.parent); if (router) { var newParentInstruction = this._findParentInstructionFromRouter(router, parentInstruction); var instruction = new NavigationInstruction(Object.assign({}, instructionInit, { params: { path: fragment }, queryParams: urlRecognizationResults ? urlRecognizationResults.queryParams : {}, router: router, parentInstruction: newParentInstruction, parentCatchHandler: true, config: null // config will be created by the chained parent catchAllHandler })); result = evaluateNavigationStrategy(instruction, router.catchAllHandler); } } if (result && parentInstruction) { this.baseUrl = generateBaseUrl(this.parent, parentInstruction); } return result || Promise.reject(new Error("Route not found: " + url)); }; /**@internal */ Router.prototype._findParentInstructionFromRouter = function (router, instruction) { if (instruction.router === router) { instruction.fragment = router.baseUrl; // need to change the fragment in case of a redirect instead of moduleId return instruction; } else if (instruction.parentInstruction) { return this._findParentInstructionFromRouter(router, instruction.parentInstruction); } return undefined; }; /**@internal */ Router.prototype._parentCatchAllHandler = function (router) { if (router.catchAllHandler) { return router; } else if (router.parent) { return this._parentCatchAllHandler(router.parent); } return false; }; /** * @internal */ Router.prototype._createRouteConfig = function (config, instruction) { var _this = this; return Promise .resolve(config) .then(function (c) { if (typeof c === 'string') { return { moduleId: c }; } else if (typeof c === 'function') { return c(instruction); } return c; }) // typing here could be either RouteConfig or RedirectConfig // but temporarily treat both as RouteConfig // todo: improve typings precision .then(function (c) { return typeof c === 'string' ? { moduleId: c } : c; }) .then(function (c) { c.route = instruction.params.path; validateRouteConfig(c); if (!c.navModel) { c.navModel = _this.createNavModel(c); } return c; }); }; return Router; }()); /* @internal exported for unit testing */ var generateBaseUrl = function (router, instruction) { return "" + (router.baseUrl || '') + (instruction.getBaseUrl() || ''); }; /* @internal exported for unit testing */ var validateRouteConfig = function (config) { if (typeof config !== 'object') { throw new Error('Invalid Route Config'); } if (typeof config.route !== 'string') { var name_1 = config.name || '(no name)'; throw new Error('Invalid Route Config for "' + name_1 + '": You must specify a "route:" pattern.'); } if (!('redirect' in config || config.moduleId || config.navigationStrategy || config.viewPorts)) { throw new Error('Invalid Route Config for "' + config.route + '": You must specify a "moduleId:", "redirect:", "navigationStrategy:", or "viewPorts:".'); } }; /* @internal exported for unit testing */ var evaluateNavigationStrategy = function (instruction, evaluator, context) { return Promise .resolve(evaluator.call(context, instruction)) .then(function () { if (!('viewPorts' in instruction.config)) { instruction.config.viewPorts = { 'default': { moduleId: instruction.config.moduleId } }; } return instruction; }); }; /**@internal exported for unit testing */ var createNextFn = function (instruction, steps) { var index = -1; var next = function () { index++; if (index < steps.length) { var currentStep = steps[index]; try { return currentStep(instruction, next); } catch (e) { return next.reject(e); } } else { return next.complete(); } }; next.complete = createCompletionHandler(next, "completed" /* Completed */); next.cancel = createCompletionHandler(next, "canceled" /* Canceled */); next.reject = createCompletionHandler(next, "rejected" /* Rejected */); return next; }; /**@internal exported for unit testing */ var createCompletionHandler = function (next, status) { return function (output) { return Promise .resolve({ status: status, output: output, completed: status === "completed" /* Completed */ }); }; }; /** * The class responsible for managing and processing the navigation pipeline. */ var Pipeline = /** @class */ (function () { function Pipeline() { /** * The pipeline steps. And steps added via addStep will be converted to a function * The actualy running functions with correct step contexts of this pipeline */ this.steps = []; } /** * Adds a step to the pipeline. * * @param step The pipeline step. */ Pipeline.prototype.addStep = function (step) { var run; if (typeof step === 'function') { run = step; } else if (typeof step.getSteps === 'function') { // getSteps is to enable support open slots // where devs can add multiple steps into the same slot name var steps = step.getSteps(); for (var i = 0, l = steps.length; i < l; i++) { this.addStep(steps[i]); } return this; } else { run = step.run.bind(step); } this.steps.push(run); return this; }; /** * Runs the pipeline. * * @param instruction The navigation instruction to process. */ Pipeline.prototype.run = function (instruction) { var nextFn = createNextFn(instruction, this.steps); return nextFn(); }; return Pipeline; }()); /** * Determines if the provided object is a navigation command. * A navigation command is anything with a navigate method. * * @param obj The object to check. */ function isNavigationCommand(obj) { return obj && typeof obj.navigate === 'function'; } /** * Used during the activation lifecycle to cause a redirect. */ var Redirect = /** @class */ (function () { /** * @param url The URL fragment to use as the navigation destination. * @param options The navigation options. */ function Redirect(url, options) { if (options === void 0) { options = {}; } this.url = url; this.options = Object.assign({ trigger: true, replace: true }, options); this.shouldContinueProcessing = false; } /** * Called by the activation system to set the child router. * * @param router The router. */ Redirect.prototype.setRouter = function (router) { this.router = router; }; /** * Called by the navigation pipeline to navigate. * * @param appRouter The router to be redirected. */ Redirect.prototype.navigate = function (appRouter) { var navigatingRouter = this.options.useAppRouter ? appRouter : (this.router || appRouter); navigatingRouter.navigate(this.url, this.options); }; return Redirect; }()); /** * Used during the activation lifecycle to cause a redirect to a named route. */ var RedirectToRoute = /** @class */ (function () { /** * @param route The name of the route. * @param params The parameters to be sent to the activation method. * @param options The options to use for navigation. */ function RedirectToRoute(route, params, options) { if (params === void 0) { params = {}; } if (options === void 0) { options = {}; } this.route = route; this.params = params; this.options = Object.assign({ trigger: true, replace: true }, options); this.shouldContinueProcessing = false; } /** * Called by the activation system to set the child router. * * @param router The router. */ RedirectToRoute.prototype.setRouter = function (router) { this.router = router; }; /** * Called by the navigation pipeline to navigate. * * @param appRouter The router to be redirected. */ RedirectToRoute.prototype.navigate = function (appRouter) { var navigatingRouter = this.options.useAppRouter ? appRouter : (this.router || appRouter); navigatingRouter.navigateToRoute(this.route, this.params, this.options); }; return RedirectToRoute; }()); /** * @internal exported for unit testing */ function _buildNavigationPlan(instruction, forceLifecycleMinimum) { var config = instruction.config; if ('redirect' in config) { return buildRedirectPlan(instruction); } var prevInstruction = instruction.previousInstruction; var defaultViewPortConfigs = instruction.router.viewPortDefaults; if (prevInstruction) { return buildTransitionPlans(instruction, prevInstruction, defaultViewPortConfigs, forceLifecycleMinimum); } // first navigation, only need to prepare a few information for each viewport plan var viewPortPlans = {}; var viewPortConfigs = config.viewPorts; for (var viewPortName in viewPortConfigs) { var viewPortConfig = viewPortConfigs[viewPortName]; if (viewPortConfig.moduleId === null && viewPortName in defaultViewPortConfigs) { viewPortConfig = defaultViewPortConfigs[viewPortName]; } viewPortPlans[viewPortName] = { name: viewPortName, strategy: "replace" /* Replace */, config: viewPortConfig }; } return Promise.resolve(viewPortPlans); } /** * Build redirect plan based on config of a navigation instruction * @internal exported for unit testing */ var buildRedirectPlan = function (instruction) { var config = instruction.config; var router = instruction.router; return router ._createNavigationInstruction(config.redirect) .then(function (redirectInstruction) { var params = {}; var originalInstructionParams = instruction.params; var redirectInstructionParams = redirectInstruction.params; for (var key in redirectInstructionParams) { // If the param on the redirect points to another param, e.g. { route: first/:this, redirect: second/:this } var val = redirectInstructionParams[key]; if (typeof val === 'string' && val[0] === ':') { val = val.slice(1); // And if that param is found on the original instruction then use it if (val in originalInstructionParams) { params[key] = originalInstructionParams[val]; } } else {