Search code examples
javascriptjquerytwitter-bootstrapknockout.jsknockout-viewmodel-plugin

Boostrap Modal resetting KnockoutJS nested ViewModels variables


I am new to Knockout JS and have come across an issue when using it alongside bootstrap modals which I have been unable to resolve despite hours of research.

Here's my issue: I'm working with nested view models and I've noticed that if I try to update any variables in the nested vm (be it an observable or not) just before or soon after I set the modal to visible, these variables will be reset to their initial value. Here's a sample code to give an idea:

  ###########Javascript########################
  function ViewModelA() {
      self = this;
      self.B = ko.observable(new ViewModelB(self));

      self.ShowB(){
         self.B.Show();
      };
  };

  function ViewModelB(parent){
      self = this;
      self.varOne = ko.observable();
      var varTwo = null;

      self.Show = function(){
         self.GetData();
         $("#modalB").modal('show');
      };

      self.GetData = function(){
         //Ajax call that sets varOne and varTwo
      };

  };

  var A = new ViewModelA();
  ko.applyBindings(A,document.getElementById('modalA'));

 ##############HTML###############################
 <div id="modalA" class="modal fade" tabindex="-1" role="dialog" aria-   labelledby="myModalLabel" aria-hidden="true">
     <div class="modal-dialog">
         <div class="modal-content">
             <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
                <h3 id="myModalLabel">Modal header</h3>
             </div>
             <div class="modal-body">
             ...

The HTML code for modalB is pretty similar with the addition of a data-bind="B" tag in the first div.

The idea is that a user clicking on a button in ModalA will trigger ModalA's Show() function which in turn populates ModalB's observables from an AJAX call and display's ModalB.

In the example above, if I call GetData() from within the Show() function then varOne and varTwo will be reset to their initial values once the modal finishes loading. If, instead, I call GetData() when ViewModelB is instantiated (ie before ModalA's Show function is invoked by the user) then the new values are preserved.

I've been scratching my head for the past couple of days trying to understand why this is happening and how to best address it. I've looked into custom binding handlers, render templates and calling the bootstrap modal "load" event.

Has anyone come across this scenario? Thanks in advance!


Solution

  • Before I make a suggestion there are some points which need to be raised:

    • Is modalB inside modalA? If not then it won't be data-bound by Knockout because you only apply data-binding to #modalA.
    • Shouldn't the line
      self.B = ko.observable(ViewModelB(self));
      actually be
      self.B = ko.observable(new ViewModelB(self));
      i.e., new ViewModelB?
    • ViewModelB.ShowB should just be ViewModelB.Show, right? Or is the call to self.B.Show wrong? Also, is my JavaScript wrong or are self.ShowB() and self.GetData() missing the function keyword?

    Now, in your example JavaScript ViewModelB.varOne is observable and ViewModelB.varTwo is not an observable.

    When you call ViewModelB.Show it first of all makes an ajax call to get values for those two variables. As soon as this asynchronous call is made your browser will then step out of ViewModelB.GetData() and back into ViewModelB.Show, wherein it will then show modalB.

    When your ajax call returns and your handler function (which you've not shown - that's quite pertinent) uses the response to set the values, only those observable variables will be updated in the DOM. In the case of your example JavaScript that means that ViewModelB.varTwo will never be updated in the DOM.

    What I suggest you do instead is ensure that both ViewModelB.varOne and ViewModelB.varTwo are observables.

    An alternative would be to place $("#modalB").modal('show'); into a callback function which you invoke only when the ajax call has returned, but that would involve an undesirable delay in displaying modalB.