Widget anatomy

What is widget?

A widget in Paperbits is a UI component consisting of a view template and view model behind it. Depending on the UI framework you use (Knockout, Vue, Angular or the other), both template syntax and view model implementation may vary.

In the simplest form the parts of a widget would look like the following:

HTML (template)

<h1>My component</h1>

Typescript (view model)

class MyComponent { }

... and used like this:

<my-component></my-component>

@Component decorator

Besides binding a view model to its template, this decorator simplifies registration of a component in UI framework. The usage of the decorator is straightforward and looks similarly for different UI frameworks:

@Component({
selector: "my-component",
template: "<h1>My component</h1>"
})
export class MyComponent {
constructor(someService: SomeService) { ... }
}

The only difference is the path from where it gets imported:

Knockout

import { Component } from "@paperbits/core/ko/component";

Vue

import { Component } from "@paperbits/core/vue/component";

Angular

import { Component } from "@angular/core/component";

Lifecycle hooks

Now, when you know how to declare a component, it’s good to know about a few hooks responsible for the lifecycle of the component.

Initialization methods - they are invoked right after a component instance was created. All the input parameters (see below) are available at this time.

Knockout

@OnMounted()
public async initialize(): Promise {
// Your initialization code
}

Vue

@OnMounted()
public async initialize(): Promise {
// Your initialization code
}

Angular

public async ngOnInit(): Promise {
// Your initialization code
}

Input parameters - this is data passed into a component from its host.

Knockout

@Param()
public message: string;

Binding the parameter:

<component [message]="message"></component>

Vue

@Prop()
public message: string;

Binding the parameter:

<component v-bind:message="message"></component>

Angular

@Input()
public message: string;

Binding the parameter:

<component params="onChange: doSomething"></component>

Events - these are callbacks used to notify the component host when needed.

Knockout

@Event()
public onChange: (model: ButtonModel) => void;

Emitting the event:

this.onChange(model);

Subscribing for the event:

<component v-on:onChange="doSomething"></component>

Vue

@Event()
public onChange: (model: ButtonModel) => void;

Emitting the event:

this.onChange(model);

Subscribing for the event:

<component v-on:onChange="doSomething"></component>

Angular

@Output()
public onChange: EventEmitter<ButtonModel>;

Emitting the event:

this.onChange.emit(model);

Subscribing for the event:

<component (onChange)="doSomething()"></component>

Dependency injection

For Inversion of Control (IoC), by default, we use InversifyJS which is greatly suitable for applications powered by TypeScript. Though, you're welcome to replace it with any other you like.

If you went through "Getting started" tutorial, you have probably seen in startup.develop.ts file how we import and instantiate a container:

@import { InversifyInjector } from "@paperbits/common/injection";
cost injector = new InversifyInjector();

... and register components:

injector.bind("myComponent", MyComponent);

... or modules:

injector.bindModule(new CoreModule());

Then, when the container is ready, we can resolve components:

const instance = injector.resolve("myComponent");

Data contract, Model, ModelBinder, ViewModelBinder

Data contract - is an interface for a raw JSON data object that represents configuration of a widget or a service (and which gets usually stored on the backend). For example:

interface HyperlinkContract {
contentTypeKey: string; // pages/abcdef
target: string; // "blank"
rel: string; // "nofollow"
}

Model - is a class that represents a structure fulfilled with data from one or more Contracts and other sources.

class HyperlinkModel {
url: string; // note that here permalink key was replaced by real URL.
target: string; // e.g. "blank"
rel: string; // e.g. "nofollow"
}

View model - is a structure that represents the model in specific UI framework. For example:

Knockout

@Component({ selector: "hyperlink" })
class HyperlinkViewModel {
url: KnockoutObservable<string>;
target: KnockoutObservable<string>;
rel: KnockoutObservable<string>;
}
<a data-bind="attr: { href: url, rel: rel }">My link</a>

Vue

@Component({ selector: "hyperlink" })
class HyperlinkViewModel {
url: string;
target: string;
rel: string;
}
<a v-bind:href="url" v-bind:rel="rel">My link</a>

As you might have guessed, the transformation of the data contract into a model is done by ModelBinder, and model data flows into a view model through ViewModelBinder (which knows the specifics of UI framework).

Design-time, publish-time, run-time

In Paperbits there are three clearly distinguished scopes:

Design-time - This is the execution scope where components required for editing content are running.

Publish-time - The scope in which static website and resources are being generated.

Run-time - Kicks in when the published website is loaded into browser.

Components of each scope run independently and do not know of each other. They have separate DI containers, their own configurations, lifecycle, etc. They may even be powered by different UI frameworks.

In the demo project you can see respective files:

startup.[scope].ts - a script where the components get bootstrapped;

config.[scope].json - configuration for a scope;

webpack.[scope].js - webpack definition for building scoped components;