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

Commit b0233a3

Browse files
gockxmlpetebacondarwin
authored andcommitted
fix(jqLite): correct implementation of mouseenter/mouseleave event
Implement mouseenter/mouseleave event referring to http://www.quirksmode.org/js/events_mouse.html#link8 and jQuery source code(not dependent on jQuery). The old implementation is wrong. When moving mouse from a parent element into a child element, it would trigger mouseleave event, which should not. And the old test about mouseenter/mouseleave is wrong too. It just triggers mouseover and mouseout events, cannot describe the process of mouse moving from one element to another element, which is important for mouseenter/mouseleave. Closes #2131, #1811
1 parent c3235db commit b0233a3

File tree

2 files changed

+71
-26
lines changed

2 files changed

+71
-26
lines changed

src/jqLite.js

+34-14
Original file line numberDiff line numberDiff line change
@@ -602,23 +602,43 @@ forEach({
602602

603603
if (!eventFns) {
604604
if (type == 'mouseenter' || type == 'mouseleave') {
605-
var counter = 0;
605+
var contains = document.body.contains || document.body.compareDocumentPosition ?
606+
function( a, b ) {
607+
var adown = a.nodeType === 9 ? a.documentElement : a,
608+
bup = b && b.parentNode;
609+
return a === bup || !!( bup && bup.nodeType === 1 && (
610+
adown.contains ?
611+
adown.contains( bup ) :
612+
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
613+
));
614+
} :
615+
function( a, b ) {
616+
if ( b ) {
617+
while ( (b = b.parentNode) ) {
618+
if ( b === a ) {
619+
return true;
620+
}
621+
}
622+
}
623+
return false;
624+
};
606625

607-
events.mouseenter = [];
608-
events.mouseleave = [];
626+
events[type] = [];
627+
628+
// Refer to jQuery's implementation of mouseenter & mouseleave
629+
// Read about mouseenter and mouseleave:
630+
// http://www.quirksmode.org/js/events_mouse.html#link8
631+
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
632+
bindFn(element, eventmap[type], function(event) {
633+
var ret, target = this, related = event.relatedTarget;
634+
// For mousenter/leave call the handler if related is outside the target.
635+
// NB: No relatedTarget if the mouse left/entered the browser window
636+
if ( !related || (related !== target && !contains(target, related)) ){
637+
handle(event, type);
638+
}
609639

610-
bindFn(element, 'mouseover', function(event) {
611-
counter++;
612-
if (counter == 1) {
613-
handle(event, 'mouseenter');
614-
}
615-
});
616-
bindFn(element, 'mouseout', function(event) {
617-
counter --;
618-
if (counter == 0) {
619-
handle(event, 'mouseleave');
620-
}
621640
});
641+
622642
} else {
623643
addEventListenerFn(element, type, handle);
624644
events[type] = [];

test/jqLiteSpec.js

+37-12
Original file line numberDiff line numberDiff line change
@@ -775,13 +775,9 @@ describe('jqLite', function() {
775775

776776
parent.bind('mouseenter', function() { log += 'parentEnter;'; });
777777
parent.bind('mouseleave', function() { log += 'parentLeave;'; });
778-
parent.mouseover = function() { browserTrigger(parent, 'mouseover'); };
779-
parent.mouseout = function() { browserTrigger(parent, 'mouseout'); };
780778

781779
child.bind('mouseenter', function() { log += 'childEnter;'; });
782780
child.bind('mouseleave', function() { log += 'childLeave;'; });
783-
child.mouseover = function() { browserTrigger(child, 'mouseover'); };
784-
child.mouseout = function() { browserTrigger(child, 'mouseout'); };
785781
});
786782

787783
afterEach(function() {
@@ -790,20 +786,49 @@ describe('jqLite', function() {
790786

791787
it('should fire mouseenter when coming from outside the browser window', function() {
792788
if (window.jQuery) return;
793-
parent.mouseover();
789+
var browserMoveTrigger = function(from, to){
790+
var fireEvent = function(type, element, relatedTarget){
791+
var msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
792+
if (msie < 9){
793+
var evnt = document.createEventObject();
794+
evnt.srcElement = element;
795+
evnt.relatedTarget = relatedTarget;
796+
element.fireEvent('on' + type, evnt);
797+
return;
798+
};
799+
var evnt = document.createEvent('MouseEvents'),
800+
originalPreventDefault = evnt.preventDefault,
801+
appWindow = window,
802+
fakeProcessDefault = true,
803+
finalProcessDefault;
804+
805+
evnt.preventDefault = function() {
806+
fakeProcessDefault = false;
807+
return originalPreventDefault.apply(evnt, arguments);
808+
};
809+
810+
var x = 0, y = 0;
811+
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
812+
false, false, 0, relatedTarget);
813+
814+
element.dispatchEvent(evnt);
815+
};
816+
fireEvent('mouseout', from[0], to[0]);
817+
fireEvent('mouseover', to[0], from[0]);
818+
};
819+
820+
browserMoveTrigger(root, parent);
794821
expect(log).toEqual('parentEnter;');
795822

796-
child.mouseover();
797-
expect(log).toEqual('parentEnter;childEnter;');
798-
child.mouseover();
823+
browserMoveTrigger(parent, child);
799824
expect(log).toEqual('parentEnter;childEnter;');
800825

801-
child.mouseout();
802-
expect(log).toEqual('parentEnter;childEnter;');
803-
child.mouseout();
826+
browserMoveTrigger(child, parent);
804827
expect(log).toEqual('parentEnter;childEnter;childLeave;');
805-
parent.mouseout();
828+
829+
browserMoveTrigger(parent, root);
806830
expect(log).toEqual('parentEnter;childEnter;childLeave;parentLeave;');
831+
807832
});
808833
});
809834
});

0 commit comments

Comments
 (0)