knockout data-bind on dynamically generated elements

JavascriptJqueryknockout.js

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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.

Solution 3 - Javascript

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());
  }
};

Solution 4 - Javascript

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>

Solution 5 - Javascript

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);
    }
};

Solution 6 - Javascript

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.

Solution 7 - Javascript

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.

Solution 8 - Javascript

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKing JulienView Question on Stackoverflow
Solution 1 - JavascriptPlTaylorView Answer on Stackoverflow
Solution 2 - JavascriptStevanicusView Answer on Stackoverflow
Solution 3 - JavascriptSalomónView Answer on Stackoverflow
Solution 4 - JavascriptIvan MalagonView Answer on Stackoverflow
Solution 5 - JavascriptvivanovView Answer on Stackoverflow
Solution 6 - JavascriptjohanneslinkView Answer on Stackoverflow
Solution 7 - JavascriptIvaylo SlavovView Answer on Stackoverflow
Solution 8 - JavascriptAntoniView Answer on Stackoverflow