0

I have Bootstrap tabs bound to a KnockoutJS observable array using a foreach binding, which is working fine. I now want to launch a Bootstrap modal from inside the active tab.

<div class="tab-content">
    <!-- ko foreach: trueData -->       
    <div class="tab-pane fade in" data-bind="css:{active: $index() == 0}, attr :{'id': 'tab' + $index()}">
    </div>
</div>

And for this the Bootstrap modal code has to be placed inside the TABS divs, but this is against this "Modal markup placement" rule.

When I do this only the modal inside the first tabs opens. The modals in the other tabs do not open.

What is the workaround for this? Is there a way to get the index of the active tab and do a separate foreach and filter binding the knockout array based on it?

Jeroen
  • 60,696
  • 40
  • 206
  • 339
Jay
  • 71
  • 2
  • 10

1 Answers1

1

I suggest using a totally different pattern for this.

Use one Bootstrap modal at the level of your $root view model. This modal shows data for a root view model observable currentModalItem and is hidden when that observable is null. The modal is activated by setting that observable from inside the tabs.

This does require a custom modal binding, but it's not difficult to create or modify. My example uses the custom binding from another answer by @huocp, but you can vary as per your needs.

It's also useful to do the tabs in a certain way, the example below utilizes a version based off this answer by @RPNiemeyer.

Here's an example:

ko.bindingHandlers.modal = {
    init: function (element, valueAccessor) {
        $(element).modal({
            show: false
        });

        var value = valueAccessor();
        if (ko.isObservable(value)) {
            $(element).on('hide.bs.modal', function() {
               value(false);
            });
        }
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
           $(element).modal("destroy");
        });

    },
    update: function (element, valueAccessor) {
        var value = valueAccessor();
        if (ko.utils.unwrapObservable(value)) {
            $(element).modal('show');
        } else {
            $(element).modal('hide');
        }
    }
}

var ItemViewModel = function(data, initiallySelected) {
  this.isSelected = ko.observable(!!initiallySelected);
  this.txt = ko.observable(data);
};

var RootViewModel = function() {
  var self = this;
  
  self.currentModalItem = ko.observable(null);
  
  self.items = ko.observableArray([new ItemViewModel("Apples", true), new ItemViewModel("Oranges"), new ItemViewModel("Pears")]);
  
  self.selectItem = function(item) {
    self.items().forEach(function(i) { i.isSelected(false); });
    item.isSelected(true);
  };
  
  self.openModal = function(item) {
    self.currentModalItem(item);
  };
};

ko.applyBindings(new RootViewModel());
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div class="modal fade" data-bind="modal: !!currentModalItem(), with: currentModalItem">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-body">
        <p>You opened item: <i data-bind="text: txt"></i></p>
      </div>
    </div>
  </div>
</div>

<div>
  <!-- Nav tabs -->
  <ul class="nav nav-tabs" data-bind="foreach: items">
    <li data-bind="css: { active: isSelected }">
      <a href="#" data-bind="text: txt, click: $parent.selectItem"></a>
    </li>
  </ul>

  <!-- Tab panes -->
  <div class="tab-content" data-bind="foreach: items">
    <div class="tab-pane" data-bind="css: { active: isSelected }">
      <h3 data-bind="text: txt"></h3>
      <p><a href="#" data-bind="click: $root.openModal">Open Modal!</a></p>
    </div>
  </div>
</div>

True, it's not perfect (yet), but the above should get you started.

Community
  • 1
  • 1
Jeroen
  • 60,696
  • 40
  • 206
  • 339