Automatic Numbering of Headings H1-H6 using jQuery
Asked Answered
B

5

8

I read related posts, but didn't find a solution for IE, so I ask for a jQuery-solution for this problem:

I've some nested hierarchical Headings like this

<h1> heading 1</h1> 
<h2> subheading 1</h2>
<h1> heading 2</h1> 
<h2> subheading 1</h2>
<h2> subheading 2</h2>

I need some automated headings output like this:

1. heading 1
1.2 subheading 1
2. heading 2
2.1. subheading 1
2.2. subheading 2

Is there a way how this can be achieved using jQuery or alike, working in IE6+?

Blowup answered 26/2, 2011 at 12:55 Comment(3)
I tend to work on the assumption there should be only one h1 in the page (the page heading), this may, or may not, be of relevance to your structure, though.Tarango
@David: I seem to recall that's what google perfers as well, so that might be relevant :) It would be trivial to adjust the script accordingly.Tigrinya
@David, that does fit with what I'd heard previously; ...it's weird getting to an age where you've learned stuff and can't remember where it's from... :-/Tarango
R
6

another possibility

var indices = [];

function addIndex() {
  // jQuery will give all the HNs in document order
  jQuery('h1,h2,h3,h4,h5,h6').each(function(i,e) {
      var hIndex = parseInt(this.nodeName.substring(1)) - 1;

      // just found a levelUp event
      if (indices.length - 1 > hIndex) {
        indices= indices.slice(0, hIndex + 1 );
      }

      // just found a levelDown event
      if (indices[hIndex] == undefined) {
         indices[hIndex] = 0;
      }

      // count + 1 at current level
      indices[hIndex]++;

      // display the full position in the hierarchy
      jQuery(this).prepend(indices.join(".")+" "+this.tagName);

  });
}

jQuery(document).ready(function() {
  addIndex();
});
Roselinerosella answered 26/2, 2011 at 14:51 Comment(4)
Ah, this is the approach I'm going with as well :) Just a note: if you're having something like 1.2.2.3.1. Very deep header, it might be immediately followed by 1.3. Subheader. Deleting the last index is not always enough.Tigrinya
You are right, i need to fix that. this way it will really look like i cloned you answer ;-). Fixed.Roselinerosella
Thanks, this gives me the best result. still tricky when the selector scope is restricted to a special area like ("#content h1,#content h2"), but it works! thanks!Blowup
@honestor, h1-h7 can be expressed as :header, so you could simply do #content :header. Another approach would be to pass context to the selector, in which case it'll only search within that scope: $('h1,h2,h3,h4,h5,h6', $('#content')[0]). Note that the content parameter should be DOM Node, and not a jQuery object. Hence the [0]. A simple document.getElementById('content') would have done the job as well!Tigrinya
T
7

Here's my take on it:

var segments = [];

$(':header').each(function() { 

  var level = parseInt(this.nodeName.substring(1), 10);

  if(segments.length == level) {
    // from Hn to another Hn, just increment the last segment
    segments[level-1]++;
  } else if(segments.length > level) {
    // from Hn to Hn-x, slice off the last x segments, and increment the last of the remaining
    segments = segments.slice(0, level);
    segments[level-1]++;
  } else if(segments.length < level) {
    // from Hn to Hn+x, (should always be Hn+1, but I'm doing some error checks anyway)
    // add '1' x times.
    for(var i = 0; i < (level-segments.length); i++) {
      segments.push(1);
    }
  }

  $(this).text(segments.join('.') + '. ' + $(this).text());

});

Working example on all levels, H1 - H6

Tigrinya answered 26/2, 2011 at 14:27 Comment(1)
thanks for your answer as well, jeromes solution wors a bit better in my environment.Blowup
R
6

another possibility

var indices = [];

function addIndex() {
  // jQuery will give all the HNs in document order
  jQuery('h1,h2,h3,h4,h5,h6').each(function(i,e) {
      var hIndex = parseInt(this.nodeName.substring(1)) - 1;

      // just found a levelUp event
      if (indices.length - 1 > hIndex) {
        indices= indices.slice(0, hIndex + 1 );
      }

      // just found a levelDown event
      if (indices[hIndex] == undefined) {
         indices[hIndex] = 0;
      }

      // count + 1 at current level
      indices[hIndex]++;

      // display the full position in the hierarchy
      jQuery(this).prepend(indices.join(".")+" "+this.tagName);

  });
}

