Erik Philips
Erik Philips

Reputation: 54618

Find descendant elements that do not have a specific ancestor

I want to be able to select an element and find all descendant elements with a specific class but are not contained with a similar parent element. It's hard to describe:

Html:

<div class="js-test js-1">
    <div class="b">1</div>
    <div class="b">1</div>
    <div class="d">
        <div class="b">1</div>        
    </div>
    <div class="js-test js-2">
        <div class="b">2</div>
        <div class="b">2</div>
        <div class="b">2</div>
        <div class="d">
            <div class="b">2</div>        
        </div>
        <div class="js-test js-3">
            <div class="b">3</div>
            <div class="b">3</div>
            <div class="b">3</div>
            <div class="b">3</div>
            <div class="d">
                <div class="b">3</div>        
            </div>
        </div>
    </div>
</div>

Attempted jQuery:

$(document).ready(function(){
    function test(element)
    {
        var test2 = element.find('.b :not(.js-test .b)');
        console.log(test2.length);
    }
    
    console.clear();
    test($('.js-1'));
    test($('.js-2'));
    test($('.js-3'));
});

Expected Results:

3

4

5

Actual Results:

0

0

0

JsFiddle Example

Upvotes: 1

Views: 359

Answers (4)

Erik Philips
Erik Philips

Reputation: 54618

I ended up using something similiar to billyonecan's answer:

$(document).ready(function(){
    function test(element)
    {
        var all = element.find('.b');
        var exclude = element.find('.js-test .b');
        var test = all.not(exclude);
        console.log(test.length);
    }
    
    console.clear();
    test($('.js-1'));
    test($('.js-2'));
    test($('.js-3'));
});

Actual Results:

3

4

5

The intended application's result set is so small that I don't believe there is any significant different in performance between any solution here.

Upvotes: 0

guest271314
guest271314

Reputation: 1

Try

// descendant `.b` of `element`,
// descendant `.b` of `element`, not `.b`, not `.js-test`
var test2 = element.find("> .b, > :not(.b):not(.js-test) .b");

jsfiddle http://jsfiddle.net/ku2pdn2j/4/


    $(document).ready(function(){
        function test(element) {
            var test2 = element.find("> .b, > :not(.b):not(.js-test) .b");
            console.log(test2.length, test2);
        }
        
        console.clear();
        test($('.js-1'));
        test($('.js-2'));
        test($('.js-3'));
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
   <div class="js-test js-1">
        <div class="b">1</div>
        <div class="b">1</div>
        <div class="d">
            <div class="b">1</div>        
        </div>
        <div class="js-test js-2">
            <div class="b">2</div>
            <div class="b">2</div>
            <div class="b">2</div>
            <div class="d">
                <div class="b">2</div>        
            </div>
            <div class="js-test js-3">
                <div class="b">3</div>
                <div class="b">3</div>
                <div class="b">3</div>
                <div class="b">3</div>
                <div class="d">
                    <div class="b">3</div>        
                </div>
            </div>
        </div>
    </div>

Upvotes: 0

BoltClock
BoltClock

Reputation: 723448

The selector .b :not(.js-test .b) finds descendants of .b elements that are not .js-test .b elements. The problem with this selector is twofold:

  • Every .b element is a descendant of a .js-test element. This includes the .b elements immediately nested within each context .js-test element that you are testing.

  • The space between the first .b and the :not() represents a descendant selector, which also means that the element being represented by :not() could very well be a :not(.b) entirely and still match.

    But none of your .b elements have any element descendants, and this is the real reason why all of your test results are zero.

Since you can't directly create a selector corresponding to the context element (the argument element), you will need to write a custom filter function like the one proposed by billyonecan, and filter according to whether its closest .js-test ancestor is the context element.

Upvotes: 1

billyonecan
billyonecan

Reputation: 20250

You'd have to do some filtering to only count occurrences of .b where the closest .js-test equals element:

element.find('.b').filter(function() {
    return $(this).closest('.js-test').is(element);
}).length;

Here's a fiddle

Upvotes: 1

Related Questions