@paperbits/core
Version:
Paperbits core components.
187 lines (148 loc) • 8.85 kB
text/typescript
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;
};
}
}