Skip to content

Instantly share code, notes, and snippets.

@peisenmann
Created February 17, 2016 19:59
Show Gist options
  • Save peisenmann/2256d5c1e6c91249923e to your computer and use it in GitHub Desktop.
Save peisenmann/2256d5c1e6c91249923e to your computer and use it in GitHub Desktop.
Service to construct arbitrary html in Aurelia
import {Container, CompositionEngine, ViewSlot} from 'aurelia-framework';
import {Origin} from 'aurelia-metadata';
export class ViewConstructionService {
static inject = [Container, CompositionEngine];
constructor(container:Container, compositionEngine:CompositionEngine) {
this.container = container;
this.compositionEngine = compositionEngine;
}
/**
* Invoke a lifecycle method on an instance
* @param instance {CustomElement|ViewModel} The instance on which to invoke a lifecycle method
* @param name {string} The name of the method to invoke
* @param model {*|Object?} The argument to pass to the lifecycle method
* @returns {*|Promise.<boolean>} A Promise with the return value of the lifecycle operation, or Promise<true> if the method doesn't exist or returns null/undefined.
*/
static invokeLifecycle(instance, name, model) {
let result = typeof instance[name] === 'function' ? instance[name](model) : true;
return Promise.resolve(typeof result !== 'undefined' && result != null ? result : true);
}
/**
* Given a context with a potential variety of viewModel types, this gets the real viewModel loaded up
* @param context {CompositionContext} The context provides information to properly load the viewModel
* @returns {Promise.<CompositionContext>} A Promise for the context after things have been resolved
*/
getViewModel(context) {
if (typeof context.viewModel === 'function') {
context.viewModel = Origin.get(context.viewModel).moduleId;
}
if (typeof context.viewModel === 'string') {
return this.compositionEngine.ensureViewModel(context);
}
return Promise.resolve(context);
}
/**
*
* @param viewModel
* @param elementHost
* @param model
* @returns {*}
*/
construct(viewModel, elementHost, model = {}) {
let childContainer = this.container.createChild(),
container = this.container;
elementHost = elementHost || document.createElement('div');
let instruction = {viewModel, model, childContainer, container};
let returnedInstruction;
return this.getViewModel(instruction)
.then(ri => returnedInstruction = ri)
.then(() => ViewConstructionService.invokeLifecycle(returnedInstruction.viewModel, 'canActivate', model))
.then(booleanPromise)
.then(() => this.compositionEngine.createController(returnedInstruction))
.then(controller => {
controller.automate();
let slot = new ViewSlot(elementHost, true);
slot.add(controller.view);
let attachmentPollingInterval = setInterval(() => {
if (elementHost.parentNode) {
slot.attached();
clearInterval(attachmentPollingInterval);
}
}, 200);
return {controller, slot, elementHost,
dispose: () => this.deconstruct(controller, slot, elementHost),
attached: () => {clearInterval(attachmentPollingInterval); slot.attached();}};
});
}
deconstruct(controller, slot: ViewSlot, elementHost: Element) {
return ViewConstructionService.invokeLifecycle(controller.bindingContext, 'canDeactivate', null)
.then(booleanPromise)
.then(() => {
ViewConstructionService.invokeLifecycle(controller.bindingContext, 'deactivate');
elementHost.parentNode.removeChild(elementHost);
slot.detached();
controller.unbind();
});
}
}
function booleanPromise(b) {
return new Promise((resolve, reject) => b || typeof b === 'undefined' ? resolve(b) : reject(b));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment