Skip to main content

Bug Tracker

Side navigation

#9747 closed bug (invalid)

Opened July 05, 2011 06:47PM UTC

Closed July 05, 2011 08:15PM UTC

Last modified July 06, 2011 04:19PM UTC

:first selector behaves inconsistently in chained find() invocations

Reported by: hallettj Owned by:
Priority: low Milestone: 1.next
Component: selector Version: 1.6.1
Keywords: Cc:
Blocked by: Blocking:
Description

Affects every version of jQuery that I tested: 1.2.6, 1.3.2, 1.4.4, 1.5.2, 1.6.1.

Reproduced in Chrome beta under Ubuntu 11.04.

Steps to reproduce:

1. Create HTML structure with multiple elements, such as a list.

3. Construct a jQuery selector that selects some or all of those elements, such as $('li').

2. Use a find() invocation on the previously selected elements with a selector that matches some nested element and include the expression ":first" in that selector. For example, $('li').find('a:first').

Expected results:

The first matched nested element is returned from the find() invocation. In the case of a list where nested elements are single anchor tags, the anchor tag in the first list element is returned.

Actual results:

Every nested element matching the given selectors is returned. It is as though ":first" has no effect on result of the find() invocation.

jsfiddle test case:

If I combine both selectors into one jQuery() call instead of using a chained find() invocation then the result is as I expect. For example, $('li a:first') returns a single anchor tag: the one in the first list item. But $('li').find('a:first') returns every anchor tag in every list item - assuming that each list item has at most one anchor tag.

Maybe this is intended behavior. But it is really not what I expected. If this is intended behavior it would be helpful to have a note in the documentation for :first, :eq, or find() that explains the discrepancy.

Attachments (0)
Change History (7)

Changed July 05, 2011 08:15PM UTC by rwaldron comment:1

component: unfiledselector
priority: undecidedlow
resolution: → invalid
status: newclosed

I reduced your test even further and there is nothing inconsistent about it: http://jsfiddle.net/rwaldron/XBM2w/

Changed July 06, 2011 12:23AM UTC by hallettj comment:2

Replying to [comment:1 rwaldron]:

I reduced your test even further and there is nothing inconsistent about it: http://jsfiddle.net/rwaldron/XBM2w/

The inconsistency is that I expected $(selectorA + " " + selectorB) to be equivalent to $(selectorA).find(selectorB) in every case. Finding out that that is not true is very surprising. If this is really not the way that find() is supposed to work there should be an explanation in the documentation as the current behavior is not obvious.

Changed July 06, 2011 03:54AM UTC by rwaldron comment:3

The description in the api is quite clear:

Description: Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.

Changed July 06, 2011 07:31AM UTC by hallettj comment:4

_comment0: What is not clear is the scope of the selectors in the find() call. Consider that, at least in a case where every 'li' has an 'a' descendant, $('li a:first') and $('li:first a') produce the same results. This implies that ':first' scopes over the entire result set, not just to the results of traversing an individual 'li' tag. So when I run $('li').find('a:first') I expect ':first' to limit the entire result set to one element. But instead I get three elements. This time ':first' *is* scoped to the results of traversing an individual 'li' tag. To my view the scope of ':first' is inexplicably inverted when used in find(). \ \ I understand now that find() probably makes separate selections against the descendants of each 'li' tag and that ':first' is probably scoped to the results each individual selection. But I was only able to come to that conclusion because of my investigation of find() behavior for this issue report. \ \ The documentation for ':eq()' says, \ \ Select the element at index n within the matched set. \ \ It does not give any indication that in the case of find() there will be a separate "matched set" corresponding to each element in the original selection, and that ':eq()' will apply to each such set individually before those sets are unioned and returned. But the actual behavior of ':first' and ':eq()' in find() seems to indicate that this is a more accurate description. \ \ I do not believe that the docs provide any information to clarify this point. Furthermore, in my opinion the behavior as it is implemented is counter-intuitive and is a bug.1309937654279707

What is not clear is the scope of the selectors in the find() call. Consider that, at least in a case where every 'li' has an 'a' descendant, $('li a:first') and $('li:first a') produce the same results. This implies that ':first' scopes over the entire result set, not just to the results of traversing an individual 'li' tag. So when I run $('li').find('a:first') I expect ':first' to limit the entire result set to one element. But instead I get three elements. This time ':first' *is* scoped to the results of traversing an individual 'li' tag. To my view the scope of ':first' is inexplicably inverted when used in find().

I understand now that find() probably makes separate selections against the descendants of each 'li' tag and that ':first' is probably scoped to the results each individual selection. But I was only able to come to that conclusion because of my investigation of find() behavior for this issue report.

The documentation for ':eq()' says,

Select the element at index n within the matched set.

It does not give any indication that in the case of find() there will be a separate "matched set" corresponding to each element in the original selection, and that ':eq()' will apply to each such set individually before those sets are unioned and returned. But the actual behavior of ':first' and ':eq()' in find() seems to indicate that this is a more accurate description.

