jquery filtering has + not

42,380

Solution 1

You can use a combination of the :not and :has selectors like this

$("a.add-span").click(function() {
    $("li:not(:has(span))").each(function(index) {
        $(this).append('<span class="spn">new span<\/span>');
    });
});

Here is a demo http://www.jsfiddle.net/xU6fV/

Solution 2

$("a.add-span").click(function() {
    $("li").each(function(index) {
       if ($(this).children('span').length === 0){
         $(this).append('<span class="spn">new span<\/span>');
       }
    })    
})

With children() method, the length property is used to check whether or not a span already exits and if it doesn't, one is added/appended.

More Info:

Solution 3

This is the first link on Google when searching "jquery not has". The specific scenario I needed to solve was a little different to this one. I had to not items that have a specific DOM element, not simply based on a tag. This meant I couldn't use a selector which each solution above used.

jQuery's has() however does accept a DOM element! So I created a jQuery plugin to combine these two inbuilt functions.

I wanted to share it here because hopefully it will help others in my situation too. It also answers the original question.

The Plugin:

(function ( $ ) {
    $.fn.extend({
        not_has: function (param) {
            return this.not(this.has(param));
        }
    });
}( jQuery ));

Implementation:

$("a.add-span").click(function() {
    $("li").not_has("span")
    .append('<span class="spn">new span<\/span>');

    // Prevent the page from navigating away
    return false;
});

https://jsfiddle.net/brjkg10n/1/


I was initially intending to find a jQuery function that would work like addBack() but using not instead. If you need something so complicated, then please feel free to use the following plugin.

(function ( $ ) {
    $.fn.extend({
        notBack: function () {
            return this.prevObject.not(this);
        }
    });
}( jQuery ));

With this, the answer would be:

$("li").has("span").notBack()
.append('<span class="spn">new span<\/span>');

https://jsfiddle.net/2g08gjj8/1/

Solution 4

The accepted answer works well, if you don't want to filter out only those elements which do have the spans as direct children.

As the :has() and the .has() loop over all descendants, not just the children.

In that case, you have to use a function

$("li").not(function() {
    // returns true for those elements with at least one span as child element
    return $(this).children('span').length > 0 
}).each(function() { /* ... */ })
Share:
42,380
FFish
Author by

FFish

Updated on October 20, 2020

Comments

  • FFish
    FFish over 3 years

    Okay I have list items, some have a span, some not.
    On my event I want to add the span when they don't have any yet.

    has() works fine, but not() adds the span to both??

    HTML:

    <ul>
        <li>
            <p>item</p>
            <span class="spn">empty span</span>
        </li>
        <li>
            <p>item 2</p>
        </li>
    <ul>
    <hr>
    <a class="add-span"href="#">check</a>
    

    JS:

    $("a.add-span").click(function() {
        $("li").each(function(index) {
            // $(this).has("span").find("span").append(" - appended");
            $(this).not("span").append('<span class="spn">new span<\/span>');
        })    
    })
    
  • Jeff Rupert
    Jeff Rupert over 13 years
    Didn't you mean if($(this).children('span') ...? But otherwise, +1. Didn't even think of that method. =)
  • Sarfraz
    Sarfraz over 13 years
    @Jeff Rupert: True, it is children, I did not focus on his selector. Thanks
  • John Hartsock
    John Hartsock over 13 years
    @Sarfraz... wouldint it be easier to just specify in the selector for the each function...IE $("li:not(:has(span))").each(...)
  • Sarfraz
    Sarfraz over 13 years
    @John Hartsock: Agreed but it didn't come in my mind at the time of writing and that is better actually :)
  • John Hartsock
    John Hartsock over 13 years
    @FFish ... This is a cleaner solution
  • FFish
    FFish over 13 years
    Nice combination of not and has, at the end I choose Sarfraz option because I have another filter in the each loop, thanks!
  • FFish
    FFish over 13 years
    Thanks for digging this solution.
  • vipul sorathiya
    vipul sorathiya about 8 years
    i want reverse of it. is it possible?
  • John Hartsock
    John Hartsock about 8 years
    @vipulsorathiya you mean you want to find elements under an li that has a span tag inside?
  • John Hartsock
    John Hartsock about 8 years
    @vipulsorathiya should be $("li:has(span)")
  • vipul sorathiya
    vipul sorathiya about 8 years
    @JohnHartsock thnks a lot...!!
  • WoodrowShigeru
    WoodrowShigeru about 7 years
    Note: it doesn't work in the reverse order (first has, then not). It has to be :not(:has(xyz)).
  • John Hartsock
    John Hartsock about 7 years
    @WoodrowShigeru Not sure what your suggesting. but the above does facilitate an answer to OP's original question.
  • WoodrowShigeru
    WoodrowShigeru about 7 years
    I'm just pointing out that the order is important, not arbitrary. Your answer is fine, I'm just adding to it (for people who like to experiment).