Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout components (not) binding on new content

Redacted question and example

I'm trying to get Knockout components to bind after the initial ko.applyBindings(); so I can add custom elements dynamically.

In my original post I referred to loading content by ajax, but my problem occurs when custom elements are added to the DOM using something like jQuery append.

Here's an example:

$(function() {

  // Register a simple widget:
  ko.components.register('like-widget', {
    template: '<div class="alert alert-info">This is the widget</div>'
  });

  // Apply bindings
  ko.applyBindings();


  // Wire up 'add' button:
  $('#btnAdd').on('click', function() {

    $('#addZone').append("<like-widget></like-widget>");


  });

});
<link data-require="bootstrap-css@*" data-semver="3.2.0" rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<body>

  Here's a widget, declared inline:
  <like-widget></like-widget>

  <button id='btnAdd'>Add a new widget to the grey box:</button>
  <br/><br/>

  <div id='addZone' class="well">
    Widgets will be appended here
  </div>

  <p>When you run this code, the widget custom element is indeed added to the box (see source) but the widget's template is not bound, so nothing appears. How do I get it to bind/appear?</p>


</body>

Original post

I've successfully created my new component, <mynew-widget></mynew-widget>

I've seen the joy of peppering my page with them and everything works beautifully... until I load new content (an AJAX-loaded modal popup, for example) which contains <mynew-widget></mynew-widget>. Nothing happens.

Is this a limitation of Knockout or have I wired things up incorrectly?

Please tell me it's the latter - as I love not having to worry about when/where to call ApplyBindings and on which parts of the DOM.

Thinking it through, I understand knockout needs to notice that the custom elements have been added to the DOM - but I was hoping it might just work in a jQuery '$().on(...)' kind of way.

like image 251
rwalter Avatar asked Nov 25 '14 22:11

rwalter


People also ask

What is binding in Knockout?

This binding is used to bind the child elements of an object in the specified object's context. This binding can also be nested with other type of bindings such as if and foreach. Syntax with: <binding-object> Parameters. Pass the object which you want to use as context for binding child elements as the parameter.

How do you activate Knockout?

To activate Knockout, add the following line to a <script> block: ko. applyBindings(myViewModel); You can either put the script block at the bottom of your HTML document, or you can put it at the top and wrap the contents in a DOM-ready handler such as jQuery's $ function.


Video Answer


1 Answers

The component binding doesn't happen magically: it happens when you call ko.applyBindings();. At this moment, the bound HTML is searched for components, and they are bound.

Later on, when you dynamically add a new component to the page, it's not bound, unless you explicitly bind it. So, in your code, the component is fully ignored.

As stated, what you need to do is to explicitly bind it. But you must take into account that you cannot bind nodes that have already been bound. However it's extremely easy to create a node with jquery, append it to the DOM, and bind it. There is a syntax to specify the viewmodel and the node to which you want to bind it: ko.applyBindings(viewModel, node);

Here you have a full working sample in jsfiddle. This is the code in that fiddle:

HTML:

Here's a widget, declared inline:

  <button id='btnAdd'>Add a new widget to the grey box:</button>
  <br/><br/>

  <div id='addZone' class="well">
    Widgets will be appended here
  </div>

JavaScript:

ko.components.register('like-widget', {
    template: '<div class="alert alert-info">This is the widget</div>'
  });

ko.applyBindings()

$('#btnAdd').on('click', function() {
   // Create your widget node
   var $newWidget = $('<like-widget>');
   // Append it to your "append area"
   $('#addZone').append($newWidget);
   // Apply bindings to the newly added node
   ko.applyBindings({}, $newWidget[0]);
});

NOTE: when calling apply bindings I pass an empty objet: don't pass a null, or you'll get an error. If your template includes a viewmodel, it will be used independently of the passed view model.

NOTE: $newWidget is a jquery object. $newWidget[0] is the first (and only, in this case) DOM element of the jQuery object, as required by applyBindings

like image 81
JotaBe Avatar answered Sep 24 '22 10:09

JotaBe