knockout.js: update bindings?

2019-01-10 20:39发布

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条回答
疯言疯语
2楼-- · 2019-01-10 20:51

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?

查看更多
相关推荐>>
3楼-- · 2019-01-10 20:57

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.

查看更多
虎瘦雄心在
4楼-- · 2019-01-10 21:13

I know you asked a long time ago but 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 workes 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. Hope this still helps someone.

查看更多
登录 后发表回答