3

For example:

<div class="mainWrapper">
    <div class="FirstLayer">
        <input class="foo" value="foo" />
    </div>

    <div class="SecondLayer">
        <div class="thirdLayer">
            <input class="fee" />
        </div>
    </div>
</div>

Lets say I have the input.fee as a jQuery object and I also need to get the value of input.foo. Now I know I can use a multitude of approaches such as $(this).parents(':eq(2)').find('.foo') but I want to use this one method on layouts which will have varying levels and numbers of nodes. So I am wondering if there is a method which will simply start from .fee and just keep going up until it finds the first matching element, .prevAll() does not appear to do this. There are many .foo and .fee elements and I need specifically the first one above the .fee in context.

Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
LaserBeak
  • 3,257
  • 10
  • 43
  • 73
  • 2
    Could you show an example with multiple `foo` and `fee` fields as the traversal will most likely be different – Rory McCrossan Nov 14 '12 at 11:30
  • You can use `closest` method, `$(this).closest('.mainWrapper').find('.foo')` – Ram Nov 14 '12 at 11:31
  • Going up will never reach `foo`, because it isn't an ancestor of `fee`. It is a sibling of an ancestor of `fee` – Asad Saeeduddin Nov 14 '12 at 11:32
  • @Asad So there is not a function that will simply go up the dom tree checking all elements for a class, irrespective if ancestor or not? – LaserBeak Nov 14 '12 at 11:37
  • @LaserBeak Right. It's called tree for a reason - you can't traverse up and expect to reach all the _branches_ and _leafs_ of a tree. – WTK Nov 14 '12 at 11:39
  • @LaserBeak `going up the dom tree` means iterating over the ancestors of the element. See raina77ow answer, in which he iterates over all ancestors, filters out the ones that contain your desired element, and finds the element in that ancestor. – Asad Saeeduddin Nov 14 '12 at 11:40
  • possible duplicate of [Traverse up until some element](http://stackoverflow.com/questions/5198259/traverse-up-until-some-element) – Felix Kling Nov 14 '12 at 11:49

3 Answers3

8

How about this:

$('input.fee').closest(':has("input.foo")')
              .find('input.foo').val();

Here's JS Fiddle to play with. )

UPDATE: Kudos to @VisioN - of course, parents:first is well replaced by closest.

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • 1
    Instead of applying `:first` selector you'd better use `closest()` method. – VisioN Nov 14 '12 at 11:38
  • @VisioN But of course. ) Was confused by that `:has` selector. – raina77ow Nov 14 '12 at 11:41
  • Great, I was stuck because didn't think of `:has` instead I was wrongly trying with `.closest('* input.foo')` +1 for a well played answer :-) – Nelson Nov 14 '12 at 11:51
  • This will not pick the previous div with input.foo. Look at fiddle [here](http://jsfiddle.net/YZ6vg/4/). I think it might traverse through parents but when matching them against a selector it must start at parent with index 0. – Bruno Nov 14 '12 at 11:59
  • @Bruno Yes, IMO the best solution for this would qualify for a new jquery method, could be called .nearest() , that would traverse back doing .prev() checking for selector and when there's no more prev() it goes to parent() and continue doing prev() again. – Nelson Nov 14 '12 at 12:13
  • @Nelson I gave an answer to this question that traverses up to elements under .mainWrapper and finds previous div that contains input.foo. That should work. – Bruno Nov 14 '12 at 12:19
  • @Bruno I think your solution is too localized to this specific html layout, while OP stated wanted a general solution that would work for, citing him `on layouts which will have varying levels and numbers of nodes` – Nelson Nov 14 '12 at 12:25
  • @Bruno I gave you upvote not for your answer but for providing here a fiddle that would show how the accepted answer won't fully work in all conditions (so not resolving the general problem in full either). – Nelson Nov 14 '12 at 12:30
  • @Nelson amended solution to handle varying levels of nodes :) – Bruno Nov 14 '12 at 12:31
1

This will select the previous input.foo

// self might have siblings that are input.foo so include in selection
$( $("input.fee").parentsUntil(":has(input.foo)").andSelf()

        // if input.off is sibling of input.fee then nothing will
        // be returned from parentsUntil. This is the only time input.fee
        // will be selected by last(). Reverse makes sure self is at index 0
        .get().reverse() )

        // last => closest element
        .last()                 

        //fetch siblings that contain or are input.foo elements
        .prevAll(":has(input.foo), input.foo") 

        // first is closest
        .first() 

        // return jQuery object with all descendants
        .find("*")         

        // include Self in case it is an input.foo element 
        .andSelf() 

        .filter("input.foo")

        // return value of first matching element
        .val() 
Bruno
  • 5,772
  • 1
  • 26
  • 43
  • This won't work if having an element between .firstlayer and .secondlayer see http://jsfiddle.net/YZ6vg/7/ And it would not also work in this scenario http://jsfiddle.net/YZ6vg/8/ – Nelson Nov 14 '12 at 12:49
  • This answer is practically as good as the accepted one, just each one handle different layout corner cases better than the other, but for any both answers a layout can easily be done so that they fail. I repeat the general solution for finding a *nearest* element would be to create a new jquery method that would combine prev() with parent() as described in my comment on the accepted answer. – Nelson Nov 14 '12 at 12:53
  • Wow, excellent work with your last version, it passed all fiddles so far but I still found it failing for this one http://jsfiddle.net/YZ6vg/16/ – Nelson Nov 14 '12 at 15:43
  • @Nelson I know I won't get any more points :) but any chance of checking the above works. If it is too much work don't bother but just out of interest. – Bruno Nov 19 '12 at 09:15
  • Yes, your final version is working for all fiddles posted so far, congratulations :-) – Nelson Nov 22 '12 at 09:26
  • @Thanks Nelson. I appreciate it :) – Bruno Nov 22 '12 at 09:36
-1

jQuery.closest() takes selector and does exactly what you need - finds the first matching element that is parent of something. There's also jQuery.parents() that does take a selector to filter element ancestors. Use those combined with find method and you're set.

$('input.fee').closest('.mainWrapper").find('.foo') does the trick, doesn't it?

WTK
  • 16,583
  • 6
  • 35
  • 45