Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connecting knockoutjs to an already populated DOM element

Tags:

knockout.js

I'm working on a page which is being server from an ASP.NET MVC3 app that I want to use KnockoutJS in.

On the View I'm getting passed a populated Model (which I'm converting to a KnockoutJS viewModel) and I want to build up some of the DOM server-side. This seems logical since I already have the objects there to generate the initial HTML and it'll provide a better experience to the users as there wont be a delay between the page load and the DOM population (it also means I have basic functionality for non-JavaScript clients).

After doing some research I'm assuming I need to create a custom bindingHandler so I created this:

ko.bindingHandlers.serverForEach = {
    init: function() { /* no-op */ },
    update: function() {
        //call off to the built in loop handler
    }
};

So my thinking was that I create a handler which does nothing in the init phase (as the DOM is already populated) and in the update phase I'll just insert the new value. I'd like to leverage the built-in templating so this can be a generic solution as well.

The problem is that everything falls apart in this scenario. If the init does nothing then the update fails as the bindingContext that comes in as the last argument seems wrong, and if I include the init it'll destroy the existing HTML.

Anyone tried this/ know what I should do to support this scenario or is it just too far removed from what you can/ should do?

Also, I don't want to have a separate DOM element for the server generated HTML than the Knockout HTML.

like image 816
Aaron Powell Avatar asked Sep 05 '11 06:09

Aaron Powell


2 Answers

I think I understand what your problem is. You are wanting to populate the markup initially for non-js users and simply overwrite it when binding to KO.

For simple bindings like text this is not an issue simply include the data-bind attribute in the markup from the server or include at runtime and away you go. For the template foreach binding things are a little more difficult since it will append values when bound.

Here is a jsfiddle that describes the above scenario and demonstrates the solution below. It's a little basic but should get you started.

The basic idea is to clear the element on init and then delegate the rest to the existing template binding.

ko.bindingHandlers.serverForEach = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // clear the list first
        $(element).children().remove();
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        ko.bindingHandlers['template'].update(element, valueAccessor, allBindingsAccessor, viewModel);
    }
};

Hope this helps.

like image 131
madcapnmckay Avatar answered Oct 16 '22 19:10

madcapnmckay


In case anyone needs this to work for knockout.js 2.0+ (note the bindingContext in update)

ko.bindingHandlers.serverForEach = {
init: function(element, valueAccessor) {
        $(element).children().remove();
        return ko.bindingHandlers.template.init(element, valueAccessor);
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        return ko.bindingHandlers.template.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
    }
};
like image 43
Jason J. Nathan Avatar answered Oct 16 '22 18:10

Jason J. Nathan