UNPKG

@paperbits/core

Version:
187 lines (148 loc) 8.85 kB
import * as ko from "knockout"; import { IInjector, IInjectorModule, InjectableMetadataKey } from "@paperbits/common/injection"; import { ComponentConfig } from "@paperbits/common/ko/decorators/component.decorator"; import { ComponentDefinition } from "./componentDefinition"; export class KnockoutRegistrationLoaders implements IInjectorModule { public register(injector: IInjector): void { const injectableComponentLoader = { loadViewModel(name: string, config: any, callback: (resolvedViewModel: any) => void): void { const injectable = Reflect.getMetadata(InjectableMetadataKey, config); if (!injectable) { console.warn(`The component "${name}" is not registered in dependency injector container. Use injector.bind(...) to register it.`); callback(null); // If no injectable metadata, let another loader handle it. return; } const viewModelConstructor: any = function (params?: any): any { const resolvedInjectable: any = injector.resolve(injectable.name); let instance = resolvedInjectable; if (resolvedInjectable.factory) { instance = resolvedInjectable.factory(injector, params); } Object.getOwnPropertyNames(instance.constructor.prototype).forEach(prop => { if (typeof instance[prop] === "function" && prop !== "constructor") { instance[prop] = instance[prop].bind(instance); } }); const parameterDescriptions = Reflect.getMetadata("params", instance.constructor); if (parameterDescriptions && params) { /* Attempt to convert params string to object, otherwise it doesn't * make sense to iterate properties */ if (typeof params === "string") { try { params = eval(`(${params})`); } catch (error) { // Do nothing } } if (typeof params === "object") { parameterDescriptions.forEach(parameterName => { const instanceValue = instance[parameterName]; const paramerterValue = params[parameterName]; if (paramerterValue === undefined) { return; } if (ko.isObservable(instanceValue)) { if (ko.isObservable(paramerterValue)) { // Assigning initial value instanceValue(paramerterValue()); let holdInstanceUpdate = false; let holdParameterUpdates = false; // Subscribing for all future changes paramerterValue.subscribe((value) => { if (holdInstanceUpdate) { return; } holdParameterUpdates = true; instanceValue(value); holdParameterUpdates = false; }); instanceValue.subscribe(value => { if (holdParameterUpdates) { return; } holdInstanceUpdate = true; paramerterValue(value); holdInstanceUpdate = false; }); } else { instanceValue(paramerterValue); } } else { instance[parameterName] = ko.unwrap(paramerterValue); } }); } } const eventDescriptions = Reflect.getMetadata("events", instance.constructor); if (eventDescriptions) { eventDescriptions.forEach(methodReference => { if (params && params[methodReference]) { instance[methodReference] = params[methodReference]; } else { console.warn(`Event "${methodReference}" in the component "${name}" doesn't have listeners.`); } }); } const onMountedMethodDescriptions = Reflect.getMetadata("onmounted", instance.constructor); if (onMountedMethodDescriptions) { onMountedMethodDescriptions.forEach(methodDescription => { const methodReference = instance[methodDescription]; if (methodReference) { methodReference(); } }); } return instance; }; ko.components.defaultLoader.loadViewModel(name, viewModelConstructor, callback); }, loadTemplate(name: string, templateHtml: any, callback: (resolvedTemplate: Node[]) => void): void { const parseHtmlFragment = ko.utils.parseHtmlFragment; const nodes = parseHtmlFragment(templateHtml, document); ko.components.defaultLoader.loadTemplate(name, nodes, callback); }, loadComponent(componentName: string, config: ComponentConfig, callback: (definition: ko.components.Component) => void): void { const callbackWrapper: (result: ko.components.Component) => void = (resultWrapper: ko.components.Component) => { const createViewModelWrapper = (params: any, options: any) => { const attrs: NamedNodeMap = options.element["attributes"]; if (attrs && attrs.length > 0) { const runtimeParams = {}; for (const attr of Array.prototype.slice.call(attrs)) { if (attr.name.startsWith("runtime-")) { const paramName = attr.name.split("-")[1]; runtimeParams[paramName] = attr.value; } } if (Object.keys(runtimeParams).length > 0) { params = { ...runtimeParams, ...params }; } } return resultWrapper.createViewModel(params, options); }; const definitionWrapper: ComponentDefinition = { template: resultWrapper.template, createViewModel: createViewModelWrapper, constructor: config.constructor, encapsulation: config.encapsulation }; callback(definitionWrapper); }; ko.components.defaultLoader.loadComponent(componentName, config, callbackWrapper); }, }; ko.components.loaders.unshift(injectableComponentLoader); ko.bindingProvider.instance.preprocessNode = (node: HTMLElement): Node[] => { if (node.removeAttribute) { const subscription = ko.bindingEvent.subscribe(node, "childrenComplete", () => { node.removeAttribute("data-bind"); subscription.dispose(); }); } return null; }; } }