knockout data-bind on dynamically generated elements
Asked Answered
F

8

53

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?

Frump answered 16/6, 2012 at 20:19 Comment(3)
are you adding this after you have done your ko.applyBindings(yourVMHere);Raper
Drop the idea of (auto) KO binding on this dynamically added DOM element and handle this manually.Rutheruthenia
correct answer near bottom: https://mcmap.net/q/338736/-knockout-data-bind-on-dynamically-generated-elementsBolyard
R
33

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.
Raper answered 16/6, 2012 at 21:46 Comment(3)
If i call applyBindings again it throw's an error: Error: You cannot apply bindings multiple times to the same element.Disciplinarian
That must be a new feature of the more recent frameworks. The second option is still viable, and honestly the better option to begin with.Raper
Yes i figured as much, it is bad practice to apply bindings more then one time on an element. As it will fire 2 times, i think that's why they added some warning's in KO 3Disciplinarian
I
17

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.

Inbeing answered 14/7, 2015 at 9:13 Comment(0)
R
11

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());
  }
};
Retrogressive answered 13/4, 2015 at 12:38 Comment(2)
After looking at all the answers and comments on this thread, IMO this is actually the best solution to the question "knockout data-bind on dynamically generated elements". Good solution!Acriflavine
If someone that understands this could edit this to be more comprehensible, I would appreciate it :)Bolyard
P
4

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>
President answered 16/4, 2013 at 18:10 Comment(4)
This seems to throw the same "cannot apply bindings twice" error. Is it possible to tell ko.applyBindings() a very specific element you want to add to the bindings?Disciplinarian
This solution does not address the problem. Generally, the dynamically added element would be of the same type which means it ought to invoke the same handler as should be bound to the VM. Ex: VM.loadData = f(){ handling.. } and would want that to be data bound to any (specific) div that would dynamically be added to the DOM. How can a new element have the same function hooked on its click using your approach?Rutheruthenia
Check this fiddle: jsfiddle.net/rniemeyer/F7pLNRutheruthenia
From version 2.3 IIRC it is an error to call applyBindings twice.Quentin
S
4

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);
    }
};
Selfsatisfaction answered 26/2, 2016 at 14:52 Comment(1)
How to call this binding then when I create new tag with bindings inside?Sprain
J
2

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.

Jeffereyjefferies answered 27/4, 2015 at 18:41 Comment(1)
Correct answer. Just load the elements into a knockout observable array instead of adding to the page directly. That's what knockout is for. OR, don't bind to the dynamic element until it has been added to the page.Bolyard
C
1

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.

Cene answered 7/11, 2014 at 20:14 Comment(0)
A
0

Checkout this answer: How do define a custom knockout 'options binding' with predefined Text and Value options

ko.applyBindingsToNode is particularly useful.

Aeriel answered 15/11, 2017 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.