Skip to content
Permalink
Browse files
Make sure that live events bubble unless explicitly told not to, like…
… a normal event. Fixes #6182.
  • Loading branch information
jeresig committed Feb 26, 2010
1 parent 7e6b20e commit 26b0e91
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 36 deletions.
@@ -984,8 +984,8 @@ jQuery.each(["live", "die"], function( i, name ) {
});

function liveHandler( event ) {
var stop, elems = [], selectors = [], args = arguments,
related, match, handleObj, elem, j, i, l, data,
var stop, maxLevel, elems = [], selectors = [],
related, match, handleObj, elem, j, i, l, data, close,
events = jQuery.data( this, "events" );

// Make sure we avoid non-left-click bubbling in Firefox (#3861)
@@ -1011,11 +1011,13 @@ function liveHandler( event ) {
match = jQuery( event.target ).closest( selectors, event.currentTarget );

for ( i = 0, l = match.length; i < l; i++ ) {
close = match[i];

for ( j = 0; j < live.length; j++ ) {
handleObj = live[j];

if ( match[i].selector === handleObj.selector ) {
elem = match[i].elem;
if ( close.selector === handleObj.selector ) {
elem = close.elem;
related = null;

// Those two events require additional checking
@@ -1024,21 +1026,31 @@ function liveHandler( event ) {
}

if ( !related || related !== elem ) {
elems.push({ elem: elem, handleObj: handleObj });
elems.push({ elem: elem, handleObj: handleObj, level: close.level });
}
}
}
}

for ( i = 0, l = elems.length; i < l; i++ ) {
match = elems[i];

if ( maxLevel && match.level > maxLevel ) {
break;
}

event.currentTarget = match.elem;
event.data = match.handleObj.data;
event.handleObj = match.handleObj;

if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) {
stop = false;
break;
ret = match.handleObj.origHandler.apply( match.elem, arguments );

if ( ret === false || event.isPropagationStopped() ) {
maxLevel = match.level;

if ( ret === false ) {
stop = false;
}
}
}

@@ -82,7 +82,7 @@ jQuery.fn.extend({

closest: function( selectors, context ) {
if ( jQuery.isArray( selectors ) ) {
var ret = [], cur = this[0], match, matches = {}, selector;
var ret = [], cur = this[0], match, matches = {}, selector, level = 1;

if ( cur && selectors.length ) {
for ( var i = 0, l = selectors.length; i < l; i++ ) {
@@ -100,11 +100,11 @@ jQuery.fn.extend({
match = matches[selector];

if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
ret.push({ selector: selector, elem: cur });
delete matches[selector];
ret.push({ selector: selector, elem: cur, level: level });
}
}
cur = cur.parentNode;
level++;
}
}

@@ -799,7 +799,7 @@ test(".live()/.die()", function() {
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 0, "Click on inner div" );
equals( div, 1, "Click on inner div" );
equals( div, 2, "Click on inner div" );
equals( livea, 1, "Click on inner div" );
equals( liveb, 1, "Click on inner div" );

@@ -815,7 +815,7 @@ test(".live()/.die()", function() {
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 0, "die Click on inner div" );
equals( div, 1, "die Click on inner div" );
equals( div, 2, "die Click on inner div" );
equals( livea, 1, "die Click on inner div" );
equals( liveb, 1, "die Click on inner div" );

@@ -824,7 +824,7 @@ test(".live()/.die()", function() {
jQuery("div#nothiddendivchild").die("click");
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 0, "die Click on inner div" );
equals( div, 1, "die Click on inner div" );
equals( div, 2, "die Click on inner div" );
equals( livea, 1, "die Click on inner div" );
equals( liveb, 0, "die Click on inner div" );

@@ -842,7 +842,7 @@ test(".live()/.die()", function() {
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 0, "stopPropagation Click on inner div" );
equals( div, 1, "stopPropagation Click on inner div" );
equals( livea, 1, "stopPropagation Click on inner div" );
equals( livea, 0, "stopPropagation Click on inner div" );
equals( liveb, 1, "stopPropagation Click on inner div" );

// Make sure click events only fire with primary click
@@ -1252,62 +1252,70 @@ test(".delegate()/.undelegate()", function() {
equals( liveb, 0, "Click on body" );

// This should trigger two events
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendiv").trigger("click");
equals( submit, 0, "Click on div" );
equals( div, 1, "Click on div" );
equals( livea, 1, "Click on div" );
equals( liveb, 0, "Click on div" );

// This should trigger three events (w/ bubbling)
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 0, "Click on inner div" );
equals( div, 2, "Click on inner div" );
equals( livea, 2, "Click on inner div" );
equals( livea, 1, "Click on inner div" );
equals( liveb, 1, "Click on inner div" );

// This should trigger one submit
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("submit");
equals( submit, 1, "Submit on div" );
equals( div, 2, "Submit on div" );
equals( livea, 2, "Submit on div" );
equals( liveb, 1, "Submit on div" );
equals( div, 0, "Submit on div" );
equals( livea, 0, "Submit on div" );
equals( liveb, 0, "Submit on div" );

// Make sure no other events were removed in the process
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 1, "undelegate Click on inner div" );
equals( div, 3, "undelegate Click on inner div" );
equals( livea, 3, "undelegate Click on inner div" );
equals( liveb, 2, "undelegate Click on inner div" );
equals( submit, 0, "undelegate Click on inner div" );
equals( div, 2, "undelegate Click on inner div" );
equals( livea, 1, "undelegate Click on inner div" );
equals( liveb, 1, "undelegate Click on inner div" );

// Now make sure that the removal works
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("#body").undelegate("div#nothiddendivchild", "click");
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 1, "undelegate Click on inner div" );
equals( div, 4, "undelegate Click on inner div" );
equals( livea, 4, "undelegate Click on inner div" );
equals( liveb, 2, "undelegate Click on inner div" );
equals( submit, 0, "undelegate Click on inner div" );
equals( div, 2, "undelegate Click on inner div" );
equals( livea, 1, "undelegate Click on inner div" );
equals( liveb, 0, "undelegate Click on inner div" );

// Make sure that the click wasn't removed too early
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("div#nothiddendiv").trigger("click");
equals( submit, 1, "undelegate Click on inner div" );
equals( div, 5, "undelegate Click on inner div" );
equals( livea, 5, "undelegate Click on inner div" );
equals( liveb, 2, "undelegate Click on inner div" );
equals( submit, 0, "undelegate Click on inner div" );
equals( div, 1, "undelegate Click on inner div" );
equals( livea, 1, "undelegate Click on inner div" );
equals( liveb, 0, "undelegate Click on inner div" );

// Make sure that stopPropgation doesn't stop live events
submit = 0, div = 0, livea = 0, liveb = 0;
jQuery("#body").delegate("div#nothiddendivchild", "click", function(e){ liveb++; e.stopPropagation(); });
jQuery("div#nothiddendivchild").trigger("click");
equals( submit, 1, "stopPropagation Click on inner div" );
equals( div, 6, "stopPropagation Click on inner div" );
equals( livea, 6, "stopPropagation Click on inner div" );
equals( liveb, 3, "stopPropagation Click on inner div" );
equals( submit, 0, "stopPropagation Click on inner div" );
equals( div, 1, "stopPropagation Click on inner div" );
equals( livea, 0, "stopPropagation Click on inner div" );
equals( liveb, 1, "stopPropagation Click on inner div" );

// Make sure click events only fire with primary click
submit = 0, div = 0, livea = 0, liveb = 0;
var event = jQuery.Event("click");
event.button = 1;
jQuery("div#nothiddendiv").trigger(event);

equals( livea, 6, "delegate secondary click" );
equals( livea, 0, "delegate secondary click" );

jQuery("#body").undelegate("div#nothiddendivchild", "click");
jQuery("#body").undelegate("div#nothiddendiv", "click");

2 comments on commit 26b0e91

@jitter
Copy link
Contributor

@jitter jitter commented on 26b0e91 Sep 27, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't removing the delete matches[selector]; break the closest functionality when passing in an array for selector? E.g.


$("#b").closest(['div']).length // == 2 now instead of 1

Now closest returns an array with 2 entries instead of one.

I got here because the changes in 8c41325 in particular the use of jQuery.unique(ret) broke some of the event-unit-tests. The idea to use jQuery.unique(ret) was ok to fix #6182 but as now closest returns more then one element when an array is passed in and unique sorts these elements in document-order this breaks the live-event-bubbling as the bubbling now happens top-down instead of bottom-up.

Check the test/unit/events.js test event: .live()/.die() and event: .delegate()/.undelegate()

@jeresig
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of that line was intentional - it makes it so that .closest(Array) returns all of the nodes up the tree that match those selectors -- of course this also makes the unique/sort change very much un-intentional as it breaks the desired functionality of that particular method. I've backed it out in commit 7be1120 (and added another test that covers the use case you pointed out). Thanks again!

Please sign in to comment.