Traversing the DOM with jQuery .closest()
Asked Answered
R

4

5

What I thought would be an easy one for .closest() to handle turned out not to be (or perhaps I am making a silly mistake).

What I am trying to do is access the <label> element from the <div> with the inner text: I AM HERE:

<li>
    <label>I WANT TO ACCESS THIS ELEMENT</label>
    <div>...</div>
    <div>
        <input/>
        <input/>
        <input/>
        <div id="start">I AM HERE</div>
    </div>
</li>

My first guess would have been to try this:

$('#start').closest('label') 

But it does not return anything.

Roentgenotherapy answered 23/2, 2012 at 21:14 Comment(5)
If .closest() doesn't seem to work right why not look at the .closest() doco? Or the list of traversal methods: api.jquery.com/category/traversing/tree-traversal to find a more appropriate method...Benavidez
nnnnnn - I did look at the documentation and that is how I came up with my test. The documentation specifically says "Get the first element that matches the selector, beginning at the current element and progressing up through the DOM tree." I thought the label I am trying to access is up in the DOM tree.Roentgenotherapy
Great question! I haven't used closest() yet, but now I know I need to do so. Great example of its use.Inextricable
Sorry Allen, so many people post here instead of using the doco (or Google) and I guess I assumed you were one such. Sorry again. "Up through the DOM tree" means straight up, not sideways. I.e., parents, grandparents, great-grandparents, etc., but not aunts/uncles, cousins, siblings.Benavidez
Thanks nnnnnn. I understand what you were trying to say =) Some of us are only as good as what we can understand from the docs.Roentgenotherapy
C
14

.closest() only looks for ancestor elements to the initial selection. You want a combination of .closest() and .siblings() or .children():

//find closest parent `<li>` element and then find that element's child `<label>` element
$('#start').closest('li').children('label');

//find the closest parent `<div>` element and then find that element's sibling `<label>` element
$('#start').closest('div').siblings('label');

Update

A very fast selector would be to use .prev() twice and .parent() like this:

$('#start').parent().prev().prev();
Carbonize answered 23/2, 2012 at 21:17 Comment(11)
Thanks Jasper! Are there any big differences between your solution and the one provided by Kevin (e.g. performance)?Roentgenotherapy
@AllenLiu Anytime you can remove strings from your selectors they will perform faster. The fastest selector I provided was: $('#start').parent().prev().prev();Carbonize
@AllenLiu. Micro difference. Don't waste time on it.Kermit
@AllenLiu also I just noticed that you are making form inputs like this: <input></input, the proper way is: <input /> (self-closing tag).Carbonize
@Kermit Here is a JSPerf to show that using $('#start').parent().prev().prev(); is twice as fast as using other methods that incorporate strings: jsperf.com/jquery-string-selector-vs-function-selectorCarbonize
Nice Jasper! =) Thanks for all your help.Roentgenotherapy
While .parent().prev().prev() may save you a millisecond or two, it will need to be updated if an element is added to that li after the label, or if #start is moved. I guess that is the price of micro-optimization.Brashear
0.00001 instead of 0.00002 is not so interesting. better spent the time on the next mission...Kermit
@KevinB Indeed. I try to move all my string selectors to simpler DOM traversal methods when I move code to the production environment. jQuery Mobile is a great example of how much this can do, when the released 1.0 Final they moved to selectors like these for performance increases of hundreds of percents on some platforms. And every single platform say a noticeable increase in performance...Carbonize
@Carbonize - I agree, but you have to weight that against code maintainability too. In the mobile environment you have a lot less room for code inefficiency, making micro-optimization much more important. I tend to forget about the mobile environment because I don't get to develop for it very often, :(Brashear
@KevinB Here is the blog post where the jQuery Mobile team depicts the improvement in performance for the 1.0 Final release: jquerymobile.com/blog/2011/11/16/announcing-jquery-mobile-1-0. I can't find where but I read that it was mostly due to doing things like using .children() instead of .find().Carbonize
B
5

.closest only finds parents of the selected elements. Try this:

$("#start").closest("li").children("label");

Update

changed to .children, the "> label" selector is depreciated.

Brashear answered 23/2, 2012 at 21:17 Comment(4)
I will ask you the same question as I asked Jasper. Are there any big differences between your solution and the one provided by Jasper (e.g. performance)?Roentgenotherapy
Instead of using .find("> label") which requires mucking around with strings you could do: .children('label') or even faster .children().first().Carbonize
None that would be worth spending time on unless you are dealing with a loop going through hundreds of elements. Create a test for it on jsperf.com if you are concerned. I suggest using whichever one is more comfortable for you to read.Brashear
Also, just for micro-optimization sake, .children("label") is faster than my method, and in my opinion easier to read.Brashear
I
2

Closest will begin at the current element and progress up the DOM tree, but because <label> is not a parent of your current element, you'll need to use 2 finds:

 $('#start').closest('li').children('label');

This is going to be your most efficient traversal.

Imide answered 23/2, 2012 at 21:19 Comment(0)
P
0

I know this is old but here is the fastest way ...

$('#start').closest('li').find('label');

find() uses native browser methods, children() uses JavaScript interpreted by the browser

Precipitate answered 19/6, 2018 at 21:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.