I do not believe that the docs provide any information to clarify this point. Furthermore, in my opinion the behavior as it is implemented is counter-intuitive and is a bug.

Changed July 06, 2011 01:40PM UTC by rwaldron comment:5

description: Affects every version of jQuery that I tested: 1.2.6, 1.3.2, 1.4.4, 1.5.2, 1.6.1. \ \ Reproduced in Chrome 13.0.782.32 beta under Ubuntu 11.04. \ \ Steps to reproduce: \ \ 1. Create HTML structure with multiple elements, such as a list. \ 3. Construct a jQuery selector that selects some or all of those elements, such as $('li'). \ 2. Use a find() invocation on the previously selected elements with a selector that matches some nested element and include the expression ":first" in that selector. For example, $('li').find('a:first'). \ \ Expected results: \ \ The first matched nested element is returned from the find() invocation. In the case of a list where nested elements are single anchor tags, the anchor tag in the first list element is returned. \ \ Actual results: \ \ Every nested element matching the given selectors is returned. It is as though ":first" has no effect on result of the find() invocation. \ \ jsfiddle test case: http://jsfiddle.net/hallettj/q9zfd/1/ \ \ If I combine both selectors into one jQuery() call instead of using a chained find() invocation then the result is as I expect. For example, `$('li a:first')` returns a single anchor tag: the one in the first list item. But `$('li').find('a:first')` returns every anchor tag in every list item - assuming that each list item has at most one anchor tag. \ \ Maybe this is intended behavior. But it is really not what I expected. If this is intended behavior it would be helpful to have a note in the documentation for :first, :eq, or find() that explains the discrepancy.Affects every version of jQuery that I tested: 1.2.6, 1.3.2, 1.4.4, 1.5.2, 1.6.1. \ \ Reproduced in Chrome beta under Ubuntu 11.04. \ \ Steps to reproduce: \ \ 1. Create HTML structure with multiple elements, such as a list. \ 3. Construct a jQuery selector that selects some or all of those elements, such as $('li'). \ 2. Use a find() invocation on the previously selected elements with a selector that matches some nested element and include the expression ":first" in that selector. For example, $('li').find('a:first'). \ \ Expected results: \ \ The first matched nested element is returned from the find() invocation. In the case of a list where nested elements are single anchor tags, the anchor tag in the first list element is returned. \ \ Actual results: \ \ Every nested element matching the given selectors is returned. It is as though ":first" has no effect on result of the find() invocation. \ \ jsfiddle test case: \ \ If I combine both selectors into one jQuery() call instead of using a chained find() invocation then the result is as I expect. For example, `$('li a:first')` returns a single anchor tag: the one in the first list item. But `$('li').find('a:first')` returns every anchor tag in every list item - assuming that each list item has at most one anchor tag. \ \ Maybe this is intended behavior. But it is really not what I expected. If this is intended behavior it would be helpful to have a note in the documentation for :first, :eq, or find() that explains the discrepancy.
keywords: → needsdocs

Just because you says it's a bug, does not make it a bug.

Changed July 06, 2011 03:00PM UTC by dmethvin comment:6

_comment0: > Consider that, at least in a case where every 'li' has an 'a' descendant, $('li a:first') and $('li:first a') produce the same results. This implies that ':first' scopes over the entire result set, not just to the results of traversing an individual 'li' tag. \ \ No it doesn't. Look at the case where the first 'li' does not have an 'a' descendant. http://jsfiddle.net/dmethvin/PSnPQ/ \ \ > To my view the scope of ':first' is inexplicably inverted when used in find(). \ \ The `find()` method finds one "first" in the matched set that it works on, assuming there is one at all. \ \ \ In any case, it's worked this way since jQuery 1.0 and we've never had any other confusion about it, so I'm going to remove the `needsdocs`. Any attempt to explain would need to be duplicated across all the positional selectors (eq, lt, gt, first, last) and IMO only make the docs more difficult to understand. 1309964469020913
keywords: needsdocs
Consider that, at least in a case where every 'li' has an 'a' descendant, $('li a:first') and $('li:first a') produce the same results. This implies that ':first' scopes over the entire result set, not just to the results of traversing an individual 'li' tag.

No it doesn't. Look at the case where the first 'li' does not have an 'a' descendant. http://jsfiddle.net/dmethvin/PSnPQ/

To my view the scope of ':first' is inexplicably inverted when used in find().

The find() method finds one "first" in each element of the matched set that it works on, assuming there is one at all.

In any case, it's worked this way since jQuery 1.0 and we've never had any other confusion about it, so I'm going to remove the needsdocs. Any attempt to explain would need to be duplicated across all the positional selectors (eq, lt, gt, first, last) and IMO only make the docs more difficult to understand.

Changed July 06, 2011 04:19PM UTC by rwaldron comment:7

Dave, while I'm 100% in agreement with you, what do you think about a single page that offers insight as you have above re: positional selectors. That way we could link all of those docs to a single reference?