38

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

Christian Engel
  • 3,738
  • 5
  • 28
  • 44
  • 1
    Can you post some code to show what you're doing? – Jim D'Angelo Nov 26 '11 at 21:51
  • well, for example something like this: $('body').append('Click me!'); – Christian Engel Nov 26 '11 at 21:58
  • 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 Nov 27 '11 at 00:19
  • 8
    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 Nov 27 '11 at 00:28
  • Awesome! I forgot about the second parm for applyBindings(). – Jim D'Angelo Nov 27 '11 at 01:08
  • And if you're doing this with KO components, in which case you can't supply a viewModel as the first parameter to the function, just call `ko.applyBindings(null, myDomReference)` – Tobias Feil Mar 27 '20 at 09:48

3 Answers3

38

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?

Community
  • 1
  • 1
ColinE
  • 68,894
  • 15
  • 164
  • 232
  • 1
    Yes, thats the solution I came up with, too :) Sadly I havent found a direct API documentation on knockoutjs.com and had to do some reverse engineering on the ko object to find this out -.- – Christian Engel Nov 27 '11 at 08:34
  • 1
    True, there is no API documentation, but it is in the more 'wordy' documentation. Look half way down this page: http://knockoutjs.com/documentation/observables.html – ColinE Nov 28 '11 at 09:47
  • Are you sure that this is the answer? Wouldn't this apply viewModelA to the newElement as if newElement is the root of the DOM for viewModelA? I can see where it would work some or even most times but I can also see how it might be product unexpeced results (I think). – Ryan Pedersen Dec 22 '11 at 06:47
  • I just thought I'd comment here as I actually tripped on a gotcha. I was loading child views by ajax and sticking them into the view model which was bound to the view to place child (partial) views in the right spot on the master page. One should be **very** careful what elements they bind to. I was carelessly calling applyBindings to the element that held the partial view, which by all appearances was causing it to reload the partial view. I had to change the logic to select the children of that element, or in other words, the entire partial view. – lassombra Nov 17 '12 at 07:25
  • "As long as nodes don't share part of the tree (e.g. they're siblings) you can call applyBindings safely on each of the nodes (in fact, that's one reason to use the second argument)." – Brian Ogden May 09 '14 at 09:48
7

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.

SamStephens
  • 5,721
  • 6
  • 36
  • 44
  • While I certainly agree with all of this, I think there are times when it does make sense to leverage knockoutjs in a way that you end up with "nested" view models. This might be more along the lines of the situation that Christian was dealing with. In which case you should have a view model that is specifically set up for and bound to your nested HTML (presumably pulled in via ajax) when it is available/has finished loading. – Jed Jul 27 '12 at 06:46
  • For the record, I think this is the correct answer. The control should be bound at declaration, not driven from event behaviour. Knockout is one of those technologies where you need to keep the entire approach consistent to avoid annoying bugs to a minimum. – Bruce Chapman Aug 17 '12 at 03:43
  • 3
    What about particularly large sites where the view itself is changing? Should all possible views be packed into the default page? Surely not. Loading a view via ajax as you transition to other "areas" of the site is certainly an acceptable action (I personally am using this in a web application I'm converting. The original application is MVC4 and has different views for different personal management data I was tracking (stocks, other financials, todo list, contact list, event calendar). Since they all use the same master page, converting them to MVVM/knockout is pretty straight forward. – lassombra Nov 17 '12 at 07:15
  • That's a fair call, there is no "one true way". But if you're going to start direct DOM manipulation, this should be a deliberate decision, knowing that you're trading off the separation of concerns Knockout provides. As I say in my answer, "Without knowing what you're up to exactly". I've edited to reflect your comment though. – SamStephens Nov 18 '12 at 17:40
1

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Maximilian Lindsey
  • 809
  • 4
  • 18
  • 35
  • 2
    Well, for me... I abandoned KnockoutJS a long time ago since it was just too unflexible for my purposes. – Christian Engel Feb 06 '13 at 19:01
  • @ChristianEngel KO is hardly inflexible .. certain things need to be done the KO way (I have yet to find a need to rebind!) to be enjoyable, but [Durandal](http://durandaljs.com) (which uses KO) -> awesome flexibility with support for views - separate views effectively mitigate the need to manually try to partially bind as each view can introduce either introduce it's own model or share [part of] a model. Now, there are a few things about KO I don't like, but it's nothing to do with lack of flexibility! The observable model is *very* flexible once it's utilized fully. – user2246674 May 26 '13 at 22:29
  • @ChristianEngel The "hackish but easy" way to handle random (external) elements being injected into the DOM is to insert them into a *flow control* binding like `if` (this post shows `foreach` which is arguably cleaner to begin with) and then toggle the content "on" after the elements are inserted - the new subtree will just bind as appropriate. There is *no* extra manual `applyBindings` and an observable controls the flow-binding. Additional bindings like `withProperties` can be used to refine properties in scope. – user2246674 May 26 '13 at 22:35