Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout and jQuery conflict in IE 8?

We are using Knockout and jQuery in an application where we need to support IE 8 (among several other browsers). We have a case where we need to use a Knockout click binding for one element and a jQuery click handler for another element. However, we have found that these two styles of click handlers conflict with each other in IE 8 (they work fine together in all of our other supported browsers).

We have distilled the problem down to a simple reproducible example. If you don't have IE 8 on your machine, you can test this out in a newer version of IE by selecting 'Browser Mode: IE8":

enter image description here

With that setting in place, you can view the conflict in this jsbin. This code is:

<html>
<head>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.0.0.js"></script>

    <script type="text/javascript">
        $(document).ready(function () {
            ko.applyBindings(function () {
                var viewModel = new Object();

                viewModel.CanViewPage = true;

                viewModel.alertA = function () {
                    alert("A");
                };

                $('#B').click(function () {
                    alert("B");
                });

                return viewModel;
            }());
        });
    </script>
</head>
<body>
    <!-- ko if: CanViewPage -->
        <span data-bind="click: alertA">A</span>
        <span id="B">B</span>
    <!-- /ko -->
</body>
</html>

When you click on "A", the expected alert is shown. However, when you click on "B", the alert("A") is erroneously called:

enter image description here

We have found two "solutions" that fix this problem, but neither one of them are viable for our application. The first way to get these two click handlers to both work in IE 8, is to remove the ko if: CanViewPage. This can be seen in this jsbin. This "solution" is not viable because it is a core page-view permissions mechanism that we use in this large application on many pages.

The second "solution" that we found was to reverse the order of the script tags which import jQuery and Knockout. This can be seen in this jsbin. This "solution" is also not viable because these imports are controlled via a master page wrapper - changing that would require a massive QA effort to retest the entire application for bugs.

Note that since we must support IE8, jQuery 2.x or newer is not an option.

Does anyone have another solution given these constraints? Can anyone explain why this is happening?

like image 928
Jason Frank Avatar asked Dec 11 '13 20:12

Jason Frank


2 Answers

Guidance

As you've noted, the combination of setting jQuery event handlers and the if binding doesn't work right. Knockout generally works best if all interaction with the DOM goes through Knockout. For example, if I expand your example so that CanViewPage is initially false but becomes true later, the event handler for "B" disappears. http://jsbin.com/uyUTuSa/16/edit

Since you've stated that you can't use Knockout for this event handler, Bradley's solution is a good workaround. Another solution that you could use if you know that CanViewPage will never change (isn't observable) is a custom binding that simply clears its contents on a false value and doesn't do any copying or cleaning.

ko.bindingHandlers.ifLight = {
    init: function(element, valueAccessor) {
        if (!valueAccessor()) {
            ko.virtualElements.emptyNode(element);
        }
    }
};
ko.virtualElements.allowedBindings.ifLight = true;

http://jsbin.com/uyUTuSa/18/edit

Technical details

jQuery keeps track of which event handler to call for an element by setting an internal property on the element. This property is assigned a unique number which is used to look up the handler in some sort of internal data storage.

The Knockout if binding first saves a copy of the content elements using cloneNode and then calls ko.cleanNode on the copy, which also calls jQuery.cleanData. This removes any data and event handlers from the copy.

Normally this all works fine, but in Internet Explorer 8 (and below), "expando" properties are converted to element attributes and are copied when using cloneNode. When jQuery cleans the copied node, the event handler data is also removed from the original node.

When the click handler is added by Knockout for the "A" element, it uses jQuery to register the handler. jQuery appears to re-use the data (and number) from cleaned elements, so the "A" element gets the same number as the "B" element.

like image 186
Michael Best Avatar answered Sep 27 '22 21:09

Michael Best


A solution for you is to change

$('#B').click(function () {
    alert("B");
});

to

$(document).on('click', '#B',function () {
    alert("B");
});

See this jsbin.

I do not know exactly why this is happening, but I suspect that the knockout "if" is causing your element to lose its click event when the ko.applyBindings() is applied.

If that is the case, the reason this works is because you are now applying the event on a parent element which is not being destroyed.

For more information on jQuery event handlers, see this excellent article : http://www.elijahmanor.com/differences-between-jquery-bind-vs-live-vs-delegate-vs-on/

like image 21
Bradley Trager Avatar answered Sep 27 '22 21:09

Bradley Trager