Knockout.js 1.3 External Templates

{Cross posted on FreshBrewedCode}

Finally! I updated my Knockout.js-External-Templates plugin to support the new template architecture in Knockout.js 1.3. “So,” you ask, “why would I want to use this?” If you’re a developer using Knockout.js, perhaps you’ve run into the inevitable code bloat as your templates multiply and begin to crowd your document? If you’ve been using jQuery templates with Knockout.js, perhaps you’ve not only grown tired of keeping templates in SCRIPT elements, but you’ve also wanted to take advantage of real markup syntax highlighting in your favorite IDE (which should be WebStorm, by the way) – but alas, markup inside a SCRIPT element just can’t do that. Fret no more! This plugin will enable you to:

  • Separate your concerns by keeping templates in separate files, OR in the document, OR both (mix and match to your heart’s content).
  • Take advantage of syntax highlighting by storing your native or jQuery templates in their own HTML file.
  • Lazy load templates only as they are needed by your application.

Enough preamble already, let’s look at an example.

Here we have a simple page that displays a list of states.  Each state has a list of cities associated with it, and each city has an image and an accompanying list of statistics:

KoExternalTemplateExample App

Here’s a look at the JavaScript view model that contains the data bound to the view(s) on the page:

var viewModel = {
    states: [
        new State("Tennessee", "Southeast", [
            new City("Nashville", [
                new Statistic("Population", "749,935"),
                new Statistic("Mayor" ,"Karl Dean")
            ]),
            new City("Franklin", [
                new Statistic("Population", "62,487"),
                new Statistic("Mayor" ,"Ken Moore")
            ]),
            new City("Brentwood", [
                new Statistic("Population", "37,060"),
                new Statistic("Mayor" ,"Paul Webb")
            ]),
            new City("Murfreesboro", [
                new Statistic("Population", "108,755"),
                new Statistic("Mayor" ,"Tommy Bragg")
            ])
        ]),
        new State("Georgia", "Southeast", [
            new City("Atlanta", [
                new Statistic("Population", "3,500,000"),
                new Statistic("Mayor" ,"Kasim Reed")
            ]),
            new City("Snellville", [
                new Statistic("Population", "18,242"),
                new Statistic("Mayor" ,"Jerry Oberholtzer")
            ])
        ]),
        new State("Ohio", "Mid-West", [
            new City("Columbus", [
                new Statistic("Population", "1,100,000"),
                new Statistic("Mayor" ,"Michael B. Coleman")
            ])
        ]),
    ]
};

To drive this page, we have a state template, a city template and a statistics template, all of which are stored separately from the index.html page, here’s a screen capture of the project folder hierarchy:

File Structure

As you can see from above, the templates reside in the “templates” folder – relative to the index.html document.  At the top of the main.js file, we tell infuser where to go and look for templates with this line: infuser.config.templateUrl = “templates”;.  You can easily change the template look-up location by altering the templateUrl.

 

The state.html template:

<li class="state-container">
    <h3 data-bind="text: name"></h3>
    <div>
        <ul data-bind="template: { name: 'city', foreach: cities }"></ul>
    </div>
</li>

The city.html template:

<li class="city-container">
    <div>
        <img class="city-img" data-bind="attr: { src: img, alt: name }">
        <div class="city-facts">
            <em data-bind="text:name"></em>
            <div data-bind="template: { name: 'stats'}"></div>
        </div>
        <span style="clear:both;"></span>
    </div>
</li>

The stats.html template:

<ul data-bind="foreach: stats">
    <li class="stat-container" data-bind="ifnot: editing">
        <div class="stat stat-name" data-bind="text:name"></div>
        <div class="stat stat-value" data-bind="text:value, click: toggleEdit "></div>
    </li>

    <li class="stat-container" data-bind="if: editing">
        <span class="stat stat-name" data-bind="text:name"></span>
        <input class="stat stat-value"type="text" data-bind="value: value"><input type="button" value="save" data-bind="click: toggleEdit">
    </li>
</ul>

The main index.html file is very lean, holding only the script & css includes, plus the initial placeholder for the starting template:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>koExternalTemplateEngine Example</title>
    <link rel="stylesheet" href="css/style.css">
    <script type="text/javascript" src="js/jquery-1.5.2.js"></script>
    <script type="text/javascript" src="js/knockout-latest.debug.js"></script>
    <script type="text/javascript" src="js/koExternalTemplateEngine_all.js"></script>
    <script type="text/javascript" src="js/main.js"></script>
</head>
<body>
    <ul class="state-list" data-bind="template: { name: 'state', foreach: states }"></ul>
</body>
</html>

When you call ko.applyBindings(viewModel), Knockout will parse the template binding on line 13 (UL element) in the index.html file and ask the template engine to get the template content for the “state” template. The KoExternalTemplateEngine will check the DOM first (if you included the template in the page), and if it doesn’t find it locally, it tells infuser to pull the template down from the external endpoint you configured via the infuser.config options. (It’s worth noting that the KoExternalTemplateEngine will handle anonymous templates just like the native engine if the template being retrieved isn’t a DOM template or an external one.) As the “state” template is evaluated, the same steps will occur when Knockout asks for the “city” template, and again when it comes across the template binding to the “stats” template.

Although this is a very simple example, I’ve included a click binding that will swap the “view” template for a statistic out with an “edit” version if you click on one of the statistic values. This is to demonstrate that all the normal “native KO template” functionality behaves as you would expect.

KoExternalTemplateEngine Example App 2

The KoExternalTemplateEngine also supports jQuery templates (special thanks to Ryan Niemeyer for fixing the last two bugs I had with jquery-tmpl support). Since it takes a dependency on infuser, you may want to look at the configuration options available in infuser (it will give you an idea of how you can control where templates are pulled from, etc.).

The sample app from this blog post is included as part of the KoExternalTemplateEngine repository on github (it’s in the example/native2 folder).

 

Tags: , , ,

  • Salvatore DI DiO..

    THank you very much,
    I have implemented such an idea, by loading html fragments with ajax.load
    and insert them in dynamically created div
    I must admit tour extension surpass my work by far :-)

    Salvatore DI DIO aka ArtyProg

    • http://ifandelse.com Jim Cowart

      Thanks – I appreciate the encouragement! We’re both trying to solve a problem consistently faced by web developers – I just got tired of doing one-off solutions every time so I made it a framework. :-)

  • thohan

    Very useful. I have a backend page using knockout and the html is pretty manageable so far, but if it continues to grow, I’ll certainly need to implement something like this.