Question

I am trying to use knockout to create a html editor/previewer. I set up a simple test with a single observable as follows:

JS:

var ViewModel = function() {
    this.content = ko.observable("<div data-bind=\"text: 'testext'\"></div>");
};

ko.bindingHandlers.bindHTML = {
    'init': function () {
    },
    'update': function (element, valueAccessor) {                         
        ko.utils.setHtml(element, valueAccessor());
    }
}

ko.applyBindings(new ViewModel());

HTML:

<input type="text" data-bind="value: content">

This seems to work fine when the page first loads, displaying a div with 'testext' in it, however as soon as I edit the input field to something like <div data-bind=\"text: 'testext2'\"></div> the binding doesn't work!

Is this a limitation of knockout, or is there something I'm doing wrong? Is there a way of preforming a rebind?

A JSFiddle is available here: http://jsfiddle.net/Q9LAA/

Was it helpful?

Solution

There is an html binding that can insert html for you automatically (I would prefer this over using setHtml), but it doesn't evaluate the bindings on the inserted html, you have to reapply the bindings on the generated html (and that is where you would need a custom binding).

ko.bindingHandlers.bindHTML = {
    init: function () {
        // we will handle the bindings of any descendants
        return { controlsDescendantBindings: true };
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        // must read the value so it will update on changes to the value
        var value = ko.utils.unwrapObservable(valueAccessor());
        // create the child html using the value
        ko.applyBindingsToNode(element, { html: value });
        // apply bindings on the created html
        ko.applyBindingsToDescendants(bindingContext, element);
    }
};

Here's an updated fiddle demonstrating its use.

OTHER TIPS

If I understand correctly, you want to use something like the Knockout html binding, but the HTML you try to bind contains bindings to. The thing is that your HTML get loaded, but Knockout doesn't automatically perform applyBindings() to the changed HTML.

So what you want is :

ko.bindingHandlers.bindHTML = {
    init: function () {
        // Prevent binding on the dynamically-injected HTML (as developers are unlikely to expect that, and it has security implications)
        return { 'controlsDescendantBindings': true };
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // setHtml will unwrap the value if needed
        ko.utils.setHtml(element, valueAccessor());

        var elementsToAdd = element.children;

        for (var i = 0; i < elementsToAdd.length; i++) {
            ko.cleanNode(elementsToAdd[i]); //Clean node from Knockout bindings
            ko.applyBindings(bindingContext, elementsToAdd[i]);
        }
    }
};

The reason I use a for loop for iterating through the html elements you want to append is because let's say you want to append using bindHTML binding :

<input type="text" data-bind="value: 'watermelon'" />
<input type="text" data-bind="value: 'orange'" />

Using the for loop, every input element will be correctly binded.

Here is your updated and working JSFiddle

NOTE: Do not do what I show below.

You could be tempted to perform ko.applyBindings() on the element having the bindHTML binding, to automatically bind all its children. This is a very bad idea!

If you do that, you will enter in an infinite loop, as you will ask knockout to execute the bindHTML again, and again, and again. That's an infinite recursive loop!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top