Templated Components with Knockout Continued

As promised in my last post, I am going to explain today how to write more complex templated components. I have already mentioned our dilemma with table components and that we wanted to use templates to solve it. When creating a table component, we want to be able to pass markup for the table header as well as for the table rows. So we need a way to define different parts of the template and to decide which parts to insert at which places into the component.

Defining Different Parts of the Template

For passing multiple parts of template into the component, we decided to place each part of the template in its own template tag. In order to distinguish between the roles of the different template parts, we use the 'data-template-function' attribute. In this example, 'data-template-function' can have the values 'table-header' or 'table-row'. So this is the markup that is passed to the templated-table component:
[crayon-5d7ddac22e672150612614/]

Accessing the Template in the Viewmodel

In the templated list example from the last blog post, the template could be injected into the component directly by using $componentTemplateNodes. This won't work here because the template consists of multiple parts which we wish to use in different places within the component. So the template needs to be processed by the component's viewmodel first.

The component's viewmodel can access the template via componentInfo.templateNodes, an array which contains all the DOM nodes of the template (as a reminder: componentInfo is passed to the component's createViewModel function). The viewmodel should provide a parameter of the type HTMLElement[] for each template view element (important, it needs to be an array!). In this example, we use the variables tableHeader and tableRow. With the help of our data-template-function attribute, we can decide which value to assign to which variable:
[crayon-5d7ddac22e676842425036/]

Inserting the Template into the View

Once set in the viewmodel, the nodes can be inserted into the html at the correct place:
[crayon-5d7ddac22e677870180164/]

Templated Components are Awesome!

We have been using templated components for some time now. Every time I reuse one of them, I am very happy to see how little effort is needed to integrate it into the code. Templated components have really save us a lot of time and lines of code!


Templated Components with Knockout

Templated Components

Some time ago I wrote about our best practices for creating components. Even though components have been a great relieve to us when developing new features, there have been limitations as to their flexibility and reusability. For example, when using a component for different tables, each of the tables needed to have more or less the same layout and the same number of columns. To a certain degree, we could adapt the tables with the help of boolean variables, but this did not exactly increase the readability and quality of our code. So we ended up creating a whole range of different table components, even though their basic logic was the same: they all needed to do sorting, scrolling, filtering, etc.

This was when we started to investigate and found out that Knockout now allows passing markup into components. Thus we can have the same logic for all tables but use completely different layouts for them.

Example: Templated List

Here is a very simple example of a templated lists. For passing template into the component, any html code can be placed inside the component's custom html element:
[crayon-5d7ddac22e9ec536862623/]
The component can inject any template into its html by using the knockout template binding. The parameter 'nodes' specifies an array of DOM nodes that are to be used as a template, the 'data' parameter supplies the data for the template to render.

The html we placed between the component tags can be accessed via $componentTemplateNodes:
[crayon-5d7ddac22e9ef933107492/]
The resulting list looks like this:

Patients

  • Name: John Smith, birth date: 23.09.1952
  • Name: Jenny Smith, birth date: 14.02.1963

Since we do not only want to use the component for patients, but also for orders, we use the same component again in a different view, and pass different template this time:
[crayon-5d7ddac22e9f0775495097/]
So we get a completely different list:

Orders

  • Name: John Smith, address: 111 Kirby Corner Rd, Coventry CV4 8GL, status: open
  • Name: Jenny Smith, address: Waterside, Stratford-upon-Avon CV37 7LS, status: delivered

Having understood the principle of how templated components work, we can now move on to creating more complex templated components that inject different parts of template into different places in their view. I will explain in one of my next blog post how this can be done.


Best Practices for Creating Knockout Components

With the release of version 3.2.0, knockout introduced Components, which are a clean and powerful way of organizing and structuring code. We have increasingly used components in our projects since then and enhanced our understanding about what is a good component and what is not. In this blog post, I will present our best practices for creating components.

Components - general principles

The idea behind components is to create self-contained chunks of code that fulfill two main purposes:

  • reusing pieces of code
  • simplifying code by breaking it into smaller pieces which each encapsulate a certain functionality; this makes it easier to maintain and understand the code

In order to be self-contained and thus easily reusable, components must be only loosely coupled to the part of the application they are embedded in.

Registration

Knockout components combine a viewmodel and a template. In order to start using a component, it must be registered with the ko.components.register function. The function specifies the viewmodel and template, both of which should be loaded from external files and not hardcoded into the registration.

Of all the different ways of specifying the viewmodel, we use the createViewModel factory function. It is called with the parameters params and componentInfo, where params is an object whose key/value pairs are the parameters passed from the component binding or custom element, and componentInfo.element is the element into which the component is being injected. By passing both params and componentInfo into the constructor, we ensure that both are accessible in the viewmodel:
[crayon-5d7ddac22eb34678146859/]

