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

Commit 06f2b2a

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 0985a37 commit 06f2b2a

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
@@ -607,23 +607,43 @@ forEach({
607607

608608
if (!eventFns) {
609609
if (type == 'mouseenter' || type == 'mouseleave') {
610-
var counter = 0;
610+
var contains = document.body.contains || document.body.compareDocumentPosition ?
611+
function( a, b ) {
612+
var adown = a.nodeType === 9 ? a.documentElement : a,
613+
bup = b && b.parentNode;
614+
return a === bup || !!( bup && bup.nodeType === 1 && (
615+
adown.contains ?
616+
adown.contains( bup ) :
617+
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
618+
));
619+
} :
620+
function( a, b ) {
621+
if ( b ) {
622+
while ( (b = b.parentNode) ) {
623+
if ( b === a ) {
624+
return true;
625+
}
626+
}
627+
}
628+
return false;
629+
};
611630

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

615-
bindFn(element, 'mouseover', function(event) {
616-
counter++;
617-
if (counter == 1) {
618-
handle(event, 'mouseenter');
619-
}
620-
});
621-
bindFn(element, 'mouseout', function(event) {
622-
counter --;
623-
if (counter == 0) {
624-
handle(event, 'mouseleave');
625-
}
626645
});
646+
627647
} else {
628648
addEventListenerFn(element, type, handle);
629649
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)