|
import { |
|
inject, Container, ViewSlot, ViewLocator, |
|
customElement, noView, CompositionTransaction, |
|
CompositionEngine, bindable |
|
} from 'aurelia-framework'; |
|
|
|
@noView() |
|
@customElement('navigation-view') |
|
@inject(Element, Container, ViewSlot, ViewLocator, CompositionEngine, CompositionTransaction) |
|
export class NavigationView { |
|
@bindable viewModel; |
|
@bindable model; |
|
@bindable title; |
|
viewChanged = null; |
|
|
|
subscribers = []; |
|
stack = []; |
|
busy = false; |
|
|
|
constructor(element, container, viewSlot, viewLocator, compositionEngine, compositionTransaction) { |
|
this.element = element; |
|
this.container = container; |
|
this.viewSlot = viewSlot; |
|
this.viewLocator = viewLocator; |
|
this.compositionEngine = compositionEngine; |
|
this.compositionTransaction = compositionTransaction; |
|
this.controller = null; |
|
|
|
if (!('initialComposition' in compositionTransaction)) { |
|
compositionTransaction.initialComposition = true; |
|
this.compositionTransactionNotifier = compositionTransaction.enlist(); |
|
} |
|
} |
|
|
|
get length() { |
|
return this.stack.length; |
|
} |
|
|
|
_getViewModel(instruction) { |
|
if (typeof instruction.viewModel === 'function') { |
|
instruction.viewModel = Origin.get(instruction.viewModel).moduleId; |
|
} |
|
|
|
if (typeof instruction.viewModel === 'string') { |
|
return this.compositionEngine.ensureViewModel(instruction); |
|
} |
|
|
|
return Promise.resolve(instruction); |
|
} |
|
|
|
created(owningView) { |
|
this.owningView = owningView; |
|
} |
|
|
|
bind(bindingContext, overrideContext) { |
|
this.container.viewModel = bindingContext; |
|
this.overrideContext = overrideContext; |
|
} |
|
|
|
attached() { |
|
this.navigate(this.viewModel, this.title, this.model); |
|
} |
|
|
|
_navigate(controller, title, swapDirection) { |
|
let oldController = this.controller; |
|
this.controller = controller; |
|
let classList = this.element.classList; |
|
this.title = title; |
|
|
|
let compositionHandler = () => { |
|
if (this.compositionTransactionNotifier) { |
|
this.compositionTransactionNotifier.done(); |
|
this.compositionTransactionNotifier = null; |
|
} |
|
classList.remove(`nav-${swapDirection}`); |
|
this.busy = false; |
|
}; |
|
|
|
if (!oldController) { |
|
return Promise.resolve(this.viewSlot.add(this.controller.view)).then( () => compositionHandler() ); |
|
} else { |
|
classList.add(`nav-${swapDirection}`); |
|
|
|
this.invokeSubscribers(swapDirection); |
|
|
|
return Promise.all([ |
|
this.viewSlot.add(this.controller.view), |
|
this.viewSlot.remove(oldController.view, true) |
|
]).then( () => compositionHandler() ); |
|
} |
|
} |
|
|
|
navigate(viewModel, title, model) { |
|
if (this.busy) return; |
|
|
|
this.busy = true; |
|
|
|
let childContainer = this.container.createChild(); |
|
let instruction = { |
|
viewModel: viewModel, |
|
container: this.container, |
|
childContainer: childContainer, |
|
model: model, |
|
skipActivation: true |
|
}; |
|
|
|
this._getViewModel(instruction).then(returnedInstruction => { |
|
this.invokeLifecycle(returnedInstruction.viewModel, 'canActivate', model).then(canActivate => { |
|
if (canActivate) { |
|
this.compositionEngine.createController(returnedInstruction).then(controller => { |
|
this.invokeLifecycle(returnedInstruction.viewModel, 'activate', model).then( () => { |
|
if (!this.compositionTransactionNotifier) { |
|
this.compositionTransactionOwnershipToken = this.compositionTransaction.tryCapture(); |
|
} |
|
controller.automate(this.overrideContext, this.owningView); |
|
|
|
if (this.compositionTransactionOwnershipToken) { |
|
return this.compositionTransactionOwnershipToken.waitForCompositionComplete().then(() => { |
|
this.compositionTransactionOwnershipToken = null; |
|
if (this.controller !== null) { |
|
this.stack.push({controller: this.controller, title: this.title}); |
|
} |
|
this._navigate(controller, title, 'forward'); |
|
}); |
|
} |
|
}); |
|
}); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
navigateBack() { |
|
if (!this.busy && this.stack.length > 0) { |
|
this.busy = true; |
|
let previousState = this.stack.pop(); |
|
let controller = previousState.controller; |
|
controller.bind(this); |
|
this._navigate(controller, previousState.title, 'back'); |
|
} |
|
} |
|
|
|
subscribe(callback) { |
|
let subscribers = this.subscribers; |
|
|
|
subscribers.push(callback); |
|
|
|
return { |
|
dispose() { |
|
let idx = subscribers.indexOf(callback); |
|
if (idx !== -1) { |
|
subscribers.splice(idx, 1); |
|
} |
|
} |
|
}; |
|
} |
|
|
|
invokeSubscribers(direction) { |
|
let i = this.subscribers.length; |
|
while (i--) { |
|
this.subscribers[i](direction); |
|
} |
|
} |
|
|
|
invokeLifecycle(instance, name, model) { |
|
if (typeof instance[name] === 'function') { |
|
let result = instance[name](model, this); |
|
|
|
if (result instanceof Promise) { |
|
return result; |
|
} |
|
|
|
if (result !== null && result !== undefined) { |
|
return Promise.resolve(result); |
|
} |
|
|
|
return Promise.resolve(true); |
|
} |
|
|
|
return Promise.resolve(true); |
|
} |
|
} |
The
activate
method of child viewModels are called passing the model as first argument and theNavigationView
as second:It can then be used to navigate to the following view from either the view or the viewModel:
nav.navigate('welcome', {title: 'test'})