37
votes

when I inject any new elements into the DOM after ko.applyBindings(); was called, then knockout won't recognize these new elements. I can understand why this is happening - they are just not indexed by knockout.

So, at first I thought this would be solved by just calling ko.applyBindings() again, after adding my new elements, BUT then I realized that for every ko.applyBindings() call you make, the according events get fired multiple times. So after applying five times, a click: binding will be fired five times, so this is not a desireable solution ;)

Is there anything like ko.updateBindings() or something else, to tell knockout to, well... update the element bindings?

greetings, Chris

3
Can you post some code to show what you're doing? - Jim D'Angelo
well, for example something like this: $('body').append('<a href="#" data-bind="click: something">Click me!</a>'); - Christian Engel
I am not sure that is enough context to provide a helpful answer. I understand what you are trying to do, but with a fuller picture of your code (why/when you are injecting new DOM elements) it's hard to answer with the best way to take care of this. It could be, after looking at your current solution, that somebody can point out a way to not have to inject the new elements or has a workaround for what you're trying to do. - Jim D'Angelo
It seems like I have found it. Just calling: ko.applyBindings(viewModel); again binds all functions again to the whole DOM. This is the reason, every callback is called twice and more after re-calling this function. The second parameter for applyBindings() is a DOM node to start applying from. This defaults to the DOM root. So, if you are adding a couple of elements with data-bind attributes "manually" to some DOM node, grab a reference to the DOM node with the new elements and pass it as second parameter! - Christian Engel
Awesome! I forgot about the second parm for applyBindings(). - Jim D'Angelo

3 Answers

38
votes

Each time you invoke ko.applyBindings the entire DOM is inspected for bindings. As a result you will get multiple bindings for each element if you do this more than once. If you just want to bind a new DOM element you can pass this element as a parameter to the applyBindings function:

ko.applyBindings(viewModelA, document.getElementById("newElement"));

See this related question:

Can you call ko.applyBindings to bind a partial view?

7
votes

Without knowing what you're up to exactly, it seems like you're going the wrong way about this. Your view should be driven by your view model. So you shouldn't be directly adding DOM elements you then need to apply knockout bindings to.

Instead you should be updating your view model to reflect the change in the view, which then causes your new element to appear.

So for example, for your $('body').append('<a href="#" data-bind="click: something">Click me!</a>');, rather than adding the DOM element when the button should be visible, control the button visibility using the view model.

So your view model includes

var viewModel = { clickMeAvailable: ko.observable(false) }

And your HTML includes

<a href="#" data-bind="click: something, visible: clickMeAvailable">Click me!</a>

When the application state changes so the click me button is available, you then just viewModel.clickMeAvailable(true).

The point of doing this, and a big part of knockout, is to separate business logic from presentation. So the code that makes click me available doesn't care that click me involves a button. All it does is update viewModel.clickMeAvailable when click me is available.

For example, say click me is a save button that should be available when a form is filled in validly. You'd tie the save button visibility to a formValid view model observable.

But then you decide to change things so after the form is valid, a legal agreement appears which has to be consented to before saving. The logic of your form doesn't change - it still sets formValid when the form is valid. You would just change what occurs when formValid changes.

As lassombra points out in the comments on this answer, there are cases when direct DOM manipulation may be your best approach - for example a complex dynamic page where you only want to hydrate parts of the view as they are needed. But you are giving up some of the separation of concerns Knockout provides by doing this. Be mindful if you are considering making this trade-off.

1
votes

I just stumbled upon a similar problem. I tried to add new elements to container and give those a onclick function.

At first tried the things you did, and even tried the approach ColinE recommended. This wasn't a practical solution for me so I tried SamStephens approach and came up with that, which works perfectly for me:

HTML:

<div id="workspace" data-bind="foreach:nodeArr, click:addNode">
<div class="node" data-bind="attr:{id:nodeID},style:{left:nodeX,top:nodeY},text:nodeID, click:$parent.changeColor"></div>
</div>

JavaScript:

<script>
function ViewModel() {
var self = this;
var id = 0;
self.nodeArr = ko.observableArray();
self.addNode = function (data, event) {
    self.nodeArr.push({
        'nodeID': 'node' + id,
        'nodeX' : (event.offsetX - 25) + 'px',
        'nodeY' : (event.offsetY - 10) + 'px'
    })
    id++;
}
self.changeColor = function(data, event){
    event.stopPropagation();
    event.target.style.color = 'green';
    event.target.style.backgroundColor = 'white';
}
}
ko.applyBindings(new ViewModel());
</script>

You can play with it in the JS Fiddle I made.