Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

knockout.js - lazy loading of templates

Tags:

knockout.js

So I come from a templating workflow that involves creating a data object (akin to a view model in knockout) passing that to a templating engine (jstemplate in our case), rendering the template using that data object, and appending it to the dom.

How do I achieve a similar work flow with knockout? Is the "if" control flow what I'm looking for? Or sticking my templates in script tags without data-bind attributes and adding them dynamically later and processing the template like ko.applyBindings(viewModel, node)?

I'm curious how others lazy load templates using knockout.

Also, extra credit if you can tell me why the js fiddle below doesn't work as I would expect it to. I'm trying to learn the if control flow binding and this doesn't work.

http://jsfiddle.net/JJgJ7/1/

like image 714
Greg Avatar asked Feb 23 '12 23:02

Greg


2 Answers

There are several directions that you can go for something like this:

you can apply different view models to different elements, as you mentioned like:

var viewModelOne = { ...  };
var viewModelTwo = { ...  };
ko.applyBindings(viewModelOne, containerElementOne);
ko.applyBindings(viewModelOne, containerElementOne);

You can even dynamically apply a binding with its data to an element like:

var viewModelOne = { ... };
ko.applyBindingsToNode(containerElement, { template: { name: 'itemTemplate', foreach: items }, viewModelOne);

Would be like this sample: http://jsfiddle.net/rniemeyer/gYk6f/

You can also do something like:

var mainViewModel = {
   sideBarModel = ko.observable(),
   contentModel = ko.observable()
};

Then, bind it like:

<div data-bind="with: contentModel"></div>
<div data-bind="with: sideBarModel"></div>

They, can even be nested like:

<div data-bind="with: contentModel">
   ...
   <div data-bind="with: $root.sideBarModel"></div>
</div>

Since, the models are observable, they can initially be empty and get populated on demand.

You can certainly use named templates in those cases as well like:

<div data-bind="template: { name: "contentTmpl", data: contentModel }"></div>
<div data-bind="template: { name: "sideBarTmpl", data: sideBarModel }"></div>

For the Extra Credit question:

p tags cannot contain other block level elements (like your div). The browser moves it out of the p tag. Replace your div with a span and it will behave like you are expecting (or replace p with div).

like image 75
RP Niemeyer Avatar answered Oct 23 '22 04:10

RP Niemeyer


I know the answer is still old by I guess you'd be interested to look at this. This is my pattern. One might say that I'm abusing the concept of knockoutjs but here it is... it works!

First you need to add an empty template, this is needed for knockout to load at least a template. If you provide something invalid, knockoutjs will sadly fail to work and abort what it was doing...

Let say you have this.

<script type="text/html" id="template-emtpy">

</script>

Cool now lets take a look at how templating works in knockout, the knockout template is just a binding actually so it is checked at runtime and all the fancy thing you can do before works. And that mean that you can have things like that.

<div data-bind="template: { name: activeTemplate, data: contentModel }"></div>

where activeTemplate could be an observable object! Oh so observables do works there... when the observable object changes, then the template is notified and will infact rerender with the correct template.

Now that you understand that we can actually set a template and change it in the future, you understand that until our template is loaded we will send knockout the empty template. There won't be any binding error because there is nothing to bind in nothing!

Now lets take a look at this!

<div data-bind="template: { name: getTemplate('reports'), data: contentModel }"></div>

Where getTemplates is defined as:

window.getTemplate = function (name) {
    var baseUrl = "/templates/";

    var loaded = ko.observable(false);

    var template = document.getElementById('template-' + name);

    if (template) {
        loaded(true);
    } else {
        jQuery.get(baseUrl + name + '.html', function (data) {
            var scriptTag = $('<script type="text/html" id="template-' + name + '"></script>');
            scriptTag.html(data);
            $('head').append(scriptTag);
            loaded(true);
        });
    }

    return ko.computed(function () {
        if (loaded()) {
            return 'template-' + name;
        } else {
            return "template-empty";
        }
    })();
};

To sum it up, we are creating an observable loaded defaulted to false. If we find the template, we change the value to true. If not, we will try to load it with jQuery (load it with however you like... you could definitely put a text area and load the template from there. Anything is possible...).

We return a computed value that depends on the loaded observable. When loaded becomes true, it will return the id of the loaded template. For some reasons, knockoutjs requires the text to be inside the script html tag. That's why I update the script tag with html.

So that's it. Also, I might be wrong and I'm not sure how does knockout handle templates but it could be possible to switch loaded on and off to reload templates... And that means that you can have dynamically loaded and editable template awesomness!!!!

Also notice that I do execute the computed value just before returning it. And notice that the function is a global... To fix that! Create your own binding to make something like that:

<div data-bind="lazy_template: { name: "reports", data: contentModel }"></div>

And then you're good to go. All you have to do is to put the loading logic inside a custom binding.

I'll create a project on github with everything working I wish it will help. I'll update later with the url.

Might not be stable because I hacked it in half an hour but it works for me. I'll probably have to write some tests one day but anyway. Have fun! https://github.com/llacroix/knockout-lazy-template

like image 44
Loïc Faure-Lacroix Avatar answered Oct 23 '22 05:10

Loïc Faure-Lacroix