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

Commit

Permalink
fix(jqLite): correct implementation of mouseenter/mouseleave event
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gockxml authored and petebacondarwin committed Apr 29, 2013
1 parent 0985a37 commit 06f2b2a
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 26 deletions.
48 changes: 34 additions & 14 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,23 +607,43 @@ forEach({

if (!eventFns) {
if (type == 'mouseenter' || type == 'mouseleave') {
var counter = 0;
var contains = document.body.contains || document.body.compareDocumentPosition ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};

events.mouseenter = [];
events.mouseleave = [];
events[type] = [];

// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
bindFn(element, eventmap[type], function(event) {
var ret, 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 && !contains(target, related)) ){
handle(event, type);
}

bindFn(element, 'mouseover', function(event) {
counter++;
if (counter == 1) {
handle(event, 'mouseenter');
}
});
bindFn(element, 'mouseout', function(event) {
counter --;
if (counter == 0) {
handle(event, 'mouseleave');
}
});

} else {
addEventListenerFn(element, type, handle);
events[type] = [];
Expand Down
49 changes: 37 additions & 12 deletions test/jqLiteSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,13 +775,9 @@ describe('jqLite', function() {

parent.bind('mouseenter', function() { log += 'parentEnter;'; });
parent.bind('mouseleave', function() { log += 'parentLeave;'; });
parent.mouseover = function() { browserTrigger(parent, 'mouseover'); };
parent.mouseout = function() { browserTrigger(parent, 'mouseout'); };

child.bind('mouseenter', function() { log += 'childEnter;'; });
child.bind('mouseleave', function() { log += 'childLeave;'; });
child.mouseover = function() { browserTrigger(child, 'mouseover'); };
child.mouseout = function() { browserTrigger(child, 'mouseout'); };
});

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

it('should fire mouseenter when coming from outside the browser window', function() {
if (window.jQuery) return;
parent.mouseover();
var browserMoveTrigger = function(from, to){
var fireEvent = function(type, element, relatedTarget){
var msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
if (msie < 9){
var evnt = document.createEventObject();
evnt.srcElement = element;
evnt.relatedTarget = relatedTarget;
element.fireEvent('on' + type, evnt);
return;
};
var evnt = document.createEvent('MouseEvents'),
originalPreventDefault = evnt.preventDefault,
appWindow = window,
fakeProcessDefault = true,
finalProcessDefault;

evnt.preventDefault = function() {
fakeProcessDefault = false;
return originalPreventDefault.apply(evnt, arguments);
};

var x = 0, y = 0;
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
false, false, 0, relatedTarget);

element.dispatchEvent(evnt);
};
fireEvent('mouseout', from[0], to[0]);
fireEvent('mouseover', to[0], from[0]);
};

browserMoveTrigger(root, parent);
expect(log).toEqual('parentEnter;');

child.mouseover();
expect(log).toEqual('parentEnter;childEnter;');
child.mouseover();
browserMoveTrigger(parent, child);
expect(log).toEqual('parentEnter;childEnter;');

child.mouseout();
expect(log).toEqual('parentEnter;childEnter;');
child.mouseout();
browserMoveTrigger(child, parent);
expect(log).toEqual('parentEnter;childEnter;childLeave;');
parent.mouseout();

browserMoveTrigger(parent, root);
expect(log).toEqual('parentEnter;childEnter;childLeave;parentLeave;');

});
});
});
Expand Down

0 comments on commit 06f2b2a

Please sign in to comment.