Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery: find() children until a certain threshold element is encountered

Tags:

jquery

I have a nested table structure like

   <table>

       <td id="first">

           <div class="wrapper">
               <input name=1>
           </div>

           <input name=2>

           <table>

               <td id="second">

                   <input name=3>

I have jQuery selection $("#first"). I'd like to traverse and find() all children <input>s within this <td> context, but not to descent into the nested <table>s.

So I need a jQuery trick which

  • Will find() all children elements of a certain element

  • Will descent n levels down in DOM tree

  • But will stop descending if certain a element (<table>) is encountered, so that the selector doesn't select inputs of a nested tables (which will be handled separately)

  • There could be any number of nested <table> levels, so the solution should work no matter how many parent <table> or children <table> are encountered within the scope of $("#first") <td> or any other <td>

I checked other jQuery find until questions. They have answers, but seems like they do not fill the last criteria

like image 527
Mikko Ohtamaa Avatar asked Nov 09 '12 09:11

Mikko Ohtamaa


4 Answers

I had a similar issue in this other question. I ended up finally figuring out a plugin on my own after going back and forth with some people trying to think of a find selector.

USAGE : ExclusiveInputs = $('#first').findExclude('input','table');

// Find-like method which masks any descendant
// branches matching the Mask argument.
$.fn.findExclude = function( Selector, Mask, result){

    // Default result to an empty jQuery object if not provided
    var result = typeof result !== 'undefined' ?
                result :
                new jQuery();

    // Iterate through all children, except those match Mask
    this.children().each(function(){

        var thisObject = jQuery( this );
        if( thisObject.is( Selector ) ) 
            result.push( this );

        // Recursively seek children without Mask
        if( !thisObject.is( Mask ) )
            thisObject.findExclude( Selector, Mask, result );
    });

    return result;
}

(Condensed Version):

$.fn.findExclude = function( selector, mask, result )
{
    var result = typeof result !== 'undefined' ? result : new jQuery();
    this.children().each( function(){
        var thisObject = jQuery( this );
        if( thisObject.is( selector ) ) 
            result.push( this );
        if( !thisObject.is( mask ) )
            thisObject.findExclude( selector, mask, result );
    });
    return result;
}
like image 125
Garet Claborn Avatar answered Dec 14 '22 23:12

Garet Claborn


Update: Let's have another take at this.

Basically, you want to match all the <input> elements that are descendants of #first and that are not children of <td> elements nested more than one level deep under #first.

(I'm not sure about that last under #first part, but implementing it allows us to support <td> elements above #first in the ancestor chain.)

Technically, the following selector alone should fulfill your requirements:

var inputs = $("#first td:not(#first td td) > input");

If this does not work in your browser (Sizzle should be up to the task I think, but complex selectors like :not() are always tricky), you can delegate the processing to jQuery methods:

var inputs = $("#first td").not("#first td td").children("input");

Original answer follows:

You can use not() to exclude <input> elements that have more than one <td> ancestor:

var firstLevelCells = $("#first").find("input").not("td td input");
like image 40
Frédéric Hamidi Avatar answered Dec 14 '22 22:12

Frédéric Hamidi


eh, i have a better idea..

var badTable = "table.bad"; //the one you want to avoid
var $goodInputs = $("#first").find('input').filter(function() {
    return $(this).closest(badTable).length == 0;
});

this may or may not be fast enough for you. it depends on your DOM which you do not want to talk about ;)

if its slow, just write the code for your algorithm by hand. There isnt a selector shortcut.

like image 36
mkoryak Avatar answered Dec 14 '22 22:12

mkoryak


I had a similar issue and was still challenged to do something without an extension loop or having the exact structure of the DOM. In my case I had already a reference to the element '#first' which if you don't we could get it for example with an each (even if it is only one object). The trick is to go back up the tree with parentsuntil and stop at your top element to see if there is any intermediate element satisfying the conditionn.

Using the shorthand lambda notation for the functions (as you can write in typescript) this would lead to this:

$('#first').each((idx, f) => $(f).find('input').filter((idx2, inp) => $(inp).parentsUntil(f, 'table').length == 0)

It might not be the most efficient way (as you first select everything to then throw away elements by mounting up the DOM-tree again, but it is compact and fairly generic.

like image 39
Koen Avatar answered Dec 15 '22 00:12

Koen