52
votes

How is it possible to make knockout data-bind work on dynamically generated elements? For example, I insert a simple html select menu inside a div and want to populate options using the knockout options binding. This is what my code looks like:

$('#menu').html('<select name="list" data-bind="options: listItems"></select>');

but this method doesn't work. Any ideas?

8
are you adding this after you have done your ko.applyBindings(yourVMHere);PlTaylor
Drop the idea of (auto) KO binding on this dynamically added DOM element and handle this manually.Vaibhav
correct answer near bottom: stackoverflow.com/a/29903552/3093731Andrew

8 Answers

33
votes

If you add this element on the fly after you have bound your viewmodel it will not be in the viewmodel and won't update. You can do one of two things.

  1. Add the element to the DOM and re-bind it by calling ko.applyBindings(); again
  2. OR add the list to the DOM from the beginning and leave the options collection in your viewmodel empty. Knockout won't render it until you add elements to options on the fly later.
15
votes

Knockout 3.3

ko.bindingHandlers.htmlWithBinding = {
          'init': function() {
            return { 'controlsDescendantBindings': true };
          },
          'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
              element.innerHTML = valueAccessor();
              ko.applyBindingsToDescendants(bindingContext, element);
          }
    };

Above code snippet allows you to inject html elements dynamically with the "htmlWithBinding" property. The child elements which are added are then also evaluated... i.e. their data-bind attributes.

11
votes

rewrite html binding code or create a new. Because html binding prevents "injected bindings" in dynamical html:

ko.bindingHandlers['html'] = {
  //'init': function() {
  //  return { 'controlsDescendantBindings': true }; // this line prevents parse "injected binding"
  //},
  'update': function (element, valueAccessor) {
    // setHtml will unwrap the value if needed
    ko.utils.setHtml(element, valueAccessor());
  }
};
4
votes

For v3.4.0 use the custom binding below:

ko.bindingHandlers['dynamicHtml'] = {
    'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        // setHtml will unwrap the value if needed
        ko.utils.setHtml(element, valueAccessor());
        ko.applyBindingsToDescendants(bindingContext, element);
    }
};
4
votes

EDIT: It seems that this doesn't work since version 2.3 IIRC as pointed by LosManos

You can add another observable to your view model using myViewModel[newObservable] = ko.observable('')

After that, call again to ko.applyBindings.

Here is a simple page where I add paragraphs dynamically and the new view model and the bindings work flawlessly.

// myViewModel starts only with one observable
    	var myViewModel = {
    	    paragraph0: ko.observable('First')
    	};
    
    	var count = 0;
    
    	$(document).ready(function() {
    		ko.applyBindings(myViewModel);
    
    		$('#add').click(function() {
    			// Add a new paragraph and make the binding
    			addParagraph();
    			// Re-apply!
    			ko.applyBindings(myViewModel);			
    			return false;	
    		});
    	});
    
    	function addParagraph() {
    		count++;
    		var newObservableName = 'paragraph' + count;
    	    $('<p data-bind="text: ' + newObservableName + '"></p>').appendTo('#placeholder');
    		
    	    // Here is where the magic happens
    		myViewModel[newObservableName] = ko.observable('');
    		myViewModel[newObservableName](Math.random());
    
    		// You can also test it in the console typing
    		// myViewModel.paragraphXXX('a random text')
    	}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>

<div id="placeholder">
    <p data-bind="text: paragraph0"></p>
</div>
    
<a id="add" href="#">Add paragraph</a>
2
votes

It's an old question but here's my hopefully up-to-date answer (knockout 3.3.0):

When using knockout templates or custom components to add elements to prebound observable collections, knockout will bind everything automatically. Your example looks like an observable collection of menu items would do the job out of the box.

0
votes

Based on this existing answer, I've achived something similar to your initial intentions:

function extendBinding(ko, container, viewModel) {
    ko.applyBindings(viewModel, container.children()[container.children().length - 1]);
}

function yourBindingFunction() {
    var container = $("#menu");
    var inner = $("<select name='list' data-bind='options: listItems'></select>");
    container.empty().append(inner);


    extendBinding(ko, container, {
        listItems: ["item1", "item2", "item3"]
    });
}

Here is a JSFiddle to play with.

Be warned, once the new element is part of the dom, you cannot re-bind it with a call to ko.applyBindings- that is why I use container.empty(). If you need to preserve the new element and make it change as the view model changes, pass an observable to the viewModel parameter of the extendBinding method.

0
votes