Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 22f6602

Browse files
committedNov 12, 2015
fix(jqLite): deregister special mouseenter / mouseleave events correctly
Closes #12795 Closes #12799
1 parent 6f8ddb6 commit 22f6602

File tree

3 files changed

+105
-32
lines changed

3 files changed

+105
-32
lines changed
 

‎src/jqLite.js

+49-31
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,23 @@ function jqLiteOff(element, type, fn, unsupported) {
307307
delete events[type];
308308
}
309309
} else {
310-
forEach(type.split(' '), function(type) {
310+
311+
var removeHandler = function(type) {
312+
var listenerFns = events[type];
311313
if (isDefined(fn)) {
312-
var listenerFns = events[type];
313314
arrayRemove(listenerFns || [], fn);
314-
if (listenerFns && listenerFns.length > 0) {
315-
return;
316-
}
317315
}
316+
if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
317+
removeEventListenerFn(element, type, handle);
318+
delete events[type];
319+
}
320+
};
318321

319-
removeEventListenerFn(element, type, handle);
320-
delete events[type];
322+
forEach(type.split(' '), function(type) {
323+
removeHandler(type);
324+
if (MOUSE_EVENT_MAP[type]) {
325+
removeHandler(MOUSE_EVENT_MAP[type]);
326+
}
321327
});
322328
}
323329
}
@@ -772,14 +778,17 @@ function createEventHandler(element, events) {
772778
return event.immediatePropagationStopped === true;
773779
};
774780

781+
// Some events have special handlers that wrap the real handler
782+
var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
783+
775784
// Copy event handlers in case event handlers array is modified during execution.
776785
if ((eventFnsLength > 1)) {
777786
eventFns = shallowCopy(eventFns);
778787
}
779788

780789
for (var i = 0; i < eventFnsLength; i++) {
781790
if (!event.isImmediatePropagationStopped()) {
782-
eventFns[i].call(element, event);
791+
handlerWrapper(element, event, eventFns[i]);
783792
}
784793
}
785794
};
@@ -790,6 +799,22 @@ function createEventHandler(element, events) {
790799
return eventHandler;
791800
}
792801

802+
function defaultHandlerWrapper(element, event, handler) {
803+
handler.call(element, event);
804+
}
805+
806+
function specialMouseHandlerWrapper(target, event, handler) {
807+
// Refer to jQuery's implementation of mouseenter & mouseleave
808+
// Read about mouseenter and mouseleave:
809+
// http://www.quirksmode.org/js/events_mouse.html#link8
810+
var related = event.relatedTarget;
811+
// For mousenter/leave call the handler if related is outside the target.
812+
// NB: No relatedTarget if the mouse left/entered the browser window
813+
if (!related || (related !== target && !jqLiteContains.call(target, related))) {
814+
handler.call(target, event);
815+
}
816+
}
817+
793818
//////////////////////////////////////////
794819
// Functions iterating traversal.
795820
// These functions chain results into a single
@@ -818,35 +843,28 @@ forEach({
818843
var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
819844
var i = types.length;
820845

821-
while (i--) {
822-
type = types[i];
846+
var addHandler = function(type, specialHandlerWrapper, noEventListener) {
823847
var eventFns = events[type];
824848

825849
if (!eventFns) {
826-
events[type] = [];
827-
828-
if (type === 'mouseenter' || type === 'mouseleave') {
829-
// Refer to jQuery's implementation of mouseenter & mouseleave
830-
// Read about mouseenter and mouseleave:
831-
// http://www.quirksmode.org/js/events_mouse.html#link8
832-
833-
jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
834-
var target = this, related = event.relatedTarget;
835-
// For mousenter/leave call the handler if related is outside the target.
836-
// NB: No relatedTarget if the mouse left/entered the browser window
837-
if (!related || (related !== target && !jqLiteContains.call(target, related))) {
838-
handle(event, type);
839-
}
840-
});
841-
842-
} else {
843-
if (type !== '$destroy') {
844-
addEventListenerFn(element, type, handle);
845-
}
850+
eventFns = events[type] = [];
851+
eventFns.specialHandlerWrapper = specialHandlerWrapper;
852+
if (type !== '$destroy' && !noEventListener) {
853+
addEventListenerFn(element, type, handle);
846854
}
847-
eventFns = events[type];
848855
}
856+
849857
eventFns.push(fn);
858+
};
859+
860+
while (i--) {
861+
type = types[i];
862+
if (MOUSE_EVENT_MAP[type]) {
863+
addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
864+
addHandler(type, undefined, true);
865+
} else {
866+
addHandler(type);
867+
}
850868
}
851869
},
852870

‎src/ngScenario/browserTrigger.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
if (!element) return;
1616

1717
eventData = eventData || {};
18+
var relatedTarget = eventData.relatedTarget || element;
1819
var keys = eventData.keys;
1920
var x = eventData.x;
2021
var y = eventData.y;
@@ -84,7 +85,7 @@
8485
x = x || 0;
8586
y = y || 0;
8687
evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
87-
pressed('alt'), pressed('shift'), pressed('meta'), 0, element);
88+
pressed('alt'), pressed('shift'), pressed('meta'), 0, relatedTarget);
8889
}
8990

