Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

KO 1.3 Preview Part 3: Template Sources

| Comments

Update: Knockout 1.3 was eventually renamed to version 2.0

Knockout 1.3 contains a number of enhancements that allow developers to extend the default capabilities of the library. Besides custom binding providers, you can now also create objects that define how to get and set a template’s contents based on the template name. These objects are called templateSources.

Knockout’s default templateSources

Knockout includes two templateSources that are used by default. The first templateSource understands how to retrieve a template from a DOM element using the template name as the id of the element. Typically, these are the templates that live in script tags. The other deals with anonymous templates, where the children of the element are used as the template. Anonymous templates are used when a name is not provided to the template binding and is most commonly used with the new control-flow bindings.

What does a templateSource look like?

A templateSource needs to provide a text and a data function. Similar to an observable, the text function when given no arguments should return the text of the template. When passed a single parameter, the function should write that value to wherever the template is being stored.

The data function is used to associate additional metadata related to the template and should accept key and value parameters. If only passed the key parameter, it should return the value. If passed both arguments, then it should use the key to write the value to an appropriate location. This meta-data is currently used for things like indicating that a template has been rewritten and to set/retrieve a precompiled version of a jQuery template.

A quick note about rewriting templates

When using a third-party engine, Knockout needs to take special care to ensure that the proper scope is available when applying bindings. You might want to use variables that are exposed by the template engine in your data-bind attributes or you may be looping over arrays using the template engine’s syntax and adding bindings on elements inside the loop. These are cases where Knockout can rewrite the template to allow bindings to execute under the proper context, as long as the template engine knows how to create a block to execute arbitrary JavaScript. Note that the new native template engine does not require this rewriting.

Writing a quick custom templateSource

Suppose that we want to keep our templates in strings. Maybe we want to load them from the server or even generate them on-the-fly. For starters, we can maintain an object that stores our template text by key like:

1
2
3
4
templates: {
    viewTemplate: "<li data-bind='text: name'></li>",
    editTemplate: "<li><input data-bind='value: name' /></li>"
}

To create our templateSource, let’s first write a constructor function that takes in the name of our template and the object that holds our templates.

1
2
3
4
ko.templateSources.stringTemplate = function(template, templates) {
    this.templateName = template;
    this.templates = templates;
};

Next our templateSource needs to provide a text method. When passed no parameters, we need to return our template from the template object. When passed a value (in the case of a rewritten template), we need to set the value on the template object. This method can be as simple as:

1
2
3
4
5
6
text: function(value) {
     if (arguments.length === 0) {
        return this.templates[this.templateName];
     }
     this.templates[this.templateName] = value;
}

Now we need a data method for storing any meta-data about the template. We could have our template object store text and data properties for each key, but to keep the template definitions simple, I am going to tuck the meta-data into a _data key on the template object.

1
2
3
4
5
6
7
8
9
10
data: function(key, value) {
    this.templates._data = this.templates._data || {};
    this.templates._data[this.templateName] = this.templates._data[this.templateName] || {};

    if (arguments.length === 1) {
        return this.templates._data[this.templateName][key];
    }

    this.templates._data[this.templateName][key] = value;
}

This method just ensures that the proper objects have been created and then does a get or set using the key.

There is one last step that we need to perform to take advantage of our new type of templateSource. We need to override the makeTemplateSource function of a template engine. To be flexible, we can create a function that takes in an existing template engine and the location of our string templates.

1
2
3
4
5
6
function createStringTemplateEngine(templateEngine, templates) {
    templateEngine.makeTemplateSource = function(template) {
        return new ko.templateSources.stringTemplate(template, templates);
    }
    return templateEngine;
}

Finally, we need to tell Knockout to use an appropriate template engine.

1
ko.setTemplateEngine(createStringTemplateEngine(new ko.nativeTemplateEngine(), viewModel.templates));

Here is a sample using this technique:

Link to sample on jsFiddle.net

You could even extend this concept to create a template source that loads its contents from an external source. Here is a sample that uses an observable to store the template contents, which allows the use of a loading template while the real template is being retrieved:

Link to sample on jsFiddle.net

Knockout 1.3 definitely has some great new extensibility features. While the default templateSources should be sufficient for most applications, it is nice to know that there is some flexibility built in to deal with templates in other ways.

Comments