Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

knockout.js: update bindings?

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

like image 362
Christian Engel Avatar asked Nov 26 '11 21:11

Christian Engel


3 Answers

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?

like image 185
ColinE Avatar answered Nov 18 '22 21:11

ColinE


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.

like image 33
SamStephens Avatar answered Nov 18 '22 21:11

SamStephens


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.

like image 36
Maximilian Lindsey Avatar answered Nov 18 '22 21:11

Maximilian Lindsey