Created
February 17, 2016 19:59
-
-
Save peisenmann/2256d5c1e6c91249923e to your computer and use it in GitHub Desktop.
Service to construct arbitrary html in Aurelia
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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