1

Imagine an unordered list:

<ul id="something">
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <li class="child">child</li>
  <li class="child">child</li>
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <li class="child">child</li>
  <li class="child">child</li>
</ul>

I want to turn the above example into:

<ul id="something">
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <ul>
    <li class="child">child</li>
    <li class="child">child</li>
  </ul>
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <ul>
    <li class="child">child</li>
    <li class="child">child</li>
  </ul>
</ul>

I've successfully done this based on a solution found here: https://stackoverflow.com/a/9023265/1447679

$('#Something').children('li.child:not(li.child + li.child)').each(function() {
  $(this).nextUntil('li.parent').andSelf().wrapAll('<ul />');
});

If I don't use the :not(selector + selector) it causes strange continual embedding of ul > li > ul > li, etc.

So basically I just want to clearly understand it.

Community
  • 1
  • 1
user1447679
  • 3,076
  • 7
  • 32
  • 69
  • Shouldn't `$('#Something').children('li.children:not(li.children + li.children)')` be `$('#something').children('li.child:not(li.child + li.child)')`? – j08691 Mar 16 '15 at 19:31
  • @j08691Yes, thanks :) After typing ().children I just kept rolling with it. – user1447679 Mar 16 '15 at 19:35

3 Answers3

1

Working

It looks like the code describes step by step what it does.

It selects the first child, and for each of those, collect all its siblings until the next parent, including itself, and wrap that collection into an ul.

or:

Annotated

$('li.child:not(li.child + li.child)') selects each first child (a child that is not preceeded by another child).

For each of those (.each(function() {), collect all its siblings until the next parent (nextUntil('li.parent')), including itself (andSelf), and wrap that collection into an ul (wrapAll('<ul>')).

not(child+child) and nextUntil

So the magic is in the combination of not(li.child + li.child) (which selects the first child of each 'group'), and the nextUntil method (which selects all subsequent children until the next parent).

If you remove the not(..+..), you will get all children and their subsequent siblings, resulting in overlapping groups which all get wrapped. That seriously messes up your HTML.

Other relevant differences

There are a couple of small differences between your version and Dan A's version in the other thread:

First of all, that one works with only one class, because he uses not(li.child) as a selector, so basically everything that is not a child is regarded a parent.

Secondly, he wraps it in <li><ul> instead of just <ul>. This makes sense, because the items are in a list already. The collection of children needs to be wrapped in an li to keep the parent list valid. Elements other than li are not valid as direct children of ul elements. Your code would put an ul inside the parent ul, technically making the HTML invalid. The browser will probably fix this for you though, by adding the implied li.

Community
  • 1
  • 1
GolezTrol
  • 114,394
  • 18
  • 182
  • 210
0

Basically what the :not(li.child + li.child) seletor does is that it only selects the first occurring li.child element in a group of immediately consecutive li.child elements.

If we dissect the markup, we can see which li.child satisfy the selector:

<ul id="something">
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <li class="child">child</li>    <!-- satisfies, not immediate sibling of li.child -->
  <li class="child">child</li>    <!-- fails, because is immediate sibling of li.child -->
  <li class="parent">parent</li>
  <!-- and etc -->
</ul>

To view it in a more direct way:

<ul id="something">
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <li class="child">child</li>    <!-- is not li.child+li.child -->
  <li class="child">child</li>    <!-- is     li.child+li.child -->
  <li class="parent">parent</li>
  <li class="parent">parent</li>
  <li class="child">child</li>    <!-- is not li.child+li.child -->
  <li class="child">child</li>    <!-- is     li.child+li.child -->
</ul>
Terry
  • 63,248
  • 15
  • 96
  • 118
0

What is happening is you're selecting the li element which have the class .child and whose previous sibling don't have the .child class.

Explanation:

The li.child + li.child selects an li element with the child class, which comes directly after a li element with the child class

The :not() excludes whatever you throw inside the ().

Erik Svedin
  • 1,286
  • 12
  • 26