how to select element that does NOT have a link inside it?
Asked Answered
H

4

6

Given this HTML:

<ul>
  <li><a href="/artists/gil_elvgren/activity_stream">Activity Stream</a></li>
  <li><a href="/artists/gil_elvgren/likes">Likes</a></li>
  <li>Favorites</li>
  <li><a href="/artists/gil_elvgren/follows">Follows</a></li>
  <li><a href="/artists/gil_elvgren/sketchbook_comments">Whiteboard</a></li>

</ul>

I want to inject the class="current" into the single line item that does NOT have a link inside it.

How do I use jQuery to find an element that does NOT contain a child?

Hydraulics answered 29/12, 2011 at 21:51 Comment(0)
W
8

Select <li> element that has no <a> child using the :has and :not selector:

$("li:not(:has(a))").addClass("current");

jsFiddle

-- EDIT --

While this might be the shortest solution, readability aside, it definitely is not the best one in regards to speed.

With that in mind, I would suggest you check a great answer provided by @bazmegakapa.

Since this is something you (or anyone else including me) might end up using more than once, I have expanded jQuery a bit with this plugin/method you can use in order to DRY your code:

jQuery.fn.thatHasNo = function(element, method) {
  if (typeof method === "undefined" || method === null) method = "children";
  return this.not(function() {
    return $(this)[method](element).length;
  });
};

Which you can call with:

$("li").thatHasNo("a").addClass("current");

This extension will, by default (if second argument to method is not passed), check if there are any direct descendants (children) that match provided selector. However, you can provide any tree-traversal parameter as second argument to method. So, this can be written as either of the following:

$("li").thatHasNo("a", "children").addClass("current");
//...
$("li").thatHasNo("a", "find").addClass("current");
//...
$("li").thatHasNo(".class", "siblings").addClass("lame");

I have updated jsFiddle to reflect that.

Willin answered 29/12, 2011 at 21:59 Comment(5)
I have added some notes concerning these selectors (:has() and :not) into my answer.Swayne
+1 Great plugin :). May I suggest typeof method == 'undefined' instead of method == null?Swayne
You can add that, but since it is an argument and by definition already defined, there is no need to check for undefined :)Willin
It will be undefined if you omit the parameter when you call the function. It works for you because you use ==, would not work with === because it is not null.Swayne
You are right, I just realized the same :) JavaScript can be weird sometimes. Originally this was piece of coffeescript: gist.github.com/1540061Willin
S
1

You should use the .not() method (which is basically a negated .filter()) with a filter function.

$('li') /* select the elements you want to test */
    .not(function () { /* run a filter function on them */
        /* those that have a direct children link (use find() if you need
        any kind of children) will return true thus excluded */
        return $(this).children('a').length; 
    })
    .addClass('current'); /* add our beloved class */

jsFiddle Demo


You could also use complicated selectors, as @Krule suggested, but I find it less readable and even the jQuery manual on :not() warns against it:

The .not() method will end up providing you with more readable selections than pushing complex selectors or variables into a :not() selector filter. In most cases, it is a better choice.

Also, :has() is not really recommended as well:

Because :has() is a jQuery extension and not part of the CSS specification, queries using :has() cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. For better performance in modern browsers, use $("your-pure-css-selector").has(selector/DOMElement) instead.

Swayne answered 29/12, 2011 at 22:9 Comment(1)
I have added a note pointing to your answer and expanded a bit on it by extending jQuery with thatHasNo method.Willin
S
0
$("li").each(function(){
    if($(this).children().length == 0){
        //found current..
        $(this).addClass("current");
    }
});
Schoolman answered 29/12, 2011 at 21:57 Comment(1)
This will only work if the li has no child elements at all. I mean, the OP only wanted those that have no a child.Swayne
G
-1

I would suggest that you just add a <span> element inside of your <li> to differentiate it between the elements that have links in them.

<ul>
    <li><a href="/artists/gil_elvgren/activity_stream">Activity Stream</a></li>
    <li><a href="/artists/gil_elvgren/likes">Likes</a></li>
    <li><span>Favorites</span></li>
    <li><a href="/artists/gil_elvgren/follows">Follows</a></li>
    <li><a href="/artists/gil_elvgren/sketchbook_comments">Whiteboard</a></li>
</ul>

CSS:

ul li a { /* Link CSS */ }
ul li span { /* Current CSS */ }
Geneticist answered 29/12, 2011 at 21:57 Comment(2)
If he could do that, why not just add the class there?Schoolman
This answer does not address the problem.Willin

© 2022 - 2024 — McMap. All rights reserved.