View

For injecting a component into a view, we use it as a custom element. We prefer this over the knockout component binding, which binds the components to regular div elements, because it is much more elegant and straightforward:
[crayon-5d7ddac22eb37953538921/]
To supply parameters to the component, we provide a key-value object as params attribute. The properties of this key-value object are defined in the containing viewmodel and will be received by the component's viewmodel constructor.

The component's view should have as its outer element a distinctive div container with the component name as its class name:
[crayon-5d7ddac22eb38532000166/]

[crayon-5d7ddac22eb39447101890/]
 
[crayon-5d7ddac22eb3a289140687/]
This makes it easier to address the component and to provide component-specific css.

Since we want the components to be reusable, the view must not contain any ids. Having ids in the component would make it impossible to use a component multiple times within one page.

Params

The properties which are passed over as params into the component are the only means of communication between the component and the containing viewmodel. In order to maintain a loose coupling, we must never pass the complete parent viewmodel into the component. This would make the component very hard to reuse and the viewmodel very hard to change.
Instead, only those properties and functions should be sent to the component that are really necessary for displaying and manipulating the component's view elements.

For better keeping track of what the properties are meant to be used for, we have established the following naming convention: if the component needs to call a function of its containing view model, the parameter for this function should have the word 'callback' in its name (such as 'cancelCallback'). If, on the other hand, the containing viewmodel needs to call a function from the component, the function's name should contain 'ComponentAction', such as "findAddressComponentAction".

Since a component should solely be concerned with itself and not with the container in which it is embedded, we should never pass over a view element from outside the component. Moreover, trying to access a component via their containing elements is a dangerous thing to do especially if a component is used more than once within the container. Instead, a component can be accessed unambiguously via the componentInfo which is provided by knockout's createViewModel function and should be passed into the constructor if the viewmodel needs to access it.

Viewmodel

A component should act as a black box which takes care of all the functionality that is expected from it. Therefore it should implement all the component-related logic. For example, if we create a table component, the viewmodel must provide the functionalities for searching, sorting, scrolling, etc., rather than the containing viewmodel.

Besides, the viewmodel class should always have a dispose function in which any resources are released that are not collected by the garbage collector, such as subscriptions, event handlers or ko.computed properties (if they are not pure computed properties). The viewmodel should have this dispose function even if the function is empty, as a reminder that we do need to remember to clean up when we extend the component later. The dispose function is called every time just before the container element is removed from the DOM.


How to use knockout-bindings to set style of DOM-element

Knockout.js is a very powerful MVVM framework to do some web-application.
I want to explain a trick how you can bind a style of DOM-element in runtime.
The simplest way to use a css-binding is:

<label data-bind="css: 'red'">

so your label will be defined by class ‘red’:

<label class="red">

Another way goes through the binding parameter 'attr'. You can set all attributes of DOM-element during this parameter directly, but if you want to set the attribute 'class' you have to use the name 'class' as follows:

<label data-bind="attr: {class: 'red'}">

The binding during 'css' attribute adds a class name to an existing list of classes.

<label class="warning" data-bind="css: {red: isDangerous}">

The class 'red' will be appended to the class 'warning' if the function isDangerous() returns true.
Unlike that, the binding during the parameter 'class' overwrites the attribute ‘class’ of an element completely.
Now, imagine the following problem: you will show a collection each element of which has a common class 'nice'. A loop through the collection will be looked as next example:

<!-- ko: foreach myCollection -->
<label class="nice" data-bind="text: someText"></label>
<!-- /ko -->

Then you want to enable or disable some rows by function isEnabled(). It’s quite easy too:

<label class="nice" data-bind="text: someText, css: {enabled: isEnabled}"></label>

At finally you want set some unique row’s style based on row’s position. You can use something like ‘row’+$index() :

<label class="nice" data-bind="text: someText, css: {enabled: isEnabled, 'row'+$index(): 1}"></label>

But it doesn’t work because Knockout doesn’t understand dynamic names of related parameters!
In such case the parameter 'class' helps well. We know that it overwrites the list of classes of DOM-element, because of this we move the description of classes from DOM-element in Knockout binding:

<label data-bind="text: someText, css: {enabled: isEnabled}, attr: {class: 'nice row'+$index()}"></label>

It works and seems good, but you’ll notice soon the class 'enabled' will be never set. Why? Because the parameter 'attr' will overwrite it.
The solution is very simple: the attribute 'class' must always be placed before the parameter 'css'!

<label data-bind="text: someText, attr: {class: 'nice row'+$index()}, css: {enabled: isEnabled}"></label>

Now it works very well.