Dellirium
Dellirium

Reputation: 1516

Knockout.js dynamicly generated HTML not getting updates

I have a rather complex system working in the background, will try to give the briefest and shortest example:

I have a single PHP/HTML page which fires an Ajax call to the server and gets a response XML back. Based on the XML the javascript dynamically creates a bunch of DOM elements. The XML also contains data which I map to a view model of the knockout that is present on the main page.

However elements are not getting updated, or rather the knockout.js is not interacting with them at all (and I will assume that ko.applyBinding(model) is to blame.

Here is what I need: 1. load a single PHP page that holds the knockout.js and a view model object. 2. send an AJAX request which returns the new Data to update the model with as well as new HTML DOM elements that get added into the page. 3. have knockout.js keep track of the data information in these new elements.

The problem from what I see is that ko.applyBinding(model) gets fired BEFORE the HTML elements are present within the DOM, thus they never get bound, however there is simply NO WAY to know which elements will be added and what data will be pulled from the server and it has to remain dynamic.

I've figgured that if I could re-apply the binding it would work (I think) but trying to do so gives an error. I've read in couple of other posts that there is no way to reapply the binding for the entire document but that it has to be done separately for each element however these elements would then have to be siblings in the DOM, which doesn't work for me as they need to be nested.

Any ideas on the solution?

EDIT: Made a JSFiddle example, however since I cannot include knockout-mapping.js into it, it doesn't work so I've prepared the .HTML document and added the needed libraries (knockout.js and knockout-mapping.js), linked them all together and uploaded them up here It is a .rar file with everything setup and the libraries included.

EDIT2: A short preview of what is needed can also be seen below:

var ViewModel = {
  buttonOneText: ko.observable("Button 1")
}

ko.applyBindings(ViewModel);

// IMAGINARY AJAX HAPPENS
// when this happens I need to add another button into the frameDiv and add it's data to the model.

document.getElementById("frameDiv").innerHTML += '<button type="button" data-bind="text: buttonTwoText"></button>'
ViewModel.buttonTwoText = "Button 2";

ko.applyBindings(ViewModel, document.getElementById("frameDiv"));

//				So far this works, and it is great!
//				If you comment out the rest of the JS code it will work perfectly
//				problem comes when you attempt to add a thrid element, because
//				even though "frameDiv" has no bind-data, it doesn't let you run it
//				again on that div.

document.getElementById("frameDiv").innerHTML += '<button type="button" data-bind="text: buttonThreeText"></button>'
ViewModel.buttonThreeText = "Button 3";

ko.applyBindings(ViewModel, document.getElementById("frameDiv"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="frameDiv">
  <button type="button" data-bind="text: buttonOneText"></button>

  <!-- The following element will be added VIA Javascript code -->
  <!--<button type="button" data-bind="text: buttonTwoText"></button>-->
</div>

Upvotes: 0

Views: 511

Answers (1)

Roy J
Roy J

Reputation: 43881

You can provide a second argument to ko.applyBindings that specifies the top node to apply the bindings to. You can call this on any nodes that you create, as long as you haven't put already-bound nodes under them.

Here's a little example script with a routine that creates a new DOM node, inserts it, and applies bindings to it. Click the button to execute the routine.

parser = new DOMParser();
foo = 1;

vm = {
  makeNode: function() {
    var el = parser.parseFromString('<div data-bind="text:' + foo + '"></div>', "text/xml").childNodes[0];
    ++foo;
    document.getElementById('foo').appendChild(el);
    ko.applyBindings(vm, el);
  }
};

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="foo">
</div>
<button data-bind="click: makeNode">Add node</button>

Upvotes: 1

Related Questions