Search code examples
twitter-bootstrapknockout.jstabsknockout-components

knockout component not binding on dynamic tab


I am working on a simple example with bootstrap tabs where the first tab will hold summary of all stations and clicking on a station will open a new tab and show knockout-component. It is rendering the template fine when I use a static tab but not on a dynamic tab.

It generates the tab fine but for some reason Knockout is doing nothing, Do I have to do something to trigger knockout other than injecting the component into the DOM?

function addNewTab(p) {
    var ary = p.split(',');
    var id = ary[0];
    var name = ary[1];
    //LoadDetails(id);
    var nextTab = $('#tabs li').size() + 1;
    $('<li><a href="#tab' + nextTab + '" data-toggle="tab">' + name + '</a><span class="glyphicon glyphicon-remove"></span></li>').appendTo('#tabs');
    $('<div class="tab-pane" id="tab' + nextTab + '"><tab-details params = "id: '+id+'"></tab-details></div>').appendTo('.tab-content');
    $('#tabs a:last').tab('show');
}


ko.components.register('tab-details', {
    template: '<div data-bind="html: brief"></div>',
    viewModel: function (params) {
        var self = this;
        self.brief = ko.observable('Hello World');
        var url = "http://localhost:3000/stationapi?id=" + params.id;
        $.getJSON(url, function (data) {
            self.brief(data.stations.content.brief);
        }); 
    } 
});
ko.applyBindings();

Solution

  • It feels like you'r approaching a Knockout solution using jquery... Not usually a great idea...

    Maybe this can get you on the right track (either open the following jsfiddle or try out stackoverflow's new in-answer script runner :) ) - jsfiddle.net/n949kf03/4/

    var vm = (function() {
      function tab(data, name, id) {
        return {
          id: id || name.replace(/ +?/g, ''),
          name: name,
          data: data
        };
      };
    
      var createNewTab = function() {
        var tl = tabArray().length;
        tabArray.push(new tab('amazing tab data: ' + tl, 'tab ' + tl));
      }
    
      // the following data is just  to fill out the tabs,
      // you would replace with var tabArray = ko.observableArray([]);
      // once you had everything working
      var tabArray = ko.observableArray([
        new tab('working', 'firstTab'),
        new tab('tab2', 'secondTab', 2),
        new tab('thirdTab', 'third')
      ]);
    
      return {
        tabs: tabArray,
        createNewTab: createNewTab
      };
    })();
    
    ko.applyBindings(vm);
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" />
    
    <!-- List all of the tabs, this first tab definition is the home tab you talked 
         about where you would click to open the other stations -->
    <ul class="nav nav-tabs" role="tablist">
      <li class="active"><a href="#home" role="tab" data-toggle="tab">Home</a>
      </li>
      <!-- ko template: { name: 'tab-definition-template', foreach: tabs } -->
      <!-- /ko -->
    </ul>
    
    <!-- Tab panes -->
    <div class="tab-content">
      <div class="tab-pane active" id="home">
        This is your home tab with links to the other tabs :D
        <br/>
        <button class="btn btn-success" data-bind="click: createNewTab">new tab</button>
      </div>
      <!-- ko template: { name: 'tab-template', foreach: tabs } -->
      <!-- /ko -->
    </div>
    
    <script type="text/html" id="tab-definition-template">
      <li>
        <a role="tab" data-toggle="tab" data-bind="text: name, attr: {'href': ('#' + id)}"></a>
      </li>
    </script>
    
    <!-- this would work better as a component -->
    <script type="text/html" id="tab-template">
      <div class="tab-pane" data-bind="text: data, attr: {'id': id}"></div>
    </script>

    What I am doing is creating two template definitions, one for the tab template definition and one for each tab. To do this i have used virtual elements.

    Ide say you choise to use components will probably be correct for the tabs that it adds (as it allows an inner vm for each tab which is good!) but for this example I just wanted to create something for you to use which will work allot better than through manipulating the dom with jquery when you really should just use knockout.