9091
/* we're unable to change the timeStamp value directly so this

‎test/jqLiteSpec.js

+54
Original file line numberDiff line numberDiff line change
@@ -1431,6 +1431,60 @@ describe('jqLite', function() {
14311431
});
14321432

14331433

1434+
it('should correctly deregister the mouseenter/mouseleave listeners', function() {
1435+
var aElem = jqLite(a);
1436+
var onMouseenter = jasmine.createSpy('onMouseenter');
1437+
var onMouseleave = jasmine.createSpy('onMouseleave');
1438+
1439+
aElem.on('mouseenter', onMouseenter);
1440+
aElem.on('mouseleave', onMouseleave);
1441+
aElem.off('mouseenter', onMouseenter);
1442+
aElem.off('mouseleave', onMouseleave);
1443+
aElem.on('mouseenter', onMouseenter);
1444+
aElem.on('mouseleave', onMouseleave);
1445+
1446+
browserTrigger(a, 'mouseover', {relatedTarget: b});
1447+
expect(onMouseenter).toHaveBeenCalledOnce();
1448+
1449+
browserTrigger(a, 'mouseout', {relatedTarget: b});
1450+
expect(onMouseleave).toHaveBeenCalledOnce();
1451+
});
1452+
1453+
1454+
it('should call a `mouseenter/leave` listener only once when `mouseenter/leave` and `mouseover/out` '
1455+
+ 'are triggered simultaneously', function() {
1456+
var aElem = jqLite(a);
1457+
var onMouseenter = jasmine.createSpy('mouseenter');
1458+
var onMouseleave = jasmine.createSpy('mouseleave');
1459+
1460+
aElem.on('mouseenter', onMouseenter);
1461+
aElem.on('mouseleave', onMouseleave);
1462+
1463+
browserTrigger(a, 'mouseenter', {relatedTarget: b});
1464+
browserTrigger(a, 'mouseover', {relatedTarget: b});
1465+
expect(onMouseenter).toHaveBeenCalledOnce();
1466+
1467+
browserTrigger(a, 'mouseleave', {relatedTarget: b});
1468+
browserTrigger(a, 'mouseout', {relatedTarget: b});
1469+
expect(onMouseleave).toHaveBeenCalledOnce();
1470+
});
1471+
1472+
it('should call a `mouseenter/leave` listener when manually triggering the event', function() {
1473+
var aElem = jqLite(a);
1474+
var onMouseenter = jasmine.createSpy('mouseenter');
1475+
var onMouseleave = jasmine.createSpy('mouseleave');
1476+
1477+
aElem.on('mouseenter', onMouseenter);
1478+
aElem.on('mouseleave', onMouseleave);
1479+
1480+
aElem.triggerHandler('mouseenter');
1481+
expect(onMouseenter).toHaveBeenCalledOnce();
1482+
1483+
aElem.triggerHandler('mouseleave');
1484+
expect(onMouseleave).toHaveBeenCalledOnce();
1485+
});
1486+
1487+
14341488
it('should deregister specific listener within the listener and call subsequent listeners', function() {
14351489
var aElem = jqLite(a),
14361490
clickSpy = jasmine.createSpy('click'),

0 commit comments

Comments
 (0)
This repository has been archived.