aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgockxml2013-04-29 18:14:40 +0100
committerPete Bacon Darwin2013-04-29 18:26:32 +0100
commit06f2b2a8cf7e8216ad9ef05f73426271c2d97faa (patch)
treee9890deaf8d9f4f66868df33cda43cb9ec34585a
parent0985a37376314616ac2b777bddd8bc07e1be7af7 (diff)
downloadangular.js-06f2b2a8cf7e8216ad9ef05f73426271c2d97faa.tar.bz2
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
-rw-r--r--src/jqLite.js48
-rw-r--r--test/jqLiteSpec.js49
2 files changed, 71 insertions, 26 deletions
diff --git a/src/jqLite.js b/src/jqLite.js
index cf63c5df..0abae3d7 100644
--- a/src/jqLite.js
+++ b/src/jqLite.js
@@ -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] = [];
diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js
index b1fa6b05..089ae78c 100644
--- a/test/jqLiteSpec.js
+++ b/test/jqLiteSpec.js
@@ -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() {
@@ -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;');
+
});
});
});