Last active
July 3, 2019 20:24
-
-
Save thiagomajesk/6bead491e1b062b10be0559b6dce9c6c to your computer and use it in GitHub Desktop.
Knockout & Typescript & Webpack bootstrap code
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
// Component definition (holds information about template and view model) | |
class ClickCounterComponent extends Component { | |
constructor(name: string, viewModel: ViewModelClass) { | |
super(name, viewModel, template) | |
} | |
} | |
// Actual view model (This separation allows us to reuse view models for components) | |
class ClickCounterViewModel extends ViewModel { | |
public count: ko.Observable<number> = ko.observable<number>(0); | |
constructor(params: any) { | |
super(params); | |
} | |
public increment(): void { | |
this.count(this.count() + 1); | |
} | |
} | |
// Standard view model | |
class DashboardViewModel extends ViewModel | |
{ | |
public number: ko.Observable<number>; | |
constructor(params: number) { | |
super(params); | |
this.number = ko.observable<number>(params); | |
} | |
public increase(): void { | |
this.number(this.number()+1); | |
} | |
} | |
/* | |
* Setup code for the application (usually the webpack entry point) | |
*/ | |
const application = new Application(); | |
application.registerViewModel("dashboard", DashboardViewModel); | |
application.registerComponent("click-counter", ClickCounterComponent, ClickCounterViewModel); | |
application.start(); |
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
<body> | |
<!-- Standard view model data bind usage --> | |
<div data-bind="viewModel: 'dashboard'"> | |
<p data-bind="text: number"></p> | |
</div> | |
<!-- Allows passing parameters --> | |
<div data-bind="viewModel: { name: 'dashboard', params: 100}"> | |
<p data-bind="text: number"></p> | |
</div> | |
<!-- Can still be used with components --> | |
<click-counter></click-counter> | |
</body> |
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 ko, { BindingHandler } from "knockout"; | |
import $ from "jquery"; | |
import { Md5 } from 'ts-md5'; | |
export class Application { | |
public readonly viewModels: Array<ViewModelDefinition> = []; | |
public readonly components: Array<ComponentDefinition> = []; | |
public masterViewModel: ViewModel; | |
constructor() { | |
let bindingHandlers: any = ko.bindingHandlers; | |
bindingHandlers.viewModel = new ViewModelBindingHandler(this); | |
this.masterViewModel = new ViewModel(); | |
} | |
public registerViewModel(name: string, viewModel: ViewModelClass) { | |
let definition = { name, class: viewModel }; | |
if (this.viewModels.includes(definition)) return; | |
this.viewModels.push(definition); | |
} | |
public registerComponent(name: string, component: ComponentClass, viewModel: ViewModelClass) { | |
let definition = { name, class: component, viewModel }; | |
if (this.components.includes(definition)) return; | |
this.components.push(definition); | |
} | |
public start(): void { | |
$(document).ready(() => { | |
this.bootstrapComponents(); | |
this.bootstrapViewModels(); | |
}); | |
} | |
private bootstrapViewModels(): void { | |
ko.applyBindings(this.masterViewModel); | |
(<any>window)["__masterViewModel__"] = this.masterViewModel; | |
} | |
private bootstrapComponents(): void { | |
this.components.forEach((definition) => { | |
let component = new definition.class(definition.name, definition.viewModel); | |
component.register(); | |
}) | |
} | |
} | |
interface ViewModelDefinition { | |
name: string, | |
class: ViewModelClass; | |
} | |
interface ComponentDefinition { | |
name: string; | |
class: ComponentClass, | |
viewModel: ViewModelClass | |
} | |
interface ViewModelBindingHandlerStructure { | |
name: string; | |
params: any; | |
} | |
class ViewModelBindingHandler implements BindingHandler<string>{ | |
constructor(private readonly application: Application) { } | |
public init = (element: HTMLElement, valueAccessor: () => string, | |
allBindings: ko.AllBindings, viewModel: any, | |
bindingContext: ko.BindingContext<string>): ko.BindingHandlerControlsDescendant => { | |
let [name, params] = this.unwrap(valueAccessor()); | |
let definition = this.application.viewModels.find(d => d.name == name); | |
if (definition == null) throw new Error(`${name} is not registered`); | |
let vm: any = new definition.class(params); | |
this.attachToMasterViewModel(vm, name, element); | |
let innerBindingContext = bindingContext.createChildContext(vm); | |
ko.applyBindingsToDescendants(innerBindingContext, element); | |
return { controlsDescendantBindings: true }; | |
} | |
private attachToMasterViewModel = async (viewModel: ViewModel, name: string, element: HTMLElement): Promise<void> => { | |
return new Promise((resolve) => { | |
let masterViewModel: any = this.application.masterViewModel; | |
masterViewModel.slaveViewModels = masterViewModel.slaveViewModels || {}; | |
let hash = Md5.hashStr(JSON.stringify(element)); | |
let propertyName = `${name}+${hash}`; | |
$(element).attr('data-id', hash as string); | |
masterViewModel.slaveViewModels[propertyName] = viewModel; | |
resolve(); | |
}); | |
} | |
private isObject(value: string | ViewModelBindingHandlerStructure): value is ViewModelBindingHandlerStructure { | |
let object = value as ViewModelBindingHandlerStructure; | |
return object.name != undefined && object.params != undefined; | |
} | |
private unwrap(value: string | ViewModelBindingHandlerStructure): [string, any] { | |
let object = ko.unwrap(value); | |
if (typeof object == "string") return [object as string, null]; | |
if (this.isObject(object)) { return [object.name, object.params] } | |
return [null, null]; | |
} | |
} | |
export interface ViewModelClass { | |
new(params?: any): ViewModel; | |
} | |
export class ViewModel { | |
constructor(private readonly params?: any) { } | |
} | |
export interface ComponentClass { | |
new(name?: string, viewModel?: ViewModelClass): Component; | |
} | |
export class Component { | |
constructor(private readonly name?: string, | |
private readonly viewModel?: ViewModelClass, | |
private readonly template?: string) { } | |
public register() { | |
if (ko.components.isRegistered(this.name)) return; | |
ko.components.register(this.name, { | |
template: this.template, | |
viewModel: this.getViewModel | |
}); | |
} | |
private getViewModel = (params: any): ViewModel => { | |
return new this.viewModel(params); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment