Skip to main content

Bug Tracker

Side navigation

#11269 closed bug (invalid)

Opened February 01, 2012 06:49PM UTC

Closed February 01, 2012 06:51PM UTC

Last modified February 01, 2012 07:43PM UTC

Event delegation with .on() will not work when a jQuery object is supplied for the Selector parameter

Reported by: warrenparsons Owned by:
Priority: undecided Milestone: None
Component: unfiled Version: 1.7.1
Keywords: Cc:
Blocked by: Blocking:
Description

Event delegation with .on() will not work when a jQuery object is supplied for the Selector parameter.

It seems to ignore the parameter in this case, and skips delegation altogether. The result is that 'this' refers to the object to which .on() is attached, rather than the element(s) specified in the Selector param.

This may be intended behavior, but it is somewhat unexpected.

Test cases: http://jsfiddle.net/tUkwZ/1/

Tested with: 1.7.1 (included with test case), 1.7.2b1 (tested locally)

Verified in: Chrome (current) and Firefox 9.0.1 - both Mac

Attachments (0)
Change History (7)

Changed February 01, 2012 06:51PM UTC by dmethvin comment:1

resolution: → invalid
status: newclosed

Yep, as documented it must be a selector string. If you are going to select the elements in advance you might as well bind them.

Changed February 01, 2012 07:09PM UTC by warrenparsons comment:2

_comment0: Hi Dave, \ \ I think the test case didn't make the problem I'm seeing clear enough. \ \ In my real-world use case, I need to delegate because the elements in question are sometimes added to the document after page load, and it would be useful to pass in a jQuery object as my selector is rather complex. \ \ Here is the selector I'm using: \ \ \ {{{ \ var $links = $('a[href^="http"], a[rel="new-window"]').not('.colorbox, .addthis-button'); \ }}} \ \ This would be pretty ugly to write as a pure CSS selector. Also, the API docs for the :not() selector ([http://api.jquery.com/not-selector/]) include the following: \ \ ''The .not() method will end up providing you with more readable selections than pushing complex selectors or variables into a :not() selector filter. In most cases, it is a better choice.'' \ \ Being able to pass in a jQuery object rather than only a string can, in cases like this, make code more readable. It also has potential benefits when selectors are dynamically constructed, since they can be built with jQuery objects rather than put together as a string. \ \ Thanks for listening.1328123403308982
_comment1: Hi Dave, \ \ I think the test case didn't make the problem I'm seeing clear enough. \ \ In my real-world use case, I need to delegate because the elements in question are sometimes added to the document after page load, and it would be useful to pass in a jQuery object, as my selector is rather complex. \ \ Here is the selector I'm using: \ \ \ {{{ \ var $links = $('a[href^="http"], a[rel="new-window"]').not('.colorbox, .addthis-button'); \ }}} \ \ This would be pretty ugly to write as a pure CSS selector. Also, the API docs for the :not() selector ([http://api.jquery.com/not-selector/]) include the following: \ \ ''The .not() method will end up providing you with more readable selections than pushing complex selectors or variables into a :not() selector filter. In most cases, it is a better choice.'' \ \ Being able to pass in a jQuery object rather than only a string can, in cases like this, make code more readable. It also has potential benefits when selectors are dynamically constructed, since they can be built with jQuery objects rather than put together as a string. \ \ Thanks for listening.1328124135264803

Realized I probably need to reply, rather than just add a comment.

Changed February 01, 2012 07:22PM UTC by warrenparsons comment:3

Replying to [comment:1 dmethvin]:

Yep, as documented it must be a selector string. If you are going to select the elements in advance you might as well bind them.

Hi Dave,

I think the test case didn't make the problem I'm seeing clear enough.

In my real-world use case, I need to delegate because the elements in question are sometimes added to the document after page load, and it would be useful to pass in a jQuery object, as my selector is rather complex.

Here is the selector I'm using:

var $links = $('a[href^="http"], a[rel="new-window"]').not('.colorbox, .addthis-button');

This would be pretty ugly to write as a pure CSS selector. Also, the API docs for the :not() selector (http://api.jquery.com/not-selector/) include the following:

''The .not() method will end up providing you with more readable selections than pushing complex selectors or variables into a :not() selector filter. In most cases, it is a better choice.''

Being able to pass in a jQuery object rather than only a string can, in cases like this, make code more readable. It also has potential benefits when selectors are dynamically constructed, since they can be built with jQuery objects rather than put together as a string.

Thanks for listening.

Changed February 01, 2012 07:31PM UTC by dmethvin comment:4

You're misunderstanding the point at which a selector selects. Once your $links variable is set, it contains specific DOM elements. If you add more to the document later, they will not be in $links. The selector string was only used to obtain the DOM elements that are now in $links. It does not represent the set of elements that currently exist in the document matching the selector string originally passed to $() at some point in the past. The reason a selector string is required is so that we can determine if the element *currently* matches the selector when an event occurs. If you do not want to repeat the long selector, just assign the selector string to a variable.

Changed February 01, 2012 07:35PM UTC by warrenparsons comment:5

Replying to [comment:4 dmethvin]:

Crap. You're right of course.

Sorry for the false alarm.

Changed February 01, 2012 07:39PM UTC by dmethvin comment:6

_comment0: And actually, for a complex case like that I would just do the delegation manually. Directly bind an event to a parent and manually check whether the complex criteria are met, e.g., \ {{{ \ var $elem = $(event.target); \ if ( this.nodeName.toLowerCase() != "a" ) \ return; \ if ( $elem.hasClass("colorbox") || $elem.hasClass("addthis-button") ) \ return; \ ... etc ... \ }}} \ 1328125213270135

And actually, for a complex case like that I would just do the delegation manually. Directly bind an event to a parent and manually check whether the complex criteria are met, e.g.,

var $elem = $(event.target);
if ( event.target.nodeName.toLowerCase() != "a" )
  return;
if ( $elem.hasClass("colorbox") || $elem.hasClass("addthis-button") )
  return;
... etc ...

Changed February 01, 2012 07:43PM UTC by warrenparsons comment:7

Replying to [comment:6 dmethvin]:

And actually, for a complex case like that I would just do the delegation manually. Directly bind an event to a parent and manually check whether the complex criteria are met, e.g.,
> var $elem = $(event.target);
> if ( event.target.nodeName.toLowerCase() != "a" )
>   return;
> if ( $elem.hasClass("colorbox") || $elem.hasClass("addthis-button") )
>   return;
> ... etc ...
> 

That's a good call.

In my (somewhat sheepish) defense, I wrote the core of this function three years ago, without any attempt at delegation. I've let it evolve over time, allowing me to overlook its incompatibility with delegation as currently written, when I should instead have just rewritten it from scratch.

Thanks.