From 9aa0c69c43bad9fce5ef7732692308afb2a38ec6 Mon Sep 17 00:00:00 2001 From: John Resig Date: Mon, 9 Feb 2009 23:29:57 +0000 Subject: [PATCH] Fixed bubbling of live events (if an inner element handles an event first - and stops progatation - then the parent event doesn't encounter the event). Thanks to Irae for the patch. Fixes bug #3980. --- src/core.js | 8 ++++++-- src/event.js | 6 +++++- test/index.html | 5 +++++ test/unit/event.js | 25 ++++++++++++++++++++++++- test/unit/selector.js | 6 +++--- 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/core.js b/src/core.js index 3a9eaa7de..ac8ec6bc4 100644 --- a/src/core.js +++ b/src/core.js @@ -346,14 +346,18 @@ jQuery.fn = jQuery.prototype = { }, closest: function( selector ) { - var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null; + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, + closer = 0; return this.map(function(){ var cur = this; while ( cur && cur.ownerDocument ) { - if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { + jQuery.data(cur, "closest", closer); return cur; + } cur = cur.parentNode; + closer++; } }); }, diff --git a/src/event.js b/src/event.js index a621eb93e..e707015c1 100644 --- a/src/event.js +++ b/src/event.js @@ -571,9 +571,13 @@ function liveHandler( event ){ } }); + elems.sort(function(a,b) { + return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest"); + }); + jQuery.each(elems, function(){ if ( this.fn.call(this.elem, event, this.fn.data) === false ) - stop = false; + return (stop = false); }); return stop; diff --git a/test/index.html b/test/index.html index 89244d7da..2c3545f0f 100644 --- a/test/index.html +++ b/test/index.html @@ -213,6 +213,11 @@ Z ...Eat some funyuns... ...Eat some funyuns... + +
+ + +
diff --git a/test/unit/event.js b/test/unit/event.js index 4d2b0aafe..1da9b5906 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -474,7 +474,7 @@ test("toggle(Function, Function, ...)", function() { }); test(".live()/.die()", function() { - expect(42); + expect(46); var submit = 0, div = 0, livea = 0, liveb = 0; @@ -611,6 +611,29 @@ test(".live()/.die()", function() { // Cleanup jQuery("#nothiddendivchild").die("click"); + + // Verify that .live() ocurs and cancel buble in the same order as + // we would expect .bind() and .click() without delegation + var lived = 0, livee = 0; + + // bind one pair in one order + jQuery('span#liveSpan1 a').live('click', function(){ lived++; return false; }); + jQuery('span#liveSpan1').live('click', function(){ livee++; }); + + jQuery('span#liveSpan1 a').click(); + equals( lived, 1, "Verify that only one first handler occurred." ); + equals( livee, 0, "Verify that second handler don't." ); + + // and one pair in inverse + jQuery('#liveHandlerOrder span#liveSpan2').live('click', function(){ livee++; }); + jQuery('#liveHandlerOrder span#liveSpan2 a').live('click', function(){ lived++; return false; }); + + jQuery('span#liveSpan2 a').click(); + equals( lived, 2, "Verify that only one first handler occurred." ); + equals( livee, 0, "Verify that second handler don't." ); + + // Cleanup + jQuery("span#liveSpan1 a, span#liveSpan1, span#liveSpan2 a, span#liveSpan2").die("click"); }); /* diff --git a/test/unit/selector.js b/test/unit/selector.js index 924f41d0e..f7c39121c 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -189,7 +189,7 @@ test("child and adjacent", function() { reset(); t( "Last Child", "p:last-child", ["sap"] ); - t( "Last Child", "a:last-child", ["simon1","anchor1","mark","yahoo","anchor2","simon"] ); + t( "Last Child", "a:last-child", ["simon1","anchor1","mark","yahoo","anchor2","simon","liveLink1","liveLink2"] ); t( "Nth-child", "#main form#form > *:nth-child(2)", ["text1"] ); t( "Nth-child", "#main form#form > :nth-child(2)", ["text1"] ); @@ -278,7 +278,7 @@ test("pseudo (:) selectors", function() { expect(53); t( "First Child", "p:first-child", ["firstp","sndp"] ); t( "Last Child", "p:last-child", ["sap"] ); - t( "Only Child", "a:only-child", ["simon1","anchor1","yahoo","anchor2"] ); + t( "Only Child", "a:only-child", ["simon1","anchor1","yahoo","anchor2","liveLink1","liveLink2"] ); t( "Empty", "ul:empty", ["firstUL"] ); t( "Enabled UI Element", "#form input:not([type=hidden]):enabled", ["text1","radio1","radio2","check1","check2","hidden2","name"] ); t( "Disabled UI Element", "#form input:disabled", ["text2"] ); @@ -290,7 +290,7 @@ test("pseudo (:) selectors", function() { t( "Text Contains", "a:contains('Google Groups (Link)')", ["groups"] ); t( "Text Contains", "a:contains('(Link)')", ["groups"] ); - t( "Element Preceded By", "p ~ div", ["foo","fx-queue","fx-tests", "moretests","tabindex-tests"] ); + t( "Element Preceded By", "p ~ div", ["foo","fx-queue","fx-tests", "moretests","tabindex-tests", "liveHandlerOrder"] ); t( "Not", "a.blog:not(.link)", ["mark"] ); t( "Not - multiple", "#form option:not(:contains('Nothing'),#option1b,:selected)", ["option1c", "option1d", "option2b", "option2c", "option3d", "option3e"] ); //t( "Not - complex", "#form option:not([id^='opt']:nth-child(-n+3))", [ "option1a", "option1d", "option2d", "option3d", "option3e"] );