Skip to content

Commit

Permalink
feat(templating-router): optional viewports
Browse files Browse the repository at this point in the history
Makes viewports optional in route configuration. Enables configuring viewports to be either empty or contain previous module.

Required by aurelia/templating-router/optional-viewports
Closes aurelia/router/issues/482
  • Loading branch information
jwx committed Oct 19, 2017
1 parent 54c84f8 commit 094e7d3
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 57 deletions.
19 changes: 15 additions & 4 deletions src/route-loader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {inject} from 'aurelia-dependency-injection';
import {CompositionEngine, useView, customElement} from 'aurelia-templating';
import {CompositionEngine, useView, inlineView, customElement} from 'aurelia-templating';
import {RouteLoader, Router} from 'aurelia-router';
import {relativeToFile} from 'aurelia-path';
import {Origin} from 'aurelia-metadata';
Expand All @@ -15,9 +15,13 @@ export class TemplatingRouteLoader extends RouteLoader {
loadRoute(router, config) {
let childContainer = router.container.createChild();

let viewModel = /\.html/.test(config.moduleId)
? createDynamicClass(config.moduleId)
: relativeToFile(config.moduleId, Origin.get(router.container.viewModel.constructor).moduleId);
let viewModel = config === null
? createEmptyClass()
: /\.html/.test(config.moduleId)
? createDynamicClass(config.moduleId)
: relativeToFile(config.moduleId, Origin.get(router.container.viewModel.constructor).moduleId);

config = config || {};

let instruction = {
viewModel: viewModel,
Expand Down Expand Up @@ -55,3 +59,10 @@ function createDynamicClass(moduleId) {

return DynamicClass;
}

function createEmptyClass() {
@inlineView('<template></template>')
class EmptyClass { }

return EmptyClass;
}
154 changes: 101 additions & 53 deletions src/router-view.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Container, inject} from 'aurelia-dependency-injection';
import {createOverrideContext} from 'aurelia-binding';
import {ViewSlot, ViewLocator, customElement, noView, BehaviorInstruction, bindable, CompositionTransaction, CompositionEngine, ShadowDOM, SwapStrategies} from 'aurelia-templating';
import {ViewSlot, ViewLocator, customElement, noView, BehaviorInstruction, bindable, CompositionTransaction, CompositionEngine, ShadowDOM, SwapStrategies, SwapStrategiesStateful} from 'aurelia-templating';
import {Router} from 'aurelia-router';
import {Origin} from 'aurelia-metadata';
import {DOM} from 'aurelia-pal';
Expand All @@ -14,6 +14,10 @@ export class RouterView {
@bindable layoutViewModel;
@bindable layoutModel;
element;
name;
stateful;
nonStatefulName;
hidden = false;

constructor(element, container, viewSlot, router, viewLocator, compositionTransaction, compositionEngine) {
this.element = element;
Expand All @@ -23,7 +27,10 @@ export class RouterView {
this.viewLocator = viewLocator;
this.compositionTransaction = compositionTransaction;
this.compositionEngine = compositionEngine;
this.router.registerViewPort(this, this.element.getAttribute('name'));
this.name = this.element.getAttribute('name');
this.stateful = this.name.indexOf('.') !== -1;
this.nonStatefulName = this.name.split('.')[0];
this.router.registerViewPort(this, this.name);

if (!('initialComposition' in compositionTransaction)) {
compositionTransaction.initialComposition = true;
Expand All @@ -47,7 +54,7 @@ export class RouterView {
let viewModelResource = component.viewModelResource;
let metadata = viewModelResource.metadata;
let config = component.router.currentInstruction.config;
let viewPort = config.viewPorts ? config.viewPorts[viewPortInstruction.name] : {};
let viewPort = (config.viewPorts ? config.viewPorts[viewPortInstruction.name] : {}) || {};

childContainer.get(RouterViewLocator)._notify(this);

Expand All @@ -67,44 +74,72 @@ export class RouterView {
}

return metadata.load(childContainer, viewModelResource.value, null, viewStrategy, true)
.then(viewFactory => {
if (!this.compositionTransactionNotifier) {
this.compositionTransactionOwnershipToken = this.compositionTransaction.tryCapture();
}

if (layoutInstruction.viewModel || layoutInstruction.view) {
viewPortInstruction.layoutInstruction = layoutInstruction;
}

viewPortInstruction.controller = metadata.create(childContainer,
BehaviorInstruction.dynamic(
this.element,
viewModel,
viewFactory
)
);

if (waitToSwap) {
return null;
}

this.swap(viewPortInstruction);
});
.then(viewFactory => {
if (!this.compositionTransactionNotifier) {
this.compositionTransactionOwnershipToken = this.compositionTransaction.tryCapture();
}

if (layoutInstruction.viewModel || layoutInstruction.view) {
viewPortInstruction.layoutInstruction = layoutInstruction;
}

viewPortInstruction.controller = metadata.create(childContainer,
BehaviorInstruction.dynamic(
this.element,
viewModel,
viewFactory
)
);

if (waitToSwap) {
return null;
}

this.swap(viewPortInstruction);
});
}

swap(viewPortInstruction) {
let layoutInstruction = viewPortInstruction.layoutInstruction;
let previousView = this.view;
let viewPort = this.router.viewPorts[viewPortInstruction.name];

let work = () => {
let swapStrategy = SwapStrategies[this.swapOrder] || SwapStrategies.after;
let viewSlot = this.viewSlot;
let siblingViewPorts = [];
for (let vpName in this.router.viewPorts) {
let vp = this.router.viewPorts[vpName];
if (vp !== viewPort && vp.nonStatefulName === viewPort.nonStatefulName) {
siblingViewPorts.push(vp);
}
}

swapStrategy(viewSlot, previousView, () => {
return Promise.resolve(viewSlot.add(this.view));
}).then(() => {
this._notify();
});
let work = () => {
if (siblingViewPorts.length > 0) {
let swapStrategy = SwapStrategiesStateful[this.swapOrder] || SwapStrategiesStateful.after;
let viewSlot = this.viewSlot;

let previous = [];
if (viewPortInstruction.active) {
previous = siblingViewPorts;
}
if (!viewPort.stateful && viewPortInstruction.strategy === 'replace') {
previous.push(viewPort);
}
return swapStrategy(this, previous, () => {
return Promise.resolve(viewPortInstruction.strategy === 'replace' ? viewSlot.add(this.view) : undefined);
}).then(() => {
this._notify();
});
}
else {
let swapStrategy = SwapStrategies[this.swapOrder] || SwapStrategies.after;
let viewSlot = this.viewSlot;

swapStrategy(viewSlot, previousView, () => {
return Promise.resolve(viewSlot.add(this.view));
}).then(() => {
this._notify();
});
}
};

let ready = owningView => {
Expand All @@ -119,29 +154,42 @@ export class RouterView {
return work();
};

if (layoutInstruction) {
if (!layoutInstruction.viewModel) {
// createController chokes if there's no viewmodel, so create a dummy one
// should we use something else for the view model here?
layoutInstruction.viewModel = {};
if (viewPortInstruction.strategy === 'replace') {
if (layoutInstruction) {
if (!layoutInstruction.viewModel) {
// createController chokes if there's no viewmodel, so create a dummy one
// should we use something else for the view model here?
layoutInstruction.viewModel = {};
}

return this.compositionEngine.createController(layoutInstruction).then(controller => {
ShadowDOM.distributeView(viewPortInstruction.controller.view, controller.slots || controller.view.slots);
controller.automate(createOverrideContext(layoutInstruction.viewModel), this.owningView);
controller.view.children.push(viewPortInstruction.controller.view);
return controller.view || controller;
}).then(newView => {
this.view = newView;
return ready(newView);
});
}

return this.compositionEngine.createController(layoutInstruction).then(controller => {
ShadowDOM.distributeView(viewPortInstruction.controller.view, controller.slots || controller.view.slots);
controller.automate(createOverrideContext(layoutInstruction.viewModel), this.owningView);
controller.view.children.push(viewPortInstruction.controller.view);
return controller.view || controller;
}).then(newView => {
this.view = newView;
return ready(newView);
});
}

this.view = viewPortInstruction.controller.view;
this.view = viewPortInstruction.controller.view;

return ready(this.owningView);
return ready(this.owningView);
}
else {
return work();
}
}


hide(hide_: boolean) {
if (this.hidden !== hide_) {
this.hidden = hide_;
return this.viewSlot.hide(hide_);
}
return Promise.resolve();
}

_notify() {
if (this.compositionTransactionNotifier) {
this.compositionTransactionNotifier.done();
Expand Down

0 comments on commit 094e7d3

Please sign in to comment.