I have a simple directive which renders a button.
The directive's link function does the following:
The handlers for the events log a simple message to the console. I would expect the handler to be invoked once on mouseenter or mouseleave. However they are executed twice as if step 2 never occured.
The code for the directive:
function ButtonDirective() {
return {
restrict: 'E',
template: '<button><span ng-transclude></span></button>',
transclude: true,
replace: true,
link: function (scope, element, attrs) {
function mouseEnterHandler() {
console.log('mouse enter');
}
function mouseLeaveHandler() {
console.log('mouse leave');
}
element.bind('mouseenter', mouseEnterHandler);
element.bind('mouseleave', mouseLeaveHandler);
element.unbind('mouseenter');
element.unbind('mouseleave');
element.bind('mouseenter', mouseEnterHandler);
element.bind('mouseleave', mouseLeaveHandler);
}
}
}
The following plunker illustrates the issue:
http://plnkr.co/ocXYYZ2jv09Ch7GDRaat
Does anybody have an idea why it behaves this way?
update: if you include jQuery instead of falling back on JQLite it works. Alas that it not an option for me.
Well, the issue appears to be located in JQLite. If you include jQuery it works as expected.
In JQLite the bind() and unbind() functions are just aliases for JQLite.off() and on().
Let's consider the code for the on() function.
on: function jqLiteOn(element, type, fn, unsupported) {
// ...
}
This function registers the handlers for the various types of events. Apparently they make an exception for the 'mouseenter' and 'mouseleave' events.
if (type === 'mouseenter' || type === 'mouseleave') {
// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
var target = this, related = event.relatedTarget;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if (!related || (related !== target && !target.contains(related))) {
handle(event, type);
}
});
}
These events are mapped (MOUSE_EVENT_MAP[type]) to 'mouseover' and 'mouseout' respectively.
var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
And then the jqLiteOn(...) function recursively calls itself and registers an anonymous function handler for these mapped events. Afterwards it registers your 'mouseenter' and 'mouseleave' events.
So basically that's why the events handlers are being called multiple times.
A workaround is to also unbind the mouseover and mouseout events.
element.unbind('mouseover mouseenter');
element.unbind('mouseout mouseleave');