UNPKG

84.5 kBJavaScriptView Raw
1import { getLogger } from 'aurelia-logging';
2import { Container } from 'aurelia-dependency-injection';
3import { History } from 'aurelia-history';
4import { RouteRecognizer } from 'aurelia-route-recognizer';
5import { EventAggregator } from 'aurelia-event-aggregator';
6
7/**
8 * Class used to represent an instruction during a navigation.
9 */
10class NavigationInstruction {
11 constructor(init) {
12 /**
13 * Current built viewport plan of this nav instruction
14 */
15 this.plan = null;
16 this.options = {};
17 Object.assign(this, init);
18 this.params = this.params || {};
19 this.viewPortInstructions = {};
20 let ancestorParams = [];
21 let current = this;
22 do {
23 let currentParams = Object.assign({}, current.params);
24 if (current.config && current.config.hasChildRouter) {
25 // remove the param for the injected child route segment
26 delete currentParams[current.getWildCardName()];
27 }
28 ancestorParams.unshift(currentParams);
29 current = current.parentInstruction;
30 } while (current);
31 let allParams = Object.assign({}, this.queryParams, ...ancestorParams);
32 this.lifecycleArgs = [allParams, this.config, this];
33 }
34 /**
35 * Gets an array containing this instruction and all child instructions for the current navigation.
36 */
37 getAllInstructions() {
38 let instructions = [this];
39 let viewPortInstructions = this.viewPortInstructions;
40 for (let key in viewPortInstructions) {
41 let childInstruction = viewPortInstructions[key].childNavigationInstruction;
42 if (childInstruction) {
43 instructions.push(...childInstruction.getAllInstructions());
44 }
45 }
46 return instructions;
47 }
48 /**
49 * Gets an array containing the instruction and all child instructions for the previous navigation.
50 * Previous instructions are no longer available after navigation completes.
51 */
52 getAllPreviousInstructions() {
53 return this.getAllInstructions().map(c => c.previousInstruction).filter(c => c);
54 }
55 addViewPortInstruction(nameOrInitOptions, strategy, moduleId, component) {
56 let viewPortInstruction;
57 let viewPortName = typeof nameOrInitOptions === 'string' ? nameOrInitOptions : nameOrInitOptions.name;
58 const lifecycleArgs = this.lifecycleArgs;
59 const config = Object.assign({}, lifecycleArgs[1], { currentViewPort: viewPortName });
60 if (typeof nameOrInitOptions === 'string') {
61 viewPortInstruction = {
62 name: nameOrInitOptions,
63 strategy: strategy,
64 moduleId: moduleId,
65 component: component,
66 childRouter: component.childRouter,
67 lifecycleArgs: [lifecycleArgs[0], config, lifecycleArgs[2]]
68 };
69 }
70 else {
71 viewPortInstruction = {
72 name: viewPortName,
73 strategy: nameOrInitOptions.strategy,
74 component: nameOrInitOptions.component,
75 moduleId: nameOrInitOptions.moduleId,
76 childRouter: nameOrInitOptions.component.childRouter,
77 lifecycleArgs: [lifecycleArgs[0], config, lifecycleArgs[2]]
78 };
79 }
80 return this.viewPortInstructions[viewPortName] = viewPortInstruction;
81 }
82 /**
83 * Gets the name of the route pattern's wildcard parameter, if applicable.
84 */
85 getWildCardName() {
86 // todo: potential issue, or at least unsafe typings
87 let configRoute = this.config.route;
88 let wildcardIndex = configRoute.lastIndexOf('*');
89 return configRoute.substr(wildcardIndex + 1);
90 }
91 /**
92 * Gets the path and query string created by filling the route
93 * pattern's wildcard parameter with the matching param.
94 */
95 getWildcardPath() {
96 let wildcardName = this.getWildCardName();
97 let path = this.params[wildcardName] || '';
98 let queryString = this.queryString;
99 if (queryString) {
100 path += '?' + queryString;
101 }
102 return path;
103 }
104 /**
105 * Gets the instruction's base URL, accounting for wildcard route parameters.
106 */
107 getBaseUrl() {
108 let $encodeURI = encodeURI;
109 let fragment = decodeURI(this.fragment);
110 if (fragment === '') {
111 let nonEmptyRoute = this.router.routes.find(route => {
112 return route.name === this.config.name &&
113 route.route !== '';
114 });
115 if (nonEmptyRoute) {
116 fragment = nonEmptyRoute.route;
117 }
118 }
119 if (!this.params) {
120 return $encodeURI(fragment);
121 }
122 let wildcardName = this.getWildCardName();
123 let path = this.params[wildcardName] || '';
124 if (!path) {
125 return $encodeURI(fragment);
126 }
127 return $encodeURI(fragment.substr(0, fragment.lastIndexOf(path)));
128 }
129 /**
130 * Finalize a viewport instruction
131 * @internal
132 */
133 _commitChanges(waitToSwap) {
134 let router = this.router;
135 router.currentInstruction = this;
136 const previousInstruction = this.previousInstruction;
137 if (previousInstruction) {
138 previousInstruction.config.navModel.isActive = false;
139 }
140 this.config.navModel.isActive = true;
141 router.refreshNavigation();
142 let loads = [];
143 let delaySwaps = [];
144 let viewPortInstructions = this.viewPortInstructions;
145 for (let viewPortName in viewPortInstructions) {
146 let viewPortInstruction = viewPortInstructions[viewPortName];
147 let viewPort = router.viewPorts[viewPortName];
148 if (!viewPort) {
149 throw new Error(`There was no router-view found in the view for ${viewPortInstruction.moduleId}.`);
150 }
151 let childNavInstruction = viewPortInstruction.childNavigationInstruction;
152 if (viewPortInstruction.strategy === "replace" /* Replace */) {
153 if (childNavInstruction && childNavInstruction.parentCatchHandler) {
154 loads.push(childNavInstruction._commitChanges(waitToSwap));
155 }
156 else {
157 if (waitToSwap) {
158 delaySwaps.push({ viewPort, viewPortInstruction });
159 }
160 loads.push(viewPort
161 .process(viewPortInstruction, waitToSwap)
162 .then(() => childNavInstruction
163 ? childNavInstruction._commitChanges(waitToSwap)
164 : Promise.resolve()));
165 }
166 }
167 else {
168 if (childNavInstruction) {
169 loads.push(childNavInstruction._commitChanges(waitToSwap));
170 }
171 }
172 }
173 return Promise
174 .all(loads)
175 .then(() => {
176 delaySwaps.forEach(x => x.viewPort.swap(x.viewPortInstruction));
177 return null;
178 })
179 .then(() => prune(this));
180 }
181 /**@internal */
182 _updateTitle() {
183 let router = this.router;
184 let title = this._buildTitle(router.titleSeparator);
185 if (title) {
186 router.history.setTitle(title);
187 }
188 }
189 /**@internal */
190 _buildTitle(separator = ' | ') {
191 let title = '';
192 let childTitles = [];
193 let navModelTitle = this.config.navModel.title;
194 let instructionRouter = this.router;
195 let viewPortInstructions = this.viewPortInstructions;
196 if (navModelTitle) {
197 title = instructionRouter.transformTitle(navModelTitle);
198 }
199 for (let viewPortName in viewPortInstructions) {
200 let viewPortInstruction = viewPortInstructions[viewPortName];
201 let child_nav_instruction = viewPortInstruction.childNavigationInstruction;
202 if (child_nav_instruction) {
203 let childTitle = child_nav_instruction._buildTitle(separator);
204 if (childTitle) {
205 childTitles.push(childTitle);
206 }
207 }
208 }
209 if (childTitles.length) {
210 title = childTitles.join(separator) + (title ? separator : '') + title;
211 }
212 if (instructionRouter.title) {
213 title += (title ? separator : '') + instructionRouter.transformTitle(instructionRouter.title);
214 }
215 return title;
216 }
217}
218const prune = (instruction) => {
219 instruction.previousInstruction = null;
220 instruction.plan = null;
221};
222
223/**
224* Class for storing and interacting with a route's navigation settings.
225*/
226class NavModel {
227 constructor(router, relativeHref) {
228 /**
229 * True if this nav item is currently active.
230 */
231 this.isActive = false;
232 /**
233 * The title.
234 */
235 this.title = null;
236 /**
237 * This nav item's absolute href.
238 */
239 this.href = null;
240 /**
241 * This nav item's relative href.
242 */
243 this.relativeHref = null;
244 /**
245 * Data attached to the route at configuration time.
246 */
247 this.settings = {};
248 /**
249 * The route config.
250 */
251 this.config = null;
252 this.router = router;
253 this.relativeHref = relativeHref;
254 }
255 /**
256 * Sets the route's title and updates document.title.
257 * If the a navigation is in progress, the change will be applied
258 * to document.title when the navigation completes.
259 *
260 * @param title The new title.
261 */
262 setTitle(title) {
263 this.title = title;
264 if (this.isActive) {
265 this.router.updateTitle();
266 }
267 }
268}
269
270function _normalizeAbsolutePath(path, hasPushState, absolute = false) {
271 if (!hasPushState && path[0] !== '#') {
272 path = '#' + path;
273 }
274 if (hasPushState && absolute) {
275 path = path.substring(1, path.length);
276 }
277 return path;
278}
279function _createRootedPath(fragment, baseUrl, hasPushState, absolute) {
280 if (isAbsoluteUrl.test(fragment)) {
281 return fragment;
282 }
283 let path = '';
284 if (baseUrl.length && baseUrl[0] !== '/') {
285 path += '/';
286 }
287 path += baseUrl;
288 if ((!path.length || path[path.length - 1] !== '/') && fragment[0] !== '/') {
289 path += '/';
290 }
291 if (path.length && path[path.length - 1] === '/' && fragment[0] === '/') {
292 path = path.substring(0, path.length - 1);
293 }
294 return _normalizeAbsolutePath(path + fragment, hasPushState, absolute);
295}
296function _resolveUrl(fragment, baseUrl, hasPushState) {
297 if (isRootedPath.test(fragment)) {
298 return _normalizeAbsolutePath(fragment, hasPushState);
299 }
300 return _createRootedPath(fragment, baseUrl, hasPushState);
301}
302function _ensureArrayWithSingleRoutePerConfig(config) {
303 let routeConfigs = [];
304 if (Array.isArray(config.route)) {
305 for (let i = 0, ii = config.route.length; i < ii; ++i) {
306 let current = Object.assign({}, config);
307 current.route = config.route[i];
308 routeConfigs.push(current);
309 }
310 }
311 else {
312 routeConfigs.push(Object.assign({}, config));
313 }
314 return routeConfigs;
315}
316const isRootedPath = /^#?\//;
317const isAbsoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i;
318
319/**
320 * Class used to configure a [[Router]] instance.
321 *
322 * @constructor
323 */
324class RouterConfiguration {
325 constructor() {
326 this.instructions = [];
327 this.options = {};
328 this.pipelineSteps = [];
329 }
330 /**
331 * Adds a step to be run during the [[Router]]'s navigation pipeline.
332 *
333 * @param name The name of the pipeline slot to insert the step into.
334 * @param step The pipeline step.
335 * @chainable
336 */
337 addPipelineStep(name, step) {
338 if (step === null || step === undefined) {
339 throw new Error('Pipeline step cannot be null or undefined.');
340 }
341 this.pipelineSteps.push({ name, step });
342 return this;
343 }
344 /**
345 * Adds a step to be run during the [[Router]]'s authorize pipeline slot.
346 *
347 * @param step The pipeline step.
348 * @chainable
349 */
350 addAuthorizeStep(step) {
351 return this.addPipelineStep("authorize" /* Authorize */, step);
352 }
353 /**
354 * Adds a step to be run during the [[Router]]'s preActivate pipeline slot.
355 *
356 * @param step The pipeline step.
357 * @chainable
358 */
359 addPreActivateStep(step) {
360 return this.addPipelineStep("preActivate" /* PreActivate */, step);
361 }
362 /**
363 * Adds a step to be run during the [[Router]]'s preRender pipeline slot.
364 *
365 * @param step The pipeline step.
366 * @chainable
367 */
368 addPreRenderStep(step) {
369 return this.addPipelineStep("preRender" /* PreRender */, step);
370 }
371 /**
372 * Adds a step to be run during the [[Router]]'s postRender pipeline slot.
373 *
374 * @param step The pipeline step.
375 * @chainable
376 */
377 addPostRenderStep(step) {
378 return this.addPipelineStep("postRender" /* PostRender */, step);
379 }
380 /**
381 * Configures a route that will be used if there is no previous location available on navigation cancellation.
382 *
383 * @param fragment The URL fragment to use as the navigation destination.
384 * @chainable
385 */
386 fallbackRoute(fragment) {
387 this._fallbackRoute = fragment;
388 return this;
389 }
390 /**
391 * Maps one or more routes to be registered with the router.
392 *
393 * @param route The [[RouteConfig]] to map, or an array of [[RouteConfig]] to map.
394 * @chainable
395 */
396 map(route) {
397 if (Array.isArray(route)) {
398 route.forEach(r => this.map(r));
399 return this;
400 }
401 return this.mapRoute(route);
402 }
403 /**
404 * Configures defaults to use for any view ports.
405 *
406 * @param viewPortConfig a view port configuration object to use as a
407 * default, of the form { viewPortName: { moduleId } }.
408 * @chainable
409 */
410 useViewPortDefaults(viewPortConfig) {
411 this.viewPortDefaults = viewPortConfig;
412 return this;
413 }
414 /**
415 * Maps a single route to be registered with the router.
416 *
417 * @param route The [[RouteConfig]] to map.
418 * @chainable
419 */
420 mapRoute(config) {
421 this.instructions.push(router => {
422 let routeConfigs = _ensureArrayWithSingleRoutePerConfig(config);
423 let navModel;
424 for (let i = 0, ii = routeConfigs.length; i < ii; ++i) {
425 let routeConfig = routeConfigs[i];
426 routeConfig.settings = routeConfig.settings || {};
427 if (!navModel) {
428 navModel = router.createNavModel(routeConfig);
429 }
430 router.addRoute(routeConfig, navModel);
431 }
432 });
433 return this;
434 }
435 /**
436 * Registers an unknown route handler to be run when the URL fragment doesn't match any registered routes.
437 *
438 * @param config A string containing a moduleId to load, or a [[RouteConfig]], or a function that takes the
439 * [[NavigationInstruction]] and selects a moduleId to load.
440 * @chainable
441 */
442 mapUnknownRoutes(config) {
443 this.unknownRouteConfig = config;
444 return this;
445 }
446 /**
447 * Applies the current configuration to the specified [[Router]].
448 *
449 * @param router The [[Router]] to apply the configuration to.
450 */
451 exportToRouter(router) {
452 let instructions = this.instructions;
453 for (let i = 0, ii = instructions.length; i < ii; ++i) {
454 instructions[i](router);
455 }
456 let { title, titleSeparator, unknownRouteConfig, _fallbackRoute, viewPortDefaults } = this;
457 if (title) {
458 router.title = title;
459 }
460 if (titleSeparator) {
461 router.titleSeparator = titleSeparator;
462 }
463 if (unknownRouteConfig) {
464 router.handleUnknownRoutes(unknownRouteConfig);
465 }
466 if (_fallbackRoute) {
467 router.fallbackRoute = _fallbackRoute;
468 }
469 if (viewPortDefaults) {
470 router.useViewPortDefaults(viewPortDefaults);
471 }
472 Object.assign(router.options, this.options);
473 let pipelineSteps = this.pipelineSteps;
474 let pipelineStepCount = pipelineSteps.length;
475 if (pipelineStepCount) {
476 if (!router.isRoot) {
477 throw new Error('Pipeline steps can only be added to the root router');
478 }
479 let pipelineProvider = router.pipelineProvider;
480 for (let i = 0, ii = pipelineStepCount; i < ii; ++i) {
481 let { name, step } = pipelineSteps[i];
482 pipelineProvider.addStep(name, step);
483 }
484 }
485 }
486}
487
488/**
489 * The primary class responsible for handling routing and navigation.
490 */
491class Router {
492 /**
493 * @param container The [[Container]] to use when child routers.
494 * @param history The [[History]] implementation to delegate navigation requests to.
495 */
496 constructor(container, history) {
497 /**
498 * The parent router, or null if this instance is not a child router.
499 */
500 this.parent = null;
501 this.options = {};
502 /**
503 * The defaults used when a viewport lacks specified content
504 */
505 this.viewPortDefaults = {};
506 /**
507 * Extension point to transform the document title before it is built and displayed.
508 * By default, child routers delegate to the parent router, and the app router
509 * returns the title unchanged.
510 */
511 this.transformTitle = (title) => {
512 if (this.parent) {
513 return this.parent.transformTitle(title);
514 }
515 return title;
516 };
517 this.container = container;
518 this.history = history;
519 this.reset();
520 }
521 /**
522 * Fully resets the router's internal state. Primarily used internally by the framework when multiple calls to setRoot are made.
523 * Use with caution (actually, avoid using this). Do not use this to simply change your navigation model.
524 */
525 reset() {
526 this.viewPorts = {};
527 this.routes = [];
528 this.baseUrl = '';
529 this.isConfigured = false;
530 this.isNavigating = false;
531 this.isExplicitNavigation = false;
532 this.isExplicitNavigationBack = false;
533 this.isNavigatingFirst = false;
534 this.isNavigatingNew = false;
535 this.isNavigatingRefresh = false;
536 this.isNavigatingForward = false;
537 this.isNavigatingBack = false;
538 this.couldDeactivate = false;
539 this.navigation = [];
540 this.currentInstruction = null;
541 this.viewPortDefaults = {};
542 this._fallbackOrder = 100;
543 this._recognizer = new RouteRecognizer();
544 this._childRecognizer = new RouteRecognizer();
545 this._configuredPromise = new Promise(resolve => {
546 this._resolveConfiguredPromise = resolve;
547 });
548 }
549 /**
550 * Gets a value indicating whether or not this [[Router]] is the root in the router tree. I.e., it has no parent.
551 */
552 get isRoot() {
553 return !this.parent;
554 }
555 /**
556 * Registers a viewPort to be used as a rendering target for activated routes.
557 *
558 * @param viewPort The viewPort.
559 * @param name The name of the viewPort. 'default' if unspecified.
560 */
561 registerViewPort(viewPort, name) {
562 name = name || 'default';
563 this.viewPorts[name] = viewPort;
564 }
565 /**
566 * Returns a Promise that resolves when the router is configured.
567 */
568 ensureConfigured() {
569 return this._configuredPromise;
570 }
571 /**
572 * Configures the router.
573 *
574 * @param callbackOrConfig The [[RouterConfiguration]] or a callback that takes a [[RouterConfiguration]].
575 */
576 configure(callbackOrConfig) {
577 this.isConfigured = true;
578 let result = callbackOrConfig;
579 let config;
580 if (typeof callbackOrConfig === 'function') {
581 config = new RouterConfiguration();
582 result = callbackOrConfig(config);
583 }
584 return Promise
585 .resolve(result)
586 .then((c) => {
587 if (c && c.exportToRouter) {
588 config = c;
589 }
590 config.exportToRouter(this);
591 this.isConfigured = true;
592 this._resolveConfiguredPromise();
593 });
594 }
595 /**
596 * Navigates to a new location.
597 *
598 * @param fragment The URL fragment to use as the navigation destination.
599 * @param options The navigation options.
600 */
601 navigate(fragment, options) {
602 if (!this.isConfigured && this.parent) {
603 return this.parent.navigate(fragment, options);
604 }
605 this.isExplicitNavigation = true;
606 return this.history.navigate(_resolveUrl(fragment, this.baseUrl, this.history._hasPushState), options);
607 }
608 /**
609 * Navigates to a new location corresponding to the route and params specified. Equivallent to [[Router.generate]] followed
610 * by [[Router.navigate]].
611 *
612 * @param route The name of the route to use when generating the navigation location.
613 * @param params The route parameters to be used when populating the route pattern.
614 * @param options The navigation options.
615 */
616 navigateToRoute(route, params, options) {
617 let path = this.generate(route, params);
618 return this.navigate(path, options);
619 }
620 /**
621 * Navigates back to the most recent location in history.
622 */
623 navigateBack() {
624 this.isExplicitNavigationBack = true;
625 this.history.navigateBack();
626 }
627 /**
628 * Creates a child router of the current router.
629 *
630 * @param container The [[Container]] to provide to the child router. Uses the current [[Router]]'s [[Container]] if unspecified.
631 * @returns {Router} The new child Router.
632 */
633 createChild(container) {
634 let childRouter = new Router(container || this.container.createChild(), this.history);
635 childRouter.parent = this;
636 return childRouter;
637 }
638 /**
639 * Generates a URL fragment matching the specified route pattern.
640 *
641 * @param name The name of the route whose pattern should be used to generate the fragment.
642 * @param params The route params to be used to populate the route pattern.
643 * @param options If options.absolute = true, then absolute url will be generated; otherwise, it will be relative url.
644 * @returns {string} A string containing the generated URL fragment.
645 */
646 generate(nameOrRoute, params = {}, options = {}) {
647 // A child recognizer generates routes for potential child routes. Any potential child route is added
648 // to the childRoute property of params for the childRouter to recognize. When generating routes, we
649 // use the childRecognizer when childRoute params are available to generate a child router enabled route.
650 let recognizer = 'childRoute' in params ? this._childRecognizer : this._recognizer;
651 let hasRoute = recognizer.hasRoute(nameOrRoute);
652 if (!hasRoute) {
653 if (this.parent) {
654 return this.parent.generate(nameOrRoute, params, options);
655 }
656 throw new Error(`A route with name '${nameOrRoute}' could not be found. Check that \`name: '${nameOrRoute}'\` was specified in the route's config.`);
657 }
658 let path = recognizer.generate(nameOrRoute, params);
659 let rootedPath = _createRootedPath(path, this.baseUrl, this.history._hasPushState, options.absolute);
660 return options.absolute ? `${this.history.getAbsoluteRoot()}${rootedPath}` : rootedPath;
661 }
662 /**
663 * Creates a [[NavModel]] for the specified route config.
664 *
665 * @param config The route config.
666 */
667 createNavModel(config) {
668 let navModel = new NavModel(this, 'href' in config
669 ? config.href
670 // potential error when config.route is a string[] ?
671 : config.route);
672 navModel.title = config.title;
673 navModel.order = config.nav;
674 navModel.href = config.href;
675 navModel.settings = config.settings;
676 navModel.config = config;
677 return navModel;
678 }
679 /**
680 * Registers a new route with the router.
681 *
682 * @param config The [[RouteConfig]].
683 * @param navModel The [[NavModel]] to use for the route. May be omitted for single-pattern routes.
684 */
685 addRoute(config, navModel) {
686 if (Array.isArray(config.route)) {
687 let routeConfigs = _ensureArrayWithSingleRoutePerConfig(config);
688 // the following is wrong. todo: fix this after TS refactoring release
689 routeConfigs.forEach(this.addRoute.bind(this));
690 return;
691 }
692 validateRouteConfig(config);
693 if (!('viewPorts' in config) && !config.navigationStrategy) {
694 config.viewPorts = {
695 'default': {
696 moduleId: config.moduleId,
697 view: config.view
698 }
699 };
700 }
701 if (!navModel) {
702 navModel = this.createNavModel(config);
703 }
704 this.routes.push(config);
705 let path = config.route;
706 if (path.charAt(0) === '/') {
707 path = path.substr(1);
708 }
709 let caseSensitive = config.caseSensitive === true;
710 let state = this._recognizer.add({
711 path: path,
712 handler: config,
713 caseSensitive: caseSensitive
714 });
715 if (path) {
716 let settings = config.settings;
717 delete config.settings;
718 let withChild = JSON.parse(JSON.stringify(config));
719 config.settings = settings;
720 withChild.route = `${path}/*childRoute`;
721 withChild.hasChildRouter = true;
722 this._childRecognizer.add({
723 path: withChild.route,
724 handler: withChild,
725 caseSensitive: caseSensitive
726 });
727 withChild.navModel = navModel;
728 withChild.settings = config.settings;
729 withChild.navigationStrategy = config.navigationStrategy;
730 }
731 config.navModel = navModel;
732 let navigation = this.navigation;
733 if ((navModel.order || navModel.order === 0) && navigation.indexOf(navModel) === -1) {
734 if ((!navModel.href && navModel.href !== '') && (state.types.dynamics || state.types.stars)) {
735 throw new Error('Invalid route config for "' + config.route + '" : dynamic routes must specify an "href:" to be included in the navigation model.');
736 }
737 if (typeof navModel.order !== 'number') {
738 navModel.order = ++this._fallbackOrder;
739 }
740 navigation.push(navModel);
741 // this is a potential error / inconsistency between browsers
742 //
743 // MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
744 // If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other,
745 // but sorted with respect to all different elements.
746 // Note: the ECMAscript standard does not guarantee this behaviour,
747 // and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
748 navigation.sort((a, b) => a.order - b.order);
749 }
750 }
751 /**
752 * Gets a value indicating whether or not this [[Router]] or one of its ancestors has a route registered with the specified name.
753 *
754 * @param name The name of the route to check.
755 */
756 hasRoute(name) {
757 return !!(this._recognizer.hasRoute(name) || this.parent && this.parent.hasRoute(name));
758 }
759 /**
760 * Gets a value indicating whether or not this [[Router]] has a route registered with the specified name.
761 *
762 * @param name The name of the route to check.
763 */
764 hasOwnRoute(name) {
765 return this._recognizer.hasRoute(name);
766 }
767 /**
768 * Register a handler to use when the incoming URL fragment doesn't match any registered routes.
769 *
770 * @param config The moduleId, or a function that selects the moduleId, or a [[RouteConfig]].
771 */
772 handleUnknownRoutes(config) {
773 if (!config) {
774 throw new Error('Invalid unknown route handler');
775 }
776 this.catchAllHandler = instruction => {
777 return this
778 ._createRouteConfig(config, instruction)
779 .then(c => {
780 instruction.config = c;
781 return instruction;
782 });
783 };
784 }
785 /**
786 * Updates the document title using the current navigation instruction.
787 */
788 updateTitle() {
789 let parentRouter = this.parent;
790 if (parentRouter) {
791 return parentRouter.updateTitle();
792 }
793 let currentInstruction = this.currentInstruction;
794 if (currentInstruction) {
795 currentInstruction._updateTitle();
796 }
797 return undefined;
798 }
799 /**
800 * Updates the navigation routes with hrefs relative to the current location.
801 * Note: This method will likely move to a plugin in a future release.
802 */
803 refreshNavigation() {
804 let nav = this.navigation;
805 for (let i = 0, length = nav.length; i < length; i++) {
806 let current = nav[i];
807 if (!current.config.href) {
808 current.href = _createRootedPath(current.relativeHref, this.baseUrl, this.history._hasPushState);
809 }
810 else {
811 current.href = _normalizeAbsolutePath(current.config.href, this.history._hasPushState);
812 }
813 }
814 }
815 /**
816 * Sets the default configuration for the view ports. This specifies how to
817 * populate a view port for which no module is specified. The default is
818 * an empty view/view-model pair.
819 */
820 useViewPortDefaults($viewPortDefaults) {
821 // a workaround to have strong typings while not requiring to expose interface ViewPortInstruction
822 let viewPortDefaults = $viewPortDefaults;
823 for (let viewPortName in viewPortDefaults) {
824 let viewPortConfig = viewPortDefaults[viewPortName];
825 this.viewPortDefaults[viewPortName] = {
826 moduleId: viewPortConfig.moduleId
827 };
828 }
829 }
830 /**@internal */
831 _refreshBaseUrl() {
832 let parentRouter = this.parent;
833 if (parentRouter) {
834 this.baseUrl = generateBaseUrl(parentRouter, parentRouter.currentInstruction);
835 }
836 }
837 /**@internal */
838 _createNavigationInstruction(url = '', parentInstruction = null) {
839 let fragment = url;
840 let queryString = '';
841 let queryIndex = url.indexOf('?');
842 if (queryIndex !== -1) {
843 fragment = url.substr(0, queryIndex);
844 queryString = url.substr(queryIndex + 1);
845 }
846 let urlRecognizationResults = this._recognizer.recognize(url);
847 if (!urlRecognizationResults || !urlRecognizationResults.length) {
848 urlRecognizationResults = this._childRecognizer.recognize(url);
849 }
850 let instructionInit = {
851 fragment,
852 queryString,
853 config: null,
854 parentInstruction,
855 previousInstruction: this.currentInstruction,
856 router: this,
857 options: {
858 compareQueryParams: this.options.compareQueryParams
859 }
860 };
861 let result;
862 if (urlRecognizationResults && urlRecognizationResults.length) {
863 let first = urlRecognizationResults[0];
864 let instruction = new NavigationInstruction(Object.assign({}, instructionInit, {
865 params: first.params,
866 queryParams: first.queryParams || urlRecognizationResults.queryParams,
867 config: first.config || first.handler
868 }));
869 if (typeof first.handler === 'function') {
870 result = evaluateNavigationStrategy(instruction, first.handler, first);
871 }
872 else if (first.handler && typeof first.handler.navigationStrategy === 'function') {
873 result = evaluateNavigationStrategy(instruction, first.handler.navigationStrategy, first.handler);
874 }
875 else {
876 result = Promise.resolve(instruction);
877 }
878 }
879 else if (this.catchAllHandler) {
880 let instruction = new NavigationInstruction(Object.assign({}, instructionInit, {
881 params: { path: fragment },
882 queryParams: urlRecognizationResults ? urlRecognizationResults.queryParams : {},
883 config: null // config will be created by the catchAllHandler
884 }));
885 result = evaluateNavigationStrategy(instruction, this.catchAllHandler);
886 }
887 else if (this.parent) {
888 let router = this._parentCatchAllHandler(this.parent);
889 if (router) {
890 let newParentInstruction = this._findParentInstructionFromRouter(router, parentInstruction);
891 let instruction = new NavigationInstruction(Object.assign({}, instructionInit, {
892 params: { path: fragment },
893 queryParams: urlRecognizationResults ? urlRecognizationResults.queryParams : {},
894 router: router,
895 parentInstruction: newParentInstruction,
896 parentCatchHandler: true,
897 config: null // config will be created by the chained parent catchAllHandler
898 }));
899 result = evaluateNavigationStrategy(instruction, router.catchAllHandler);
900 }
901 }
902 if (result && parentInstruction) {
903 this.baseUrl = generateBaseUrl(this.parent, parentInstruction);
904 }
905 return result || Promise.reject(new Error(`Route not found: ${url}`));
906 }
907 /**@internal */
908 _findParentInstructionFromRouter(router, instruction) {
909 if (instruction.router === router) {
910 instruction.fragment = router.baseUrl; // need to change the fragment in case of a redirect instead of moduleId
911 return instruction;
912 }
913 else if (instruction.parentInstruction) {
914 return this._findParentInstructionFromRouter(router, instruction.parentInstruction);
915 }
916 return undefined;
917 }
918 /**@internal */
919 _parentCatchAllHandler(router) {
920 if (router.catchAllHandler) {
921 return router;
922 }
923 else if (router.parent) {
924 return this._parentCatchAllHandler(router.parent);
925 }
926 return false;
927 }
928 /**
929 * @internal
930 */
931 _createRouteConfig(config, instruction) {
932 return Promise
933 .resolve(config)
934 .then((c) => {
935 if (typeof c === 'string') {
936 return { moduleId: c };
937 }
938 else if (typeof c === 'function') {
939 return c(instruction);
940 }
941 return c;
942 })
943 // typing here could be either RouteConfig or RedirectConfig
944 // but temporarily treat both as RouteConfig
945 // todo: improve typings precision
946 .then((c) => typeof c === 'string' ? { moduleId: c } : c)
947 .then((c) => {
948 c.route = instruction.params.path;
949 validateRouteConfig(c);
950 if (!c.navModel) {
951 c.navModel = this.createNavModel(c);
952 }
953 return c;
954 });
955 }
956}
957/* @internal exported for unit testing */
958const generateBaseUrl = (router, instruction) => {
959 return `${router.baseUrl || ''}${instruction.getBaseUrl() || ''}`;
960};
961/* @internal exported for unit testing */
962const validateRouteConfig = (config) => {
963 if (typeof config !== 'object') {
964 throw new Error('Invalid Route Config');
965 }
966 if (typeof config.route !== 'string') {
967 let name = config.name || '(no name)';
968 throw new Error('Invalid Route Config for "' + name + '": You must specify a "route:" pattern.');
969 }
970 if (!('redirect' in config || config.moduleId || config.navigationStrategy || config.viewPorts)) {
971 throw new Error('Invalid Route Config for "' + config.route + '": You must specify a "moduleId:", "redirect:", "navigationStrategy:", or "viewPorts:".');
972 }
973};
974/* @internal exported for unit testing */
975const evaluateNavigationStrategy = (instruction, evaluator, context) => {
976 return Promise
977 .resolve(evaluator.call(context, instruction))
978 .then(() => {
979 if (!('viewPorts' in instruction.config)) {
980 instruction.config.viewPorts = {
981 'default': {
982 moduleId: instruction.config.moduleId
983 }
984 };
985 }
986 return instruction;
987 });
988};
989
990/**@internal exported for unit testing */
991const createNextFn = (instruction, steps) => {
992 let index = -1;
993 const next = function () {
994 index++;
995 if (index < steps.length) {
996 let currentStep = steps[index];
997 try {
998 return currentStep(instruction, next);
999 }
1000 catch (e) {
1001 return next.reject(e);
1002 }
1003 }
1004 else {
1005 return next.complete();
1006 }
1007 };
1008 next.complete = createCompletionHandler(next, "completed" /* Completed */);
1009 next.cancel = createCompletionHandler(next, "canceled" /* Canceled */);
1010 next.reject = createCompletionHandler(next, "rejected" /* Rejected */);
1011 return next;
1012};
1013/**@internal exported for unit testing */
1014const createCompletionHandler = (next, status) => {
1015 return (output) => Promise
1016 .resolve({
1017 status,
1018 output,
1019 completed: status === "completed" /* Completed */
1020 });
1021};
1022
1023/**
1024 * The class responsible for managing and processing the navigation pipeline.
1025 */
1026class Pipeline {
1027 constructor() {
1028 /**
1029 * The pipeline steps. And steps added via addStep will be converted to a function
1030 * The actualy running functions with correct step contexts of this pipeline
1031 */
1032 this.steps = [];
1033 }
1034 /**
1035 * Adds a step to the pipeline.
1036 *
1037 * @param step The pipeline step.
1038 */
1039 addStep(step) {
1040 let run;
1041 if (typeof step === 'function') {
1042 run = step;
1043 }
1044 else if (typeof step.getSteps === 'function') {
1045 // getSteps is to enable support open slots
1046 // where devs can add multiple steps into the same slot name
1047 let steps = step.getSteps();
1048 for (let i = 0, l = steps.length; i < l; i++) {
1049 this.addStep(steps[i]);
1050 }
1051 return this;
1052 }
1053 else {
1054 run = step.run.bind(step);
1055 }
1056 this.steps.push(run);
1057 return this;
1058 }
1059 /**
1060 * Runs the pipeline.
1061 *
1062 * @param instruction The navigation instruction to process.
1063 */
1064 run(instruction) {
1065 const nextFn = createNextFn(instruction, this.steps);
1066 return nextFn();
1067 }
1068}
1069
1070/**
1071* Determines if the provided object is a navigation command.
1072* A navigation command is anything with a navigate method.
1073*
1074* @param obj The object to check.
1075*/
1076function isNavigationCommand(obj) {
1077 return obj && typeof obj.navigate === 'function';
1078}
1079/**
1080* Used during the activation lifecycle to cause a redirect.
1081*/
1082class Redirect {
1083 /**
1084 * @param url The URL fragment to use as the navigation destination.
1085 * @param options The navigation options.
1086 */
1087 constructor(url, options = {}) {
1088 this.url = url;
1089 this.options = Object.assign({ trigger: true, replace: true }, options);
1090 this.shouldContinueProcessing = false;
1091 }
1092 /**
1093 * Called by the activation system to set the child router.
1094 *
1095 * @param router The router.
1096 */
1097 setRouter(router) {
1098 this.router = router;
1099 }
1100 /**
1101 * Called by the navigation pipeline to navigate.
1102 *
1103 * @param appRouter The router to be redirected.
1104 */
1105 navigate(appRouter) {
1106 let navigatingRouter = this.options.useAppRouter ? appRouter : (this.router || appRouter);
1107 navigatingRouter.navigate(this.url, this.options);
1108 }
1109}
1110/**
1111 * Used during the activation lifecycle to cause a redirect to a named route.
1112 */
1113class RedirectToRoute {
1114 /**
1115 * @param route The name of the route.
1116 * @param params The parameters to be sent to the activation method.
1117 * @param options The options to use for navigation.
1118 */
1119 constructor(route, params = {}, options = {}) {
1120 this.route = route;
1121 this.params = params;
1122 this.options = Object.assign({ trigger: true, replace: true }, options);
1123 this.shouldContinueProcessing = false;
1124 }
1125 /**
1126 * Called by the activation system to set the child router.
1127 *
1128 * @param router The router.
1129 */
1130 setRouter(router) {
1131 this.router = router;
1132 }
1133 /**
1134 * Called by the navigation pipeline to navigate.
1135 *
1136 * @param appRouter The router to be redirected.
1137 */
1138 navigate(appRouter) {
1139 let navigatingRouter = this.options.useAppRouter ? appRouter : (this.router || appRouter);
1140 navigatingRouter.navigateToRoute(this.route, this.params, this.options);
1141 }
1142}
1143
1144/**
1145 * @internal exported for unit testing
1146 */
1147function _buildNavigationPlan(instruction, forceLifecycleMinimum) {
1148 let config = instruction.config;
1149 if ('redirect' in config) {
1150 return buildRedirectPlan(instruction);
1151 }
1152 const prevInstruction = instruction.previousInstruction;
1153 const defaultViewPortConfigs = instruction.router.viewPortDefaults;
1154 if (prevInstruction) {
1155 return buildTransitionPlans(instruction, prevInstruction, defaultViewPortConfigs, forceLifecycleMinimum);
1156 }
1157 // first navigation, only need to prepare a few information for each viewport plan
1158 const viewPortPlans = {};
1159 let viewPortConfigs = config.viewPorts;
1160 for (let viewPortName in viewPortConfigs) {
1161 let viewPortConfig = viewPortConfigs[viewPortName];
1162 if (viewPortConfig.moduleId === null && viewPortName in defaultViewPortConfigs) {
1163 viewPortConfig = defaultViewPortConfigs[viewPortName];
1164 }
1165 viewPortPlans[viewPortName] = {
1166 name: viewPortName,
1167 strategy: "replace" /* Replace */,
1168 config: viewPortConfig
1169 };
1170 }
1171 return Promise.resolve(viewPortPlans);
1172}
1173/**
1174 * Build redirect plan based on config of a navigation instruction
1175 * @internal exported for unit testing
1176 */
1177const buildRedirectPlan = (instruction) => {
1178 const config = instruction.config;
1179 const router = instruction.router;
1180 return router
1181 ._createNavigationInstruction(config.redirect)
1182 .then(redirectInstruction => {
1183 const params = {};
1184 const originalInstructionParams = instruction.params;
1185 const redirectInstructionParams = redirectInstruction.params;
1186 for (let key in redirectInstructionParams) {
1187 // If the param on the redirect points to another param, e.g. { route: first/:this, redirect: second/:this }
1188 let val = redirectInstructionParams[key];
1189 if (typeof val === 'string' && val[0] === ':') {
1190 val = val.slice(1);
1191 // And if that param is found on the original instruction then use it
1192 if (val in originalInstructionParams) {
1193 params[key] = originalInstructionParams[val];
1194 }
1195 }
1196 else {
1197 params[key] = redirectInstructionParams[key];
1198 }
1199 }
1200 let redirectLocation = router.generate(redirectInstruction.config, params, instruction.options);
1201 // Special handling for child routes
1202 for (let key in originalInstructionParams) {
1203 redirectLocation = redirectLocation.replace(`:${key}`, originalInstructionParams[key]);
1204 }
1205 let queryString = instruction.queryString;
1206 if (queryString) {
1207 redirectLocation += '?' + queryString;
1208 }
1209 return Promise.resolve(new Redirect(redirectLocation));
1210 });
1211};
1212/**
1213 * @param viewPortPlans the Plan record that holds information about built plans
1214 * @internal exported for unit testing
1215 */
1216const buildTransitionPlans = (currentInstruction, previousInstruction, defaultViewPortConfigs, forceLifecycleMinimum) => {
1217 let viewPortPlans = {};
1218 let newInstructionConfig = currentInstruction.config;
1219 let hasNewParams = hasDifferentParameterValues(previousInstruction, currentInstruction);
1220 let pending = [];
1221 let previousViewPortInstructions = previousInstruction.viewPortInstructions;
1222 for (let viewPortName in previousViewPortInstructions) {
1223 const prevViewPortInstruction = previousViewPortInstructions[viewPortName];
1224 const prevViewPortComponent = prevViewPortInstruction.component;
1225 const newInstructionViewPortConfigs = newInstructionConfig.viewPorts;
1226 // if this is invoked on a viewport without any changes, based on new url,
1227 // newViewPortConfig will be the existing viewport instruction
1228 let nextViewPortConfig = viewPortName in newInstructionViewPortConfigs
1229 ? newInstructionViewPortConfigs[viewPortName]
1230 : prevViewPortInstruction;
1231 if (nextViewPortConfig.moduleId === null && viewPortName in defaultViewPortConfigs) {
1232 nextViewPortConfig = defaultViewPortConfigs[viewPortName];
1233 }
1234 const viewPortActivationStrategy = determineActivationStrategy(currentInstruction, prevViewPortInstruction, nextViewPortConfig, hasNewParams, forceLifecycleMinimum);
1235 const viewPortPlan = viewPortPlans[viewPortName] = {
1236 name: viewPortName,
1237 // ViewPortInstruction can quack like a RouteConfig
1238 config: nextViewPortConfig,
1239 prevComponent: prevViewPortComponent,
1240 prevModuleId: prevViewPortInstruction.moduleId,
1241 strategy: viewPortActivationStrategy
1242 };
1243 // recursively build nav plans for all existing child routers/viewports of this viewport
1244 // this is possible because existing child viewports and routers already have necessary information
1245 // to process the wildcard path from parent instruction
1246 if (viewPortActivationStrategy !== "replace" /* Replace */ && prevViewPortInstruction.childRouter) {
1247 const path = currentInstruction.getWildcardPath();
1248 const task = prevViewPortInstruction
1249 .childRouter
1250 ._createNavigationInstruction(path, currentInstruction)
1251 .then((childInstruction) => {
1252 viewPortPlan.childNavigationInstruction = childInstruction;
1253 return _buildNavigationPlan(childInstruction,
1254 // is it safe to assume viewPortPlan has not been changed from previous assignment?
1255 // if so, can just use local variable viewPortPlanStrategy
1256 // there could be user code modifying viewport plan during _createNavigationInstruction?
1257 viewPortPlan.strategy === "invoke-lifecycle" /* InvokeLifecycle */)
1258 .then(childPlan => {
1259 if (childPlan instanceof Redirect) {
1260 return Promise.reject(childPlan);
1261 }
1262 childInstruction.plan = childPlan;
1263 // for bluebird ?
1264 return null;
1265 });
1266 });
1267 pending.push(task);
1268 }
1269 }
1270 return Promise.all(pending).then(() => viewPortPlans);
1271};
1272/**
1273 * @param newViewPortConfig if this is invoked on a viewport without any changes, based on new url, newViewPortConfig will be the existing viewport instruction
1274 * @internal exported for unit testing
1275 */
1276const determineActivationStrategy = (currentNavInstruction, prevViewPortInstruction, newViewPortConfig,
1277// indicates whether there is difference between old and new url params
1278hasNewParams, forceLifecycleMinimum) => {
1279 let newInstructionConfig = currentNavInstruction.config;
1280 let prevViewPortViewModel = prevViewPortInstruction.component.viewModel;
1281 let viewPortPlanStrategy;
1282 if (prevViewPortInstruction.moduleId !== newViewPortConfig.moduleId) {
1283 viewPortPlanStrategy = "replace" /* Replace */;
1284 }
1285 else if ('determineActivationStrategy' in prevViewPortViewModel) {
1286 viewPortPlanStrategy = prevViewPortViewModel.determineActivationStrategy(...currentNavInstruction.lifecycleArgs);
1287 }
1288 else if (newInstructionConfig.activationStrategy) {
1289 viewPortPlanStrategy = newInstructionConfig.activationStrategy;
1290 }
1291 else if (hasNewParams || forceLifecycleMinimum) {
1292 viewPortPlanStrategy = "invoke-lifecycle" /* InvokeLifecycle */;
1293 }
1294 else {
1295 viewPortPlanStrategy = "no-change" /* NoChange */;
1296 }
1297 return viewPortPlanStrategy;
1298};
1299/**@internal exported for unit testing */
1300const hasDifferentParameterValues = (prev, next) => {
1301 let prevParams = prev.params;
1302 let nextParams = next.params;
1303 let nextWildCardName = next.config.hasChildRouter ? next.getWildCardName() : null;
1304 for (let key in nextParams) {
1305 if (key === nextWildCardName) {
1306 continue;
1307 }
1308 if (prevParams[key] !== nextParams[key]) {
1309 return true;
1310 }
1311 }
1312 for (let key in prevParams) {
1313 if (key === nextWildCardName) {
1314 continue;
1315 }
1316 if (prevParams[key] !== nextParams[key]) {
1317 return true;
1318 }
1319 }
1320 if (!next.options.compareQueryParams) {
1321 return false;
1322 }
1323 let prevQueryParams = prev.queryParams;
1324 let nextQueryParams = next.queryParams;
1325 for (let key in nextQueryParams) {
1326 if (prevQueryParams[key] !== nextQueryParams[key]) {
1327 return true;
1328 }
1329 }
1330 for (let key in prevQueryParams) {
1331 if (prevQueryParams[key] !== nextQueryParams[key]) {
1332 return true;
1333 }
1334 }
1335 return false;
1336};
1337
1338/**
1339 * Transform a navigation instruction into viewport plan record object,
1340 * or a redirect request if user viewmodel demands
1341 */
1342class BuildNavigationPlanStep {
1343 run(navigationInstruction, next) {
1344 return _buildNavigationPlan(navigationInstruction)
1345 .then(plan => {
1346 if (plan instanceof Redirect) {
1347 return next.cancel(plan);
1348 }
1349 navigationInstruction.plan = plan;
1350 return next();
1351 })
1352 .catch(next.cancel);
1353 }
1354}
1355
1356/**
1357 * @internal Exported for unit testing
1358 */
1359const loadNewRoute = (routeLoader, navigationInstruction) => {
1360 let loadingPlans = determineLoadingPlans(navigationInstruction);
1361 let loadPromises = loadingPlans.map((loadingPlan) => loadRoute(routeLoader, loadingPlan.navigationInstruction, loadingPlan.viewPortPlan));
1362 return Promise.all(loadPromises);
1363};
1364/**
1365 * @internal Exported for unit testing
1366 */
1367const determineLoadingPlans = (navigationInstruction, loadingPlans = []) => {
1368 let viewPortPlans = navigationInstruction.plan;
1369 for (let viewPortName in viewPortPlans) {
1370 let viewPortPlan = viewPortPlans[viewPortName];
1371 let childNavInstruction = viewPortPlan.childNavigationInstruction;
1372 if (viewPortPlan.strategy === "replace" /* Replace */) {
1373 loadingPlans.push({ viewPortPlan, navigationInstruction });
1374 if (childNavInstruction) {
1375 determineLoadingPlans(childNavInstruction, loadingPlans);
1376 }
1377 }
1378 else {
1379 let viewPortInstruction = navigationInstruction.addViewPortInstruction({
1380 name: viewPortName,
1381 strategy: viewPortPlan.strategy,
1382 moduleId: viewPortPlan.prevModuleId,
1383 component: viewPortPlan.prevComponent
1384 });
1385 if (childNavInstruction) {
1386 viewPortInstruction.childNavigationInstruction = childNavInstruction;
1387 determineLoadingPlans(childNavInstruction, loadingPlans);
1388 }
1389 }
1390 }
1391 return loadingPlans;
1392};
1393/**
1394 * @internal Exported for unit testing
1395 */
1396const loadRoute = (routeLoader, navigationInstruction, viewPortPlan) => {
1397 let planConfig = viewPortPlan.config;
1398 let moduleId = planConfig ? planConfig.moduleId : null;
1399 return loadComponent(routeLoader, navigationInstruction, planConfig)
1400 .then((component) => {
1401 let viewPortInstruction = navigationInstruction.addViewPortInstruction({
1402 name: viewPortPlan.name,
1403 strategy: viewPortPlan.strategy,
1404 moduleId: moduleId,
1405 component: component
1406 });
1407 let childRouter = component.childRouter;
1408 if (childRouter) {
1409 let path = navigationInstruction.getWildcardPath();
1410 return childRouter
1411 ._createNavigationInstruction(path, navigationInstruction)
1412 .then((childInstruction) => {
1413 viewPortPlan.childNavigationInstruction = childInstruction;
1414 return _buildNavigationPlan(childInstruction)
1415 .then((childPlan) => {
1416 if (childPlan instanceof Redirect) {
1417 return Promise.reject(childPlan);
1418 }
1419 childInstruction.plan = childPlan;
1420 viewPortInstruction.childNavigationInstruction = childInstruction;
1421 return loadNewRoute(routeLoader, childInstruction);
1422 });
1423 });
1424 }
1425 // ts complains without this, though they are same
1426 return void 0;
1427 });
1428};
1429/**
1430 * Load a routed-component based on navigation instruction and route config
1431 * @internal exported for unit testing only
1432 */
1433const loadComponent = (routeLoader, navigationInstruction, config) => {
1434 let router = navigationInstruction.router;
1435 let lifecycleArgs = navigationInstruction.lifecycleArgs;
1436 return Promise.resolve()
1437 .then(() => routeLoader.loadRoute(router, config, navigationInstruction))
1438 .then(
1439 /**
1440 * @param component an object carrying information about loaded route
1441 * typically contains information about view model, childContainer, view and router
1442 */
1443 (component) => {
1444 let { viewModel, childContainer } = component;
1445 component.router = router;
1446 component.config = config;
1447 if ('configureRouter' in viewModel) {
1448 let childRouter = childContainer.getChildRouter();
1449 component.childRouter = childRouter;
1450 return childRouter
1451 .configure(c => viewModel.configureRouter(c, childRouter, lifecycleArgs[0], lifecycleArgs[1], lifecycleArgs[2]))
1452 .then(() => component);
1453 }
1454 return component;
1455 });
1456};
1457
1458/**
1459 * Abstract class that is responsible for loading view / view model from a route config
1460 * The default implementation can be found in `aurelia-templating-router`
1461 */
1462class RouteLoader {
1463 /**
1464 * Load a route config based on its viewmodel / view configuration
1465 */
1466 // return typing: return typings used to be never
1467 // as it was a throw. Changing it to Promise<any> should not cause any issues
1468 loadRoute(router, config, navigationInstruction) {
1469 throw new Error('Route loaders must implement "loadRoute(router, config, navigationInstruction)".');
1470 }
1471}
1472
1473/**
1474 * A pipeline step responsible for loading a route config of a navigation instruction
1475 */
1476class LoadRouteStep {
1477 /**@internal */
1478 static inject() { return [RouteLoader]; }
1479 constructor(routeLoader) {
1480 this.routeLoader = routeLoader;
1481 }
1482 /**
1483 * Run the internal to load route config of a navigation instruction to prepare for next steps in the pipeline
1484 */
1485 run(navigationInstruction, next) {
1486 return loadNewRoute(this.routeLoader, navigationInstruction)
1487 .then(next, next.cancel);
1488 }
1489}
1490
1491/**
1492 * A pipeline step for instructing a piepline to commit changes on a navigation instruction
1493 */
1494class CommitChangesStep {
1495 run(navigationInstruction, next) {
1496 return navigationInstruction
1497 ._commitChanges(/*wait to swap?*/ true)
1498 .then(() => {
1499 navigationInstruction._updateTitle();
1500 return next();
1501 });
1502 }
1503}
1504
1505/**
1506 * An optional interface describing the available activation strategies.
1507 * @internal Used internally.
1508 */
1509var InternalActivationStrategy;
1510(function (InternalActivationStrategy) {
1511 /**
1512 * Reuse the existing view model, without invoking Router lifecycle hooks.
1513 */
1514 InternalActivationStrategy["NoChange"] = "no-change";
1515 /**
1516 * Reuse the existing view model, invoking Router lifecycle hooks.
1517 */
1518 InternalActivationStrategy["InvokeLifecycle"] = "invoke-lifecycle";
1519 /**
1520 * Replace the existing view model, invoking Router lifecycle hooks.
1521 */
1522 InternalActivationStrategy["Replace"] = "replace";
1523})(InternalActivationStrategy || (InternalActivationStrategy = {}));
1524/**
1525 * The strategy to use when activating modules during navigation.
1526 */
1527// kept for compat reason
1528const activationStrategy = {
1529 noChange: "no-change" /* NoChange */,
1530 invokeLifecycle: "invoke-lifecycle" /* InvokeLifecycle */,
1531 replace: "replace" /* Replace */
1532};
1533
1534/**
1535 * Recursively find list of deactivate-able view models
1536 * and invoke the either 'canDeactivate' or 'deactivate' on each
1537 * @internal exported for unit testing
1538 */
1539const processDeactivatable = (navigationInstruction, callbackName, next, ignoreResult) => {
1540 let plan = navigationInstruction.plan;
1541 let infos = findDeactivatable(plan, callbackName);
1542 let i = infos.length; // query from inside out
1543 function inspect(val) {
1544 if (ignoreResult || shouldContinue(val)) {
1545 return iterate();
1546 }
1547 return next.cancel(val);
1548 }
1549 function iterate() {
1550 if (i--) {
1551 try {
1552 let viewModel = infos[i];
1553 let result = viewModel[callbackName](navigationInstruction);
1554 return processPotential(result, inspect, next.cancel);
1555 }
1556 catch (error) {
1557 return next.cancel(error);
1558 }
1559 }
1560 navigationInstruction.router.couldDeactivate = true;
1561 return next();
1562 }
1563 return iterate();
1564};
1565/**
1566 * Recursively find and returns a list of deactivate-able view models
1567 * @internal exported for unit testing
1568 */
1569const findDeactivatable = (plan, callbackName, list = []) => {
1570 for (let viewPortName in plan) {
1571 let viewPortPlan = plan[viewPortName];
1572 let prevComponent = viewPortPlan.prevComponent;
1573 if ((viewPortPlan.strategy === activationStrategy.invokeLifecycle || viewPortPlan.strategy === activationStrategy.replace)
1574 && prevComponent) {
1575 let viewModel = prevComponent.viewModel;
1576 if (callbackName in viewModel) {
1577 list.push(viewModel);
1578 }
1579 }
1580 if (viewPortPlan.strategy === activationStrategy.replace && prevComponent) {
1581 addPreviousDeactivatable(prevComponent, callbackName, list);
1582 }
1583 else if (viewPortPlan.childNavigationInstruction) {
1584 findDeactivatable(viewPortPlan.childNavigationInstruction.plan, callbackName, list);
1585 }
1586 }
1587 return list;
1588};
1589/**
1590 * @internal exported for unit testing
1591 */
1592const addPreviousDeactivatable = (component, callbackName, list) => {
1593 let childRouter = component.childRouter;
1594 if (childRouter && childRouter.currentInstruction) {
1595 let viewPortInstructions = childRouter.currentInstruction.viewPortInstructions;
1596 for (let viewPortName in viewPortInstructions) {
1597 let viewPortInstruction = viewPortInstructions[viewPortName];
1598 let prevComponent = viewPortInstruction.component;
1599 let prevViewModel = prevComponent.viewModel;
1600 if (callbackName in prevViewModel) {
1601 list.push(prevViewModel);
1602 }
1603 addPreviousDeactivatable(prevComponent, callbackName, list);
1604 }
1605 }
1606};
1607/**
1608 * @internal exported for unit testing
1609 */
1610const processActivatable = (navigationInstruction, callbackName, next, ignoreResult) => {
1611 let infos = findActivatable(navigationInstruction, callbackName);
1612 let length = infos.length;
1613 let i = -1; // query from top down
1614 function inspect(val, router) {
1615 if (ignoreResult || shouldContinue(val, router)) {
1616 return iterate();
1617 }
1618 return next.cancel(val);
1619 }
1620 function iterate() {
1621 i++;
1622 if (i < length) {
1623 try {
1624 let current = infos[i];
1625 let result = current.viewModel[callbackName](...current.lifecycleArgs);
1626 return processPotential(result, (val) => inspect(val, current.router), next.cancel);
1627 }
1628 catch (error) {
1629 return next.cancel(error);
1630 }
1631 }
1632 return next();
1633 }
1634 return iterate();
1635};
1636/**
1637 * Find list of activatable view model and add to list (3rd parameter)
1638 * @internal exported for unit testing
1639 */
1640const findActivatable = (navigationInstruction, callbackName, list = [], router) => {
1641 let plan = navigationInstruction.plan;
1642 Object
1643 .keys(plan)
1644 .forEach((viewPortName) => {
1645 let viewPortPlan = plan[viewPortName];
1646 let viewPortInstruction = navigationInstruction.viewPortInstructions[viewPortName];
1647 let viewPortComponent = viewPortInstruction.component;
1648 let viewModel = viewPortComponent.viewModel;
1649 if ((viewPortPlan.strategy === activationStrategy.invokeLifecycle
1650 || viewPortPlan.strategy === activationStrategy.replace)
1651 && callbackName in viewModel) {
1652 list.push({
1653 viewModel,
1654 lifecycleArgs: viewPortInstruction.lifecycleArgs,
1655 router
1656 });
1657 }
1658 let childNavInstruction = viewPortPlan.childNavigationInstruction;
1659 if (childNavInstruction) {
1660 findActivatable(childNavInstruction, callbackName, list, viewPortComponent.childRouter || router);
1661 }
1662 });
1663 return list;
1664};
1665const shouldContinue = (output, router) => {
1666 if (output instanceof Error) {
1667 return false;
1668 }
1669 if (isNavigationCommand(output)) {
1670 if (typeof output.setRouter === 'function') {
1671 output.setRouter(router);
1672 }
1673 return !!output.shouldContinueProcessing;
1674 }
1675 if (output === undefined) {
1676 return true;
1677 }
1678 return output;
1679};
1680/**
1681 * wraps a subscription, allowing unsubscribe calls even if
1682 * the first value comes synchronously
1683 */
1684class SafeSubscription {
1685 constructor(subscriptionFunc) {
1686 this._subscribed = true;
1687 this._subscription = subscriptionFunc(this);
1688 if (!this._subscribed) {
1689 this.unsubscribe();
1690 }
1691 }
1692 get subscribed() {
1693 return this._subscribed;
1694 }
1695 unsubscribe() {
1696 if (this._subscribed && this._subscription) {
1697 this._subscription.unsubscribe();
1698 }
1699 this._subscribed = false;
1700 }
1701}
1702/**
1703 * A function to process return value from `activate`/`canActivate` steps
1704 * Supports observable/promise
1705 *
1706 * For observable, resolve at first next() or on complete()
1707 */
1708const processPotential = (obj, resolve, reject) => {
1709 // if promise like
1710 if (obj && typeof obj.then === 'function') {
1711 return Promise.resolve(obj).then(resolve).catch(reject);
1712 }
1713 // if observable
1714 if (obj && typeof obj.subscribe === 'function') {
1715 let obs = obj;
1716 return new SafeSubscription(sub => obs.subscribe({
1717 next() {
1718 if (sub.subscribed) {
1719 sub.unsubscribe();
1720 resolve(obj);
1721 }
1722 },
1723 error(error) {
1724 if (sub.subscribed) {
1725 sub.unsubscribe();
1726 reject(error);
1727 }
1728 },
1729 complete() {
1730 if (sub.subscribed) {
1731 sub.unsubscribe();
1732 resolve(obj);
1733 }
1734 }
1735 }));
1736 }
1737 // else just resolve
1738 try {
1739 return resolve(obj);
1740 }
1741 catch (error) {
1742 return reject(error);
1743 }
1744};
1745
1746/**
1747 * A pipeline step responsible for finding and activating method `canDeactivate` on a view model of a route
1748 */
1749class CanDeactivatePreviousStep {
1750 run(navigationInstruction, next) {
1751 return processDeactivatable(navigationInstruction, 'canDeactivate', next);
1752 }
1753}
1754/**
1755 * A pipeline step responsible for finding and activating method `canActivate` on a view model of a route
1756 */
1757class CanActivateNextStep {
1758 run(navigationInstruction, next) {
1759 return processActivatable(navigationInstruction, 'canActivate', next);
1760 }
1761}
1762/**
1763 * A pipeline step responsible for finding and activating method `deactivate` on a view model of a route
1764 */
1765class DeactivatePreviousStep {
1766 run(navigationInstruction, next) {
1767 return processDeactivatable(navigationInstruction, 'deactivate', next, true);
1768 }
1769}
1770/**
1771 * A pipeline step responsible for finding and activating method `activate` on a view model of a route
1772 */
1773class ActivateNextStep {
1774 run(navigationInstruction, next) {
1775 return processActivatable(navigationInstruction, 'activate', next, true);
1776 }
1777}
1778
1779/**
1780 * A multi-slots Pipeline Placeholder Step for hooking into a pipeline execution
1781 */
1782class PipelineSlot {
1783 constructor(container, name, alias) {
1784 this.steps = [];
1785 this.container = container;
1786 this.slotName = name;
1787 this.slotAlias = alias;
1788 }
1789 getSteps() {
1790 return this.steps.map(x => this.container.get(x));
1791 }
1792}
1793/**
1794 * Class responsible for creating the navigation pipeline.
1795 */
1796class PipelineProvider {
1797 /**@internal */
1798 static inject() { return [Container]; }
1799 constructor(container) {
1800 this.container = container;
1801 this.steps = [
1802 BuildNavigationPlanStep,
1803 CanDeactivatePreviousStep,
1804 LoadRouteStep,
1805 createPipelineSlot(container, "authorize" /* Authorize */),
1806 CanActivateNextStep,
1807 createPipelineSlot(container, "preActivate" /* PreActivate */, 'modelbind'),
1808 // NOTE: app state changes start below - point of no return
1809 DeactivatePreviousStep,
1810 ActivateNextStep,
1811 createPipelineSlot(container, "preRender" /* PreRender */, 'precommit'),
1812 CommitChangesStep,
1813 createPipelineSlot(container, "postRender" /* PostRender */, 'postcomplete')
1814 ];
1815 }
1816 /**
1817 * Create the navigation pipeline.
1818 */
1819 createPipeline(useCanDeactivateStep = true) {
1820 let pipeline = new Pipeline();
1821 this.steps.forEach(step => {
1822 if (useCanDeactivateStep || step !== CanDeactivatePreviousStep) {
1823 pipeline.addStep(this.container.get(step));
1824 }
1825 });
1826 return pipeline;
1827 }
1828 /**@internal */
1829 _findStep(name) {
1830 // Steps that are not PipelineSlots are constructor functions, and they will automatically fail. Probably.
1831 return this.steps.find(x => x.slotName === name || x.slotAlias === name);
1832 }
1833 /**
1834 * Adds a step into the pipeline at a known slot location.
1835 */
1836 addStep(name, step) {
1837 let found = this._findStep(name);
1838 if (found) {
1839 let slotSteps = found.steps;
1840 // prevent duplicates
1841 if (!slotSteps.includes(step)) {
1842 slotSteps.push(step);
1843 }
1844 }
1845 else {
1846 throw new Error(`Invalid pipeline slot name: ${name}.`);
1847 }
1848 }
1849 /**
1850 * Removes a step from a slot in the pipeline
1851 */
1852 removeStep(name, step) {
1853 let slot = this._findStep(name);
1854 if (slot) {
1855 let slotSteps = slot.steps;
1856 slotSteps.splice(slotSteps.indexOf(step), 1);
1857 }
1858 }
1859 /**
1860 * Clears all steps from a slot in the pipeline
1861 * @internal
1862 */
1863 _clearSteps(name = '') {
1864 let slot = this._findStep(name);
1865 if (slot) {
1866 slot.steps = [];
1867 }
1868 }
1869 /**
1870 * Resets all pipeline slots
1871 */
1872 reset() {
1873 this._clearSteps("authorize" /* Authorize */);
1874 this._clearSteps("preActivate" /* PreActivate */);
1875 this._clearSteps("preRender" /* PreRender */);
1876 this._clearSteps("postRender" /* PostRender */);
1877 }
1878}
1879/**@internal */
1880const createPipelineSlot = (container, name, alias) => {
1881 return new PipelineSlot(container, name, alias);
1882};
1883
1884const logger = getLogger('app-router');
1885/**
1886 * The main application router.
1887 */
1888class AppRouter extends Router {
1889 /**@internal */
1890 static inject() { return [Container, History, PipelineProvider, EventAggregator]; }
1891 constructor(container, history, pipelineProvider, events) {
1892 super(container, history); // Note the super will call reset internally.
1893 this.pipelineProvider = pipelineProvider;
1894 this.events = events;
1895 }
1896 /**
1897 * Fully resets the router's internal state. Primarily used internally by the framework when multiple calls to setRoot are made.
1898 * Use with caution (actually, avoid using this). Do not use this to simply change your navigation model.
1899 */
1900 reset() {
1901 super.reset();
1902 this.maxInstructionCount = 10;
1903 if (!this._queue) {
1904 this._queue = [];
1905 }
1906 else {
1907 this._queue.length = 0;
1908 }
1909 }
1910 /**
1911 * Loads the specified URL.
1912 *
1913 * @param url The URL fragment to load.
1914 */
1915 loadUrl(url) {
1916 return this
1917 ._createNavigationInstruction(url)
1918 .then(instruction => this._queueInstruction(instruction))
1919 .catch(error => {
1920 logger.error(error);
1921 restorePreviousLocation(this);
1922 });
1923 }
1924 /**
1925 * Registers a viewPort to be used as a rendering target for activated routes.
1926 *
1927 * @param viewPort The viewPort. This is typically a <router-view/> element in Aurelia default impl
1928 * @param name The name of the viewPort. 'default' if unspecified.
1929 */
1930 registerViewPort(viewPort, name) {
1931 // having strong typing without changing public API
1932 const $viewPort = viewPort;
1933 super.registerViewPort($viewPort, name);
1934 // beside adding viewport to the registry of this instance
1935 // AppRouter also configure routing/history to start routing functionality
1936 // There are situation where there are more than 1 <router-view/> element at root view
1937 // in that case, still only activate once via the following guard
1938 if (!this.isActive) {
1939 const viewModel = this._findViewModel($viewPort);
1940 if ('configureRouter' in viewModel) {
1941 // If there are more than one <router-view/> element at root view
1942 // use this flag to guard against configure method being invoked multiple times
1943 // this flag is set inside method configure
1944 if (!this.isConfigured) {
1945 // replace the real resolve with a noop to guarantee that any action in base class Router
1946 // won't resolve the configurePromise prematurely
1947 const resolveConfiguredPromise = this._resolveConfiguredPromise;
1948 this._resolveConfiguredPromise = () => { };
1949 return this
1950 .configure(config => Promise
1951 .resolve(viewModel.configureRouter(config, this))
1952 // an issue with configure interface. Should be fixed there
1953 // todo: fix this via configure interface in router
1954 .then(() => config))
1955 .then(() => {
1956 this.activate();
1957 resolveConfiguredPromise();
1958 });
1959 }
1960 }
1961 else {
1962 this.activate();
1963 }
1964 }
1965 // when a viewport is added dynamically to a root view that is already activated
1966 // just process the navigation instruction
1967 else {
1968 this._dequeueInstruction();
1969 }
1970 return Promise.resolve();
1971 }
1972 /**
1973 * Activates the router. This instructs the router to begin listening for history changes and processing instructions.
1974 *
1975 * @params options The set of options to activate the router with.
1976 */
1977 activate(options) {
1978 if (this.isActive) {
1979 return;
1980 }
1981 this.isActive = true;
1982 // route handler property is responsible for handling url change
1983 // the interface of aurelia-history isn't clear on this perspective
1984 this.options = Object.assign({ routeHandler: this.loadUrl.bind(this) }, this.options, options);
1985 this.history.activate(this.options);
1986 this._dequeueInstruction();
1987 }
1988 /**
1989 * Deactivates the router.
1990 */
1991 deactivate() {
1992 this.isActive = false;
1993 this.history.deactivate();
1994 }
1995 /**@internal */
1996 _queueInstruction(instruction) {
1997 return new Promise((resolve) => {
1998 instruction.resolve = resolve;
1999 this._queue.unshift(instruction);
2000 this._dequeueInstruction();
2001 });
2002 }
2003 /**@internal */
2004 _dequeueInstruction(instructionCount = 0) {
2005 return Promise.resolve().then(() => {
2006 if (this.isNavigating && !instructionCount) {
2007 // ts complains about inconsistent returns without void 0
2008 return void 0;
2009 }
2010 let instruction = this._queue.shift();
2011 this._queue.length = 0;
2012 if (!instruction) {
2013 // ts complains about inconsistent returns without void 0
2014 return void 0;
2015 }
2016 this.isNavigating = true;
2017 let navtracker = this.history.getState('NavigationTracker');
2018 let currentNavTracker = this.currentNavigationTracker;
2019 if (!navtracker && !currentNavTracker) {
2020 this.isNavigatingFirst = true;
2021 this.isNavigatingNew = true;
2022 }
2023 else if (!navtracker) {
2024 this.isNavigatingNew = true;
2025 }
2026 else if (!currentNavTracker) {
2027 this.isNavigatingRefresh = true;
2028 }
2029 else if (currentNavTracker < navtracker) {
2030 this.isNavigatingForward = true;
2031 }
2032 else if (currentNavTracker > navtracker) {
2033 this.isNavigatingBack = true;
2034 }
2035 if (!navtracker) {
2036 navtracker = Date.now();
2037 this.history.setState('NavigationTracker', navtracker);
2038 }
2039 this.currentNavigationTracker = navtracker;
2040 instruction.previousInstruction = this.currentInstruction;
2041 let maxInstructionCount = this.maxInstructionCount;
2042 if (!instructionCount) {
2043 this.events.publish("router:navigation:processing" /* Processing */, { instruction });
2044 }
2045 else if (instructionCount === maxInstructionCount - 1) {
2046 logger.error(`${instructionCount + 1} navigation instructions have been attempted without success. Restoring last known good location.`);
2047 restorePreviousLocation(this);
2048 return this._dequeueInstruction(instructionCount + 1);
2049 }
2050 else if (instructionCount > maxInstructionCount) {
2051 throw new Error('Maximum navigation attempts exceeded. Giving up.');
2052 }
2053 let pipeline = this.pipelineProvider.createPipeline(!this.couldDeactivate);
2054 return pipeline
2055 .run(instruction)
2056 .then(result => processResult(instruction, result, instructionCount, this))
2057 .catch(error => {
2058 return { output: error instanceof Error ? error : new Error(error) };
2059 })
2060 .then(result => resolveInstruction(instruction, result, !!instructionCount, this));
2061 });
2062 }
2063 /**@internal */
2064 _findViewModel(viewPort) {
2065 if (this.container.viewModel) {
2066 return this.container.viewModel;
2067 }
2068 if (viewPort.container) {
2069 let container = viewPort.container;
2070 while (container) {
2071 if (container.viewModel) {
2072 this.container.viewModel = container.viewModel;
2073 return container.viewModel;
2074 }
2075 container = container.parent;
2076 }
2077 }
2078 return undefined;
2079 }
2080}
2081const processResult = (instruction, result, instructionCount, router) => {
2082 if (!(result && 'completed' in result && 'output' in result)) {
2083 result = result || {};
2084 result.output = new Error(`Expected router pipeline to return a navigation result, but got [${JSON.stringify(result)}] instead.`);
2085 }
2086 let finalResult = null;
2087 let navigationCommandResult = null;
2088 if (isNavigationCommand(result.output)) {
2089 navigationCommandResult = result.output.navigate(router);
2090 }
2091 else {
2092 finalResult = result;
2093 if (!result.completed) {
2094 if (result.output instanceof Error) {
2095 logger.error(result.output.toString());
2096 }
2097 restorePreviousLocation(router);
2098 }
2099 }
2100 return Promise.resolve(navigationCommandResult)
2101 .then(_ => router._dequeueInstruction(instructionCount + 1))
2102 .then(innerResult => finalResult || innerResult || result);
2103};
2104const resolveInstruction = (instruction, result, isInnerInstruction, router) => {
2105 instruction.resolve(result);
2106 let eventAggregator = router.events;
2107 let eventArgs = { instruction, result };
2108 if (!isInnerInstruction) {
2109 router.isNavigating = false;
2110 router.isExplicitNavigation = false;
2111 router.isExplicitNavigationBack = false;
2112 router.isNavigatingFirst = false;
2113 router.isNavigatingNew = false;
2114 router.isNavigatingRefresh = false;
2115 router.isNavigatingForward = false;
2116 router.isNavigatingBack = false;
2117 router.couldDeactivate = false;
2118 let eventName;
2119 if (result.output instanceof Error) {
2120 eventName = "router:navigation:error" /* Error */;
2121 }
2122 else if (!result.completed) {
2123 eventName = "router:navigation:canceled" /* Canceled */;
2124 }
2125 else {
2126 let queryString = instruction.queryString ? ('?' + instruction.queryString) : '';
2127 router.history.previousLocation = instruction.fragment + queryString;
2128 eventName = "router:navigation:success" /* Success */;
2129 }
2130 eventAggregator.publish(eventName, eventArgs);
2131 eventAggregator.publish("router:navigation:complete" /* Complete */, eventArgs);
2132 }
2133 else {
2134 eventAggregator.publish("router:navigation:child:complete" /* ChildComplete */, eventArgs);
2135 }
2136 return result;
2137};
2138const restorePreviousLocation = (router) => {
2139 let previousLocation = router.history.previousLocation;
2140 if (previousLocation) {
2141 router.navigate(previousLocation, { trigger: false, replace: true });
2142 }
2143 else if (router.fallbackRoute) {
2144 router.navigate(router.fallbackRoute, { trigger: true, replace: true });
2145 }
2146 else {
2147 logger.error('Router navigation failed, and no previous location or fallbackRoute could be restored.');
2148 }
2149};
2150
2151/**
2152* The status of a Pipeline.
2153*/
2154var PipelineStatus;
2155(function (PipelineStatus) {
2156 PipelineStatus["Completed"] = "completed";
2157 PipelineStatus["Canceled"] = "canceled";
2158 PipelineStatus["Rejected"] = "rejected";
2159 PipelineStatus["Running"] = "running";
2160})(PipelineStatus || (PipelineStatus = {}));
2161
2162/**
2163 * A list of known router events used by the Aurelia router
2164 * to signal the pipeline has come to a certain state
2165 */
2166// const enum is preserved in tsconfig
2167var RouterEvent;
2168(function (RouterEvent) {
2169 RouterEvent["Processing"] = "router:navigation:processing";
2170 RouterEvent["Error"] = "router:navigation:error";
2171 RouterEvent["Canceled"] = "router:navigation:canceled";
2172 RouterEvent["Complete"] = "router:navigation:complete";
2173 RouterEvent["Success"] = "router:navigation:success";
2174 RouterEvent["ChildComplete"] = "router:navigation:child:complete";
2175})(RouterEvent || (RouterEvent = {}));
2176
2177/**
2178 * Available pipeline slot names to insert interceptor into router pipeline
2179 */
2180// const enum is preserved in tsconfig
2181var PipelineSlotName;
2182(function (PipelineSlotName) {
2183 /**
2184 * Authorization slot. Invoked early in the pipeline,
2185 * before `canActivate` hook of incoming route
2186 */
2187 PipelineSlotName["Authorize"] = "authorize";
2188 /**
2189 * Pre-activation slot. Invoked early in the pipeline,
2190 * Invoked timing:
2191 * - after Authorization slot
2192 * - after canActivate hook on new view model
2193 * - before deactivate hook on old view model
2194 * - before activate hook on new view model
2195 */
2196 PipelineSlotName["PreActivate"] = "preActivate";
2197 /**
2198 * Pre-render slot. Invoked later in the pipeline
2199 * Invokcation timing:
2200 * - after activate hook on new view model
2201 * - before commit step on new navigation instruction
2202 */
2203 PipelineSlotName["PreRender"] = "preRender";
2204 /**
2205 * Post-render slot. Invoked last in the pipeline
2206 */
2207 PipelineSlotName["PostRender"] = "postRender";
2208})(PipelineSlotName || (PipelineSlotName = {}));
2209
2210export { ActivateNextStep, AppRouter, BuildNavigationPlanStep, CanActivateNextStep, CanDeactivatePreviousStep, CommitChangesStep, DeactivatePreviousStep, LoadRouteStep, NavModel, NavigationInstruction, Pipeline, PipelineProvider, PipelineSlotName, PipelineStatus, Redirect, RedirectToRoute, RouteLoader, Router, RouterConfiguration, RouterEvent, activationStrategy, isNavigationCommand };
2211//# sourceMappingURL=aurelia-router.js.map