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.

Allen Liu
  • 3,948
  • 8
  • 35
  • 47
  • 1
    If `.closest()` doesn't seem to work right why not look at the [`.closest()` doco](http://api.jquery.com/closest/)? Or the list of traversal methods: http://api.jquery.com/category/traversing/tree-traversal/ to find a more appropriate method... – nnnnnn Feb 23 '12 at 21:21
  • 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. – Allen Liu Feb 23 '12 at 21:27
  • Great question! I haven't used closest() yet, but now I know I need to do so. Great example of its use. – Evik James Feb 23 '12 at 21:31
  • 1
    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. – nnnnnn Feb 23 '12 at 21:39
  • 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. – Allen Liu Feb 23 '12 at 21:41

4 Answers4

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();
Jasper
  • 75,717
  • 14
  • 151
  • 146
  • Thanks Jasper! Are there any big differences between your solution and the one provided by Kevin (e.g. performance)? – Allen Liu Feb 23 '12 at 21:19
  • 1
    @AllenLiu Anytime you can remove strings from your selectors they will perform faster. The fastest selector I provided was: `$('#start').parent().prev().prev();` – Jasper Feb 23 '12 at 21:21
  • @AllenLiu. Micro difference. Don't waste time on it. – gdoron Feb 23 '12 at 21:22
  • 1
    @AllenLiu also I just noticed that you are making form inputs like this: `` (self-closing tag). – Jasper Feb 23 '12 at 21:25
  • @gdoron Here is a JSPerf to show that using `$('#start').parent().prev().prev();` is twice as fast as using other methods that incorporate strings: http://jsperf.com/jquery-string-selector-vs-function-selector – Jasper Feb 23 '12 at 21:26
  • Nice Jasper! =) Thanks for all your help. – Allen Liu Feb 23 '12 at 21:30
  • 3
    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. – Kevin B Feb 23 '12 at 21:31
  • 0.00001 instead of 0.00002 is not so interesting. better spent the time on the next mission... – gdoron Feb 23 '12 at 21:31
  • @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... – Jasper Feb 23 '12 at 21:33
  • @Jasper - 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, :( – Kevin B Feb 23 '12 at 21:35
  • @KevinB Here is the blog post where the jQuery Mobile team depicts the improvement in performance for the 1.0 Final release: http://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()`. – Jasper Feb 23 '12 at 21:36
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.

Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • 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)? – Allen Liu Feb 23 '12 at 21:19
  • 2
    Instead of using `.find("> label")` which requires mucking around with strings you could do: `.children('label')` or even faster `.children().first()`. – Jasper Feb 23 '12 at 21:20
  • 2
    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 http://jsperf.com if you are concerned. I suggest using whichever one is more comfortable for you to read. – Kevin B Feb 23 '12 at 21:21
  • Also, just for micro-optimization sake, `.children("label")` is faster than my method, and in my opinion easier to read. – Kevin B Feb 23 '12 at 21:38
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.

Highway of Life
  • 22,803
  • 16
  • 52
  • 80
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

Scotty G
  • 374
  • 2
  • 6