views:

32

answers:

2

I have an arbitrarily deep list, like so:

<ul>
<li></li>
<li>
  <ul>
    <li></li>
    <li>
      <ul>
        <li></li>
        <li></li>
      </ul>
    </li>
  </ul>
</li>

Using jQuery, how can I select every li in the list that is not a leaf node. In other words, I want to select all of the li elements that have children ULs.

Thanks for your help!

+5  A: 
jQuery('li:has(ul)');

More info about :has.

J-P
Beautiful. jQuery to the rescue once again.
Travis
+2  A: 

Here's another alternative:

$('li ul').closest('li')

This is likely to be a fair bit faster than :has on modern browsers, since it'll use the native querySelectorAll method on the main selector. :has can't use native support since it's not a standard CSS selector but a jQuery extension.

bobince
good to know. thanks.
Travis
I tried this vs. `$('li:has(ul)');` in Chrome and FF and the latter was around ~30% faster. I think this is less efficient than it seems on the surface. Whether or not the native selector engine is used, the selector is still parsed in reverse (AFAIK)... So, all `ul` elements are gotten and then each one's ancestor-chain is checked for an `li` element. Then... each conforming `ul` is submitted to the same routine with `closest()`...
J-P
So, to sum up, `li ul` is fast... but only if you actually want the `ul` elements. The call to `closest()` makes this solution less efficient than using `:has`.
J-P
I ran `$('li ul').closest('li').size()` and `$('li:has(ul)').size()` in a loop of 20000 against the example markup in the question. `closest` was between two to four times faster in browsers with Selectors-API support and about 10% slower in IE7.
bobince
Interesting. Admittedly, my tests were a little far fetched. I just changed it out and used the OP's markup -- now in FF they're almost identical, and Chrome shows yours as faster, albeit inconsistently.
J-P