Search code examples
javascriptjqueryknockout.jsknockout-3.0

ko.cleanNode removes the jQuery .on("focusout") event


I am working with Knockout JS application. Here i have a problem when i use ko.cleanNode(node), it removes focusout client event. i.e., before calling cleanNode method focusOut works properly on text box. After that focusOut event does not triggered. I have checked many of the stack overflow questions, I have only found the cause of this removal, not any alternative solution. Here is my Code.

$("#btn").click(function() {
  viewModel = {
    nValue: ko.observable(10)
  }
  ko.cleanNode(document.getElementById("txt"));
  ko.applyBindings(viewModel, document.getElementById("txt"));
});
ko.applyBindings({
  nValue: ko.observable(100)
});
$("#txt").on("focusout", function() {
  alert("focusOut");
});
<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.4.2/knockout-min.js"></script>
<input type="text" id="txt" data-bind="value: nValue" />
<input type="button" id="btn" value="cleannode" />


Solution

  • From the Knockout documentation on disposal logic:

    When removing an element, Knockout runs logic to clean up any data associated with the element. As part of this logic, Knockout calls jQuery’s cleanData method if jQuery is loaded in your page. In advanced scenarios, you may want to prevent or customize how this data is removed in your application. Knockout exposes a function, ko.utils.domNodeDisposal.cleanExternalData(node), that can be overridden to support custom logic. For example, to prevent cleanData from being called, an empty function could be used to replace the standard cleanExternalData implementation

    So, override the cleanExternalData as mentioned in the documentation:

    ko.utils.domNodeDisposal.cleanExternalData = function () {
        // Do nothing. Now any jQuery data associated with elements will
        // not be cleaned up when the elements are removed from the DOM.
    };
    
    $("#btn").click(function() {
      viewModel = {
        nValue: ko.observable(10)
      }
      ko.cleanNode(document.getElementById("txt"));
      ko.applyBindings(viewModel, document.getElementById("txt"));
    });
    
    ko.applyBindings({
      nValue: ko.observable(100)
    });
    
    $("#txt").on("focusout", function() {
      alert("focusOut");
    });
    <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.4.2/knockout-min.js"></script>
    <input type="text" id="txt" data-bind="value: nValue" />
    <input type="button" id="btn" value="cleannode" />


    Looking at your code, if your intention is just updating the viewmodel's observable upon a click and adding a focusOut event handler, you don't need to cleanNode or applyBindings() or use jquery at all. You can use knokcout's click and event binding like this:

    var viewModel = function() {
      var self = this;
    
      self.nValue = ko.observable(100);
    
      self.increment = function() {
        self.nValue(self.nValue() + 1);
      }
    
      self.focusOut = function() {
        alert("Focus Out");
      }
    };
    
    ko.applyBindings(new viewModel());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <input type="text" id="txt" data-bind="value: nValue, event: { focusout: focusOut }" />
    <input type="button" id="btn" value="increment" data-bind="click: increment" />