jQuery(document).ready(function() {
  addIndex();
});
Roselinerosella answered 26/2, 2011 at 14:51 Comment(4)
Ah, this is the approach I'm going with as well :) Just a note: if you're having something like 1.2.2.3.1. Very deep header, it might be immediately followed by 1.3. Subheader. Deleting the last index is not always enough.Tigrinya
You are right, i need to fix that. this way it will really look like i cloned you answer ;-). Fixed.Roselinerosella
Thanks, this gives me the best result. still tricky when the selector scope is restricted to a special area like ("#content h1,#content h2"), but it works! thanks!Blowup
@honestor, h1-h7 can be expressed as :header, so you could simply do #content :header. Another approach would be to pass context to the selector, in which case it'll only search within that scope: $('h1,h2,h3,h4,h5,h6', $('#content')[0]). Note that the content parameter should be DOM Node, and not a jQuery object. Hence the [0]. A simple document.getElementById('content') would have done the job as well!Tigrinya
J
1

honestor, simply speaking, the problem will be solved once we can get all the <h2> tags that follows right after every <h1> tag and just before the next <h1> tag, right ?

This code has made it and working just fine

$("h1").each(function(mainHeadIndex)
 {
     // Numbering the main heads
     $(this).html(mainHeadIndex +1 +'. ' + $(this).html());

     // Find all the h2 tags right under this particular main head
     $(this).nextUntil("h1").filter("h2").each(function(subIndex)
      {
          // calculate the right sub head number
          var newSubIndex = subIndex+1;
          // Numbering the sub heads
          $(this).html(mainHeadIndex +1 +'.'+ newSubIndex +$(this).html());
      });
  })

you can test it on this link

P.S: thanks for you MatejB, I didn't know jsfiddle.com before your post :)

Janinejanis answered 26/2, 2011 at 13:47 Comment(5)
Thanks David, the code is now updated to match your scenario.Janinejanis
another enhancement here is to limit the scope of this code to the 'h1' container to not affect any other h1s or h2s in the page. this can be done by starting the script with this line instead of the original one: by container's tag name: '$('divContainer h1')' or by container's ID: '$('#containerID').find('h1')'Janinejanis
@Mohammed: No worries about the format. The comment markdown is a simplified version of that in the answers. You can click the 'help' link when you're adding a comment, for formatting instructions. Now the code is better in that it doesn't fire on <p> tags and similar, but you've reduced your scope to only H1 and H2. While those are the only headers in his example, the title says H1-H6. I would expect he wants stuff like 1.1.2.3.1. Very deep subheaderTigrinya
@david: exactly, that's what I'm looking for. I like in your approach that it's possible to define the selector, but I still need h3-h6 covered somehow.Blowup
@honestor: hmm, my approach does cover h1-h6...?Tigrinya
T
0

This works for me:

var i = 0;
var j = 0;
$('h1').each(function(index, element){
    i++;
    $(element).text(i + '. ' + $(element).text());

    j = 1;
    $(element).nextUntil('h1').each(function(subindex, subelement){
        if (!$(this).is('h2')) return; // so subelements other than h2 can be left alone
        j++;
        $(subelement).text(i + '.' + j + '. ' + $(subelement).text());
    });
});

Woking example on jsfiddle.

Tricycle answered 26/2, 2011 at 13:18 Comment(2)
That treats every single element that isn't a h1 as a subheader. in <h1>header</h1><h2>subheader</h2><p>paragraph 1</p><p>paragraph 2</p>, the paragraphs would be prepended with 1.2 and 1.3 respecitvely.Tigrinya
@honestor I think David Hedlund gave you the best solution ;)Cleliaclellan
L
0


I think you should wrap H2 subheadings in DIV then use the following code:

$(document).ready( function() {
    var result = 0;
    $('h1').each(function(index) {
        $(this).text((index + 1) + '. ' + $(this).text());
        var saveIndexNum = index + 1;
        $(this).next().find('h2').each(function(index) {
            $(this).text(saveIndexNum + '.' +(index + 1) + ' ' + $(this).text());
        });
    });
});

try this



<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready( function() {
    var result = 0;
    $('h1').each(function(index) {
        $(this).text((index + 1) + '. ' + $(this).text());
        var saveIndexNum = index + 1;
        $(this).next().find('h2').each(function(index) {
            $(this).text(saveIndexNum + '.' +(index + 1) + ' ' + $(this).text());
        });
    });
});
</script>
<style>
h1
{
     color:red;
}
.subheading h2
{
    color:green;
}
.subheading
{
    background:#e2e2e2;
}
</style>
</head>
<body>
<h1> heading 1</h1> 
<div class="subheading">
    <h2> subheading 1.1</h2>
</div>
<h1> heading 2</h1> 
<div class="subheading">
    <h2> subheading 2.1</h2>
    <h2> subheading 2.2</h2>
    <h2> subheading 2.3</h2>
    <h2> subheading 2.4</h2>
    <h2> subheading 2.5</h2>
</div>
<h1> heading 3</h1> 
<div class="subheading">
    <h2> subheading 3.1</h2>
    <h2> subheading 3.2</h2>
</div>
<h1> heading 4</h1> 
<div class="subheading">
    <h2> subheading 4.1</h2>
    <h2> subheading 4.2</h2>
</div>
</body></html>

Lumpen answered 26/2, 2011 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.