Knockout: Knockout binding removes my jquery DOM events

Created on 1 Nov 2017  路  18Comments  路  Source: knockout/knockout

I have several jquery dom events that are created on DOM load or document ready. These are mostly default behaviors that should be applied to all forms in my application. Example:

$('input:text').focus(function () { $(this).select(); });

Right before applying knockout binding, I can check my dom elements and all events are there:

before knockout binding

But when I run the applyBindings method to bind the viewmodel to my DOM, the "with" binding removes all events that are not related to knockout:

after knockout biding

I have tried overwriting the cleanExternalData as explained on the documentation and on this answer. But that did not help with this, the function is replaced, but the events are still removed from the DOM when the templating is applied on the binding process.

For the record, this is not an exclusive behavior of the with function, but all anonymous templating functions also do that, _foreach_, _if_, _ifnot_. Using template, as expected, also behaves the same way. The DOM element is completely destroyed, stored as a template, then added again on my document when the condition is satisfied, but now without any jquery event handlers.

How to avoid that knockout removes the events from my DOM elements?

All 18 comments

Great report. Are you able to reproduce in a jsfiddle?

@shadowkras Can you provide an reproduction in jsfiddle, etc.?

I'm seeing a similar issue using a "with binding" along with the jquery ui accordion. It works fine for initialization, but loses the events when the model object changes. Tracing through Chrome debug, I can see that knockout clones the elements, removes them, then re-adds them, but cloning doesn't get the jquery ui events.

@davisnw What you're describing is the expected behaviour, so there'd need to be a design change.

When a template/if/with binding is truthy it does not remove the elements. However, if ever a conditional is falsy, it will remove it, and any native event bindings will be lost.

There's no way to get the event listeners on the DOM, and while detaching nodes from the DOM would preserve those events. See e.g. https://stackoverflow.com/questions/15408394/

So I think the correct solution might be to add a preprocessor hook that allows users to preserve the events for those cases where event handlers are wrapped by a library and copyable e.g. jQuery.

@davisnw Perhaps you would benefit from the using binding (#1811). It's coming in the next version, but you can copy the binding code and use it in your application now.

@mbest
Thanks for the suggestion - I can't use non-production versions right now, but will keep an eye on it.

I admit to still being confused why the "with" binding doesn't work - you say that if a binding is "truthy" then the elements should not be removed - according to https://developer.mozilla.org/en-US/docs/Glossary/Truthy

All values are truthy unless they are defined as falsy (i.e., except for false, 0, "", null, undefined, and NaN).

So as far as I can tell, my submodel is "truthy" - I'm never setting it to one of the "falsy" values listed above.

I've attached a simplified sample of what I'm trying to accomplish. The one that does not use the "with" binding works fine, but the one that uses the "with" binding loses the accordion as soon as you click "Reset and toggle"

kockoutAccordion.zip

Or as fiddles:

Works: https://jsfiddle.net/L4nuaxws/1/

Breaks: https://jsfiddle.net/tywdxjds/2/

@davisnw Thanks for the example. Here it is with the using binding: https://jsfiddle.net/tywdxjds/3/ You don't need to use a non-production version of Knockout, you just need to add it as a custom binding.

@mbest Thanks, that looks like a good solution for my present case.

For what it's worth - I found this github issue from the comments on https://stackoverflow.com/questions/14243698/prevent-with-binding-from-removing-dom-elements-knockout-js/14246811#14246811

The with binding will remove and re-render all child nodes if the model changes. It won't remove/re-render initially (since version 2.2). Changing the with binding to not re-render would be a breaking change. So we're instead adding a different binding to support that.

So now that we've gone through this a bit, I wonder if my example is actually distinct from the original post - whereas I am clearly changing the model instance, it is not clear that @shadowkras is.

Sorry, we were on holidays here in my country yesterday. I can try to get a jsfiddle working, if it is still needed, would be my first time trying to setup an environment on it though. But for the record, i saved my dom object in a variable before applybinding and on another variable after applybinding, they are different objects when compared.
@mbest So you mean that if I initiate my object with a value, the with binding will not remove the dom? Because my viewmodel creates this object already with a dummy value, then with user interaction this object's properties gain new values, and the dom is still removed. To clarify, its a table that you can click an edit button and a modal with the selected item properties can be changed. I will try using the using binding. Thanks.
@davisnw Thanks for the support, that comment on stackex was mine. And yes, it seems we have the same problem.

Alright, an update. I had tried your using binding before, same for let and withLight. But this helped me to figure out what I am doing wrong. When I wanted to update my observable object, I was using observableObject(NewObject), and this triggered knockout that the old object was destroyed (and thus, falsy for a second) and a new object took its place, even though all properties are there, they just got different values.
To go around this, instead of doing that, I replaced the value of each property of the observable object to have the value of the new object. This didn't destroy the object and knockout updated my binding properly. with still killed some of my events (from angular ng-change for instance), but it kept all jquery events in there (which is great). And the using binding didn't kill any of my events at all (which is even better).

Now, is that the proper behavior? If I pass a new object with the same properties to an observable object, it should destroy the old one? What are the conditions for knockout to consider that the objects are different?

What are the conditions for knockout to consider that the objects are different?

I believe in the case of objects (ie: not native objects like numbers), different objects are different references. Ie:

```
let obj1 = {"a": 1, "b": 2};
let obj2 = {"a": 1, "b": 2};

let isEqual = obj1 == obj2; // will be false
````

@shadowkras You shouldn't trigger an update with the with binding if you use e.g. Object.assign or _.extend or set the properties individually. You will trigger an update if those properties are observable and they mutate i.e. you pass new values to them.

You are correct @brianmhunt . I am still new to knockout (2 months working with it so far), but changing how my observable object recieves the new values is also working with with:

myViewModel.setSelectedItem = function setSelectedItem (newObject)
{
    for (var prop in myViewModel.myObservableObject())
        myViewModel.myObservableObject()[prop](newObject[prop]);
}

As long as all properties in my observable object are also observable. Otherwise it worked as I described on my previous post.

Closing since there doesn't appear to be a problem in Knockout.

Not necessarily intending to re-open this issue, just documenting additional information for future visitors (or my future self!).

I found that working with the using binding caused the knockout-validation plugin (https://github.com/Knockout-Contrib/Knockout-Validation) to not work when the model instance changed. The reason has to do with the using binding (as defined at the time - I haven't checked latest source) not reprocessing bindings when the complex model instance changed. Knockout validation works by associating an element via the wrapped binding handlers (wrapping value, etc) and extending the associated observable, so that when validation is triggered, the extended observable knows which element to associate the error with. However, when the model instance changes, and the bindings are not reprocessed, the new model instance does not have its associated observables extended, so validation doesn't work.

The with binding redoes the dom elements and bindings, so it works fine with knockout-validation.

In my particular case, I found that I could solve my issues with my "accordion" binding, and still use validation by simply ensuring that with and accordion were not on the same element. My "accordion" binding worked fine when "with" was on a parent or child element, just not when it was on the same element.

Might be a stupid question, but have you tried

$(document).on('focus', 'input:text', function () {
    $(this).select();
});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

brunolau picture brunolau  路  8Comments

nkosi23 picture nkosi23  路  5Comments

johnpapa picture johnpapa  路  9Comments

andersekdahl picture andersekdahl  路  6Comments

mcarpenterjr picture mcarpenterjr  路  3Comments