0
votes

I have an application that builds a small page upon load, which presents the user with navigation options. When the user selects a navigation option, the page creates an object representing the ViewModel of the selected navigation option, then gets the corresponding HTML from the server and places it inside of a div.

My expectation is that I could get the HTML, use a Knockout HTML binding to put it into the div (wrapped in a container div with a preselected ID), then bind the container div to the ViewModel.

However, I get errors when I try to do this. This seems very much in line with what Knockout is supposed to do, so I'm a little puzzled about what I'm doing wrong. Any help would be much appreciated.

I have created a fiddle to encapsulate the problem (clicking the links does nothing):

http://jsfiddle.net/Tgh44

Here's the HTML:

<body>
    <div id="container">
        <div id="menu">
            <ul data-bind="foreach: modules">
                <li><a href="#" data-bind="click: $root.goTo.bind($data), text: $data"></a></li>
            </ul>
        </div>
        <div id="body" data-bind="html: curBody">

        </div>
    </div>
</body>

And the JavaScript:

function GlobalVM() {
    var self = this;

    self.curVM = ko.observable();
    self.test = ko.observable('test');

    self.curBody = ko.observable('body');    
    self.modules = ko.observableArray();

    self.goTo = function(module) {
        if (module == 'Test1') {
            self.curVM(new SubVM1());

            self.curBody('<div id=\'realbody\' data-bind=\'text: curVM().test1\'></div>');
            ko.applyBindings(self, document.getElementById('realbody'));
        }

        if (module == 'Test2') {
            self.curVM(new SubVM2());

            self.curBody('<div id=\'realbody\' data-bind=\'text: curVM().test2\'></div>');
            ko.applyBindings(self, document.getElementById('realbody'));
        }
    };

    self.initialize = function() {
        self.modules.push('Test1');
        self.modules.push('Test2');
    };
}

function SubVM1() {
    var self = this;

    var test1 = ko.observable('test1');
}

function SubVM2() {
    var self = this;

    var test2 = ko.observable('test2');
}

var vm = new GlobalVM();
vm.initialize();
ko.applyBindings(vm);
1
Look into the template binding and using an observable to store the template name. That will let Knockout handle the bindings automatically. - ebohlman
I have messed with templates, but I didn't like needing to embed them all in my layout view. Is it possible to dynamically retrieve the template when I need it, so the initial page doesn't come with a bunch of template code, most of which is not immediately needed, which would presumably make the page load slower? - SuperNES
(Oh, and thank you--I had kinda forgotten about templates, but it's possible I gave up on them too soon.) - SuperNES
You might want to look at the <a href="github.com/ifandelse/… Template Engine</a> plugin. - ebohlman

1 Answers

0
votes

Using templates would probably be the best way to go for this. Here is an updated fiddle demonstrating this.

I added an observable that tracks the current "page"

self.currentPage = ko.observable('Home');

then in your goTo method, I changed it to be this

self.goTo = function(module) {
    self.currentPage(module);
};

and added the templates to the html.

Edit

I just saw your comments on your original post. Yes, you can load templates dynamically. You can load a template by dynamically adding that template to the DOM. Example:

$.get(templateurl, function(data){
    $('body').append('<div style="display:none">' + data + '<\/div>');
});

If you have a number of templates (or any, really), it's cleaner and easier to work with if you keep the templates in separate files - separated from the HTML of your primary view. You could then load them all into your view via ajax. However, loading in templates AFTER you have called ko.applyBindings won't work if you apply the bindings to the entire page. Some options are calling ko.applyBindings on individual DOM elements or using ko.renderTemplate - see this SO post on lazy loading KO templates for some great ideas.