diff --git a/package.json b/package.json index e9f8032e..1933c724 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "aurelia-history": "^1.0.0", "aurelia-logging": "^1.0.0", "aurelia-path": "^1.0.0", - "aurelia-route-recognizer": "^1.0.0" + "aurelia-route-recognizer": "^1.0.0", + "aurelia-templating": "^1.6.0" }, "devDependencies": { "aurelia-pal-browser": "^1.0.0-rc.1.0.0", @@ -59,7 +60,8 @@ "aurelia-history": "^1.0.0", "aurelia-logging": "^1.0.0", "aurelia-path": "^1.0.0", - "aurelia-route-recognizer": "^1.0.0" + "aurelia-route-recognizer": "^1.0.0", + "aurelia-templating": "^1.6.0" }, "devDependencies": { "aurelia-tools": "0.2.4", diff --git a/src/navigation-instruction.js b/src/navigation-instruction.js index 42fa2446..76d74a98 100644 --- a/src/navigation-instruction.js +++ b/src/navigation-instruction.js @@ -121,7 +121,7 @@ export class NavigationInstruction { /** * Adds a viewPort instruction. */ - addViewPortInstruction(viewPortName: string, strategy: string, moduleId: string, component: any): any { + addViewPortInstruction(viewPortName: string, strategy: string, moduleId: string, component: any, active: boolean): any { const config = Object.assign({}, this.lifecycleArgs[1], { currentViewPort: viewPortName }); let viewportInstruction = this.viewPortInstructions[viewPortName] = { name: viewPortName, @@ -129,7 +129,8 @@ export class NavigationInstruction { moduleId: moduleId, component: component, childRouter: component.childRouter, - lifecycleArgs: [].concat(this.lifecycleArgs[0], config, this.lifecycleArgs[2]) + lifecycleArgs: [].concat(this.lifecycleArgs[0], config, this.lifecycleArgs[2]), + active: active }; return viewportInstruction; @@ -227,6 +228,9 @@ export class NavigationInstruction { return undefined; })); } + } + else if (viewPortInstruction.active && !viewPortInstruction.childNavigationInstruction) { + delaySwaps.push({viewPort, viewPortInstruction}); } else { if (viewPortInstruction.childNavigationInstruction) { loads.push(viewPortInstruction.childNavigationInstruction._commitChanges(waitToSwap)); diff --git a/src/navigation-plan.js b/src/navigation-plan.js index c8f27c74..b1a80bc4 100644 --- a/src/navigation-plan.js +++ b/src/navigation-plan.js @@ -1,5 +1,5 @@ -import { Redirect } from './navigation-commands'; -import { _resolveUrl } from './util'; +import {Redirect} from './navigation-commands'; +import {_resolveUrl} from './util'; /** * The strategy to use when activating modules during navigation. @@ -64,8 +64,15 @@ export function _buildNavigationPlan(instruction: NavigationInstruction, forceLi if (config.viewPorts) { for (let viewPortName in config.viewPorts) { + if (config.viewPorts[viewPortName] === null || config.viewPorts[viewPortName].moduleId === null) { + config.viewPorts[viewPortName] = null; + } if (config.viewPorts[viewPortName] !== undefined || !viewPorts[viewPortName]) { - viewPorts[viewPortName] = config.viewPorts[viewPortName]; + if (config.stateful || (config.viewPorts[viewPortName] && config.viewPorts[viewPortName].stateful)) { + config.viewPorts[viewPortName].stateful = true; + viewPortName = instruction.router._ensureStatefulViewPort(viewPortName, config.viewPorts[viewPortName].moduleId); + } + viewPorts[viewPortName] = config.viewPorts[viewPortName.split('.')[0]]; } } } @@ -78,7 +85,21 @@ export function _buildNavigationPlan(instruction: NavigationInstruction, forceLi } } - return Promise.all(pending).then(() => plan); + return Promise.all(pending).then(() => { + for (let viewPortName in plan) { + if (viewPortName.indexOf('.') != -1) { + let shortName = viewPortName.split('.')[0]; + if (!plan[shortName]) { + plan[shortName] = { + name: shortName, + strategy: activationStrategy.replace, + config: null + } + } + } + } + return plan; + }); } function buildViewPortPlan(instruction: NavigationInstruction, viewPorts: any, forceLifecycleMinimum, newParams: boolean, viewPortName: string, previous: boolean) { @@ -101,7 +122,7 @@ function buildViewPortPlan(instruction: NavigationInstruction, viewPorts: any, f viewPortPlan.prevComponent = prevViewPortInstruction.component; viewPortPlan.prevModuleId = prevViewPortInstruction.moduleId; } - if (nextViewPortConfig) { + if (nextViewPortConfig !== undefined) { viewPortPlan.config = nextViewPortConfig; viewPortPlan.active = true; } @@ -118,6 +139,9 @@ function buildViewPortPlan(instruction: NavigationInstruction, viewPorts: any, f } else if (prevViewPortInstruction.moduleId !== nextViewPortConfig.moduleId) { viewPortPlan.strategy = activationStrategy.replace; + } + else if (!nextViewPortConfig.stateful && !prevViewPortInstruction.active) { + viewPortPlan.strategy = activationStrategy.replace; } else if ('determineActivationStrategy' in prevViewPortInstruction.component.viewModel) { viewPortPlan.strategy = prevViewPortInstruction.component.viewModel .determineActivationStrategy(...instruction.lifecycleArgs); diff --git a/src/route-loading.js b/src/route-loading.js index 2b19a89b..aa83a451 100644 --- a/src/route-loading.js +++ b/src/route-loading.js @@ -49,7 +49,8 @@ function determineWhatToLoad(navigationInstruction: NavigationInstruction, toLoa viewPortName, viewPortPlan.strategy, viewPortPlan.prevModuleId, - viewPortPlan.prevComponent); + viewPortPlan.prevComponent, + viewPortPlan.active); if (viewPortPlan.childNavigationInstruction) { viewPortInstruction.childNavigationInstruction = viewPortPlan.childNavigationInstruction; @@ -69,7 +70,8 @@ function loadRoute(routeLoader: RouteLoader, navigationInstruction: NavigationIn viewPortPlan.name, viewPortPlan.strategy, moduleId, - component); + component, + viewPortPlan.active); let childRouter = component.childRouter; if (childRouter) { diff --git a/src/router.js b/src/router.js index c7fa11c8..7907de5c 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import {RouteRecognizer} from 'aurelia-route-recognizer'; import {Container} from 'aurelia-dependency-injection'; import {History} from 'aurelia-history'; +import {TemplatingEngine} from 'aurelia-templating'; import {NavigationInstruction} from './navigation-instruction'; import {NavModel} from './nav-model'; import {RouterConfiguration} from './router-configuration'; @@ -504,6 +505,25 @@ export class Router { return c; }); } + + _ensureStatefulViewPort(name, moduleId) { + let viewPort = this.viewPorts[name]; + let viewPortName = `${name}.${moduleId}`; + + if (!this.viewPorts[viewPortName]) { + let newElement = viewPort.element.ownerDocument.createElement('router-view'); + newElement.setAttribute('name', viewPortName); + viewPort.element.insertAdjacentElement('afterend', newElement); + let templatingEngine = viewPort.container.get(TemplatingEngine); + templatingEngine.enhance({ + element: newElement, + container: viewPort.container, + resources: viewPort.resources + }); + } + + return viewPortName; + } } function validateRouteConfig(config: RouteConfig, routes: Array): void { diff --git a/test/navigation-plan.spec.js b/test/navigation-plan.spec.js index 6f81660d..1847fee9 100644 --- a/test/navigation-plan.spec.js +++ b/test/navigation-plan.spec.js @@ -24,20 +24,23 @@ describe('NavigationPlanStep', () => { firstInstruction = new NavigationInstruction({ fragment: 'first', config: { viewPorts: { default: { moduleId: './first' }}}, - params: { id: '1' } + params: { id: '1' }, + router: {} }); sameAsFirstInstruction = new NavigationInstruction({ fragment: 'first', config: { viewPorts: { default: { moduleId: './first' }}}, previousInstruction: firstInstruction, - params: { id: '1' } + params: { id: '1' }, + router: {} }); secondInstruction = new NavigationInstruction({ fragment: 'second', config: { viewPorts: { default: { moduleId: './second' }}}, - previousInstruction: firstInstruction + previousInstruction: firstInstruction, + router: {} }); }); @@ -95,8 +98,8 @@ describe('NavigationPlanStep', () => { }); it('is no-change when nothing changes', (done) => { - firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}); - + firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}, true); + step.run(sameAsFirstInstruction, state.next) .then(() => { expect(state.result).toBe(true); @@ -107,7 +110,7 @@ describe('NavigationPlanStep', () => { it('can be determined by route config', (done) => { sameAsFirstInstruction.config.activationStrategy = 'fake-strategy'; - firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}); + firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}, true); step.run(sameAsFirstInstruction, state.next) .then(() => { @@ -119,7 +122,7 @@ describe('NavigationPlanStep', () => { it('can be determined by view model', (done) => { let viewModel = { determineActivationStrategy: () => 'vm-strategy'}; - firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel }); + firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel }, true); step.run(sameAsFirstInstruction, state.next) .then(() => { @@ -132,7 +135,7 @@ describe('NavigationPlanStep', () => { it('is invoke-lifecycle when only params change', (done) => { firstInstruction.params = { id: '1' }; sameAsFirstInstruction.params = { id: '2' }; - firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}); + firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}, true); step.run(sameAsFirstInstruction, state.next) .then(() => { @@ -146,7 +149,7 @@ describe('NavigationPlanStep', () => { firstInstruction.queryParams = { param: 'foo' }; sameAsFirstInstruction.queryParams = { param: 'bar' }; sameAsFirstInstruction.options.compareQueryParams = true; - firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}); + firstInstruction.addViewPortInstruction('default', 'ignored', './first', { viewModel: {}}, true); step.run(sameAsFirstInstruction, state.next) .then(() => {