javascriptjquerynextuntil

How to wrap all elements after heading until next heading that's same or higher?


I have content consisting of headings (h1, h2, h3, h4, h5, h6).

What I need:

Using jQuery, I need to wrap all elements following each heading until the next same heading or higher heading. By "higher", I mean to say:

...and so on, so forth.

E.g. Wrap all elements after h3 until the next h3 or higher headings (h1 or h2).

Here's an example of the desired end result:

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: Arial;
  color: white;
  background-color: black;
  margin: 0;
  padding: 5px;
}

.h1-section,
.h2-section,
.h3-section,
.h4-section,
.h5-section,
.h6-section {
  border: 1px solid black;
  border-top: none;
  padding: 20px;
}
<h1>1</h1>
<section class="h1-section">
  <h2>2</h2>
  <section class="h2-section">
    <h3>3</h3>
    <section class="h3-section">
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
    </section>
  </section>
</section>
<h1>1</h1>
<section class="h1-section">
  <h2>2</h2>
  <section class="h2-section">
    <h3>3</h3>
    <section class="h3-section">
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
    </section>
    <h3>3</h3>
    <section class="h3-section">
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
    </section>
  </section>
  <h2>2</h2>
  <section class="h2-section">
    <h3>3</h3>
    <section class="h3-section">
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
    </section>
    <h3>3</h3>
    <section class="h3-section">
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
      <h4>4</h4>
      <section class="h4-section">
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
        <h5>5</h5>
        <section class="h5-section">
          <h6>6</h6>
          <section class="h6-section">
          </section>
          <h6>6</h6>
          <section class="h6-section">
          </section>
        </section>
      </section>
    </section>
  </section>
</section>

What I've tried:

Using jQuery, I've tried using .nextUntil(), but I'm incorrectly selecting and wrapping elements:

$(document).ready(function() {
  $("h1").nextUntil("h1").wrapAll("<section class='h1-section'></section>");
  $("h2").nextUntil("h1, h2").wrapAll("<section class='h2-section'></section>");
  $("h3").nextUntil("h1, h2, h3").wrapAll("<section class='h3-section'></section>");
  $("h4").nextUntil("h1, h2, h3, h4").wrapAll("<section class='h4-section'></section>");
  $("h5").nextUntil("h1, h2, h3, h4, h5").wrapAll("<section class='h5-section'></section>");
  $("h6").nextUntil("h1, h2, h3, h4, h5, h6").wrapAll("<section class='h6-section'></section>");
});
h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: Arial;
  color: white;
  background-color: black;
  margin: 0;
  padding: 5px;
}

.h1-section,
.h2-section,
.h3-section,
.h4-section,
.h5-section,
.h6-section {
  border: 1px solid black;
  border-top: none;
  padding: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>1</h1>
<h2>2</h2>
<h3>3</h3>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h1>1</h1>
<h2>2</h2>
<h3>3</h3>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h3>3</h3>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h2>2</h2>
<h3>3</h3>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h3>3</h3>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h4>4</h4>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>
<h5>5</h5>
<h6>6</h6>
<h6>6</h6>

How can I achieve the desired outcome using jQuery?


Solution

  • The issue is that something like:

    $("h5").nextUntil("h5")
    

    will return all of the nextUntils, not just the ones for each h5 that you're expecting. ie it will get the nextUntils for the first h5 and combine them with the nextUntils of the second h5, etc then when you apply wrapAll, it will wrapAll all of them, not just the individual sets.

    Example using .text() to show this

    console.log($("h5").nextUntil("h5").length, $("h5").nextUntil("h5").text())
    h5,h6 { margin:0; padding:0; }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <h5></h5><h6>1</h6><h6>2</h6>
    <h5></h5><h6>3</h6><h6>4</h6>

    So you need to look at each h5 one at a time

    $("h5").each(function() {
        console.log($(this).nextUntil("h5").length, $(this).nextUntil("h5").text())
    });
    h5,h6 { margin:0; padding:0; }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <h5></h5><h6>1</h6><h6>2</h6>
    <h5></h5><h6>3</h6><h6>4</h6>

    Then you can replace the .length / .text() with your .wrapAll():

    $("h5").each(function() {
        $(this).nextUntil("h5").wrapAll("<div class='section-h5'>")
    });
    $("h6").each(function() {
        $(this).nextUntil("h6,h5").wrapAll("<div class='section-h6'>")
    });
    h5,h6 { margin:0; padding:5px; }
    h5 {  margin-top:4px;  }
    h6 { }
    
    .section-h5 { padding: 5px; border:1px solid blue; }
    .section-h6 { padding: 5px; border:1px solid green; }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <h5></h5><h6></h6><div>1</div><h6></h6><div>2</div>
    <h5></h5><h6></h6><div>3</div><h6></h6><div>4</div>

    (extend to other H levels as required)