Programatically number <h1-h6> with jQuery function

Viewed 365

I'm trying to create a javascript function (that I can reuse on multiple projects) that creates auto numbering for headings (h1-h6).

I currently can do it via css using the following:

body { counter-reset: h1; }
h1 { counter-reset: h2; }
h2 { counter-reset: h3; }
h3 { counter-reset: h4; }
h4 { counter-reset: h5; }
h5 { counter-reset: h6; }

h1:before {
    content: counter(h1,decimal) ". ";
    counter-increment: h1;
}
h2:before {
    content: counter(h1, decimal) "." 
             counter(h2, decimal) ". ";
    counter-increment:h2;
}
h3:before {
    content: counter(h1, decimal) "." 
             counter(h2, decimal) "."
             counter(h3, decimal) ". "
    counter-increment:h3;
}
h4:before {
    content: counter(h1, decimal) "." 
             counter(h2, decimal) "."
             counter(h3, decimal) "."
             counter(h4, decimal) ". ";
    counter-increment:h4;
}
h5:before {
    content: counter(h1, decimal) "." 
             counter(h2, decimal) "."
             counter(h3, decimal) "."
             counter(h4, decimal) "."
             counter(h5, decimal) ". ";
    counter-increment:h5;
}
h6:before {
    content: counter(h1, decimal) "." 
             counter(h2, decimal) "."
             counter(h3, decimal) "."
             counter(h4, decimal) "."
             counter(h5, decimal) "." 
             counter(h6, decimal) ". ";
    counter-increment:h6;
}

Which creates this sort of format:

1. Heading number 1
1.1. Heading level 2
1.1.1. Heading level 3

2. Heading number 2
3. Heading number 3
2.1. Heading level 2
2.2. Heading level 2
2.2.1. Heading level 3

But I want to convert this into a JS function (and remove the CSS potion) so than on certain pages I can have:

if( typeof mbopts !== 'undefined' && mbopts.length > 0 ) {
    var mbopts = {
        levels:    Int,     // [1-6 being H1-H6]
        separator: String,  // decimal, hyphen, brace -> .,)
        startAt:   Int,     // default: 1, but what the begin numbering at
    };
}
$('#main').mbheaders(mbopts);

and then in the function it would:

(function(h) {
    h.fn.mbheaders = function( mbopts ) {
        // create the defaults
        let mbdefaults = {
            levels: 6,
            separator: "decimal",
            startAt: 1
        };

        // extend the defaults
        let mboptions = h.extend( {}, mbdefaults, mbopts );


        return this.each( function() {

            // the variable for this
            var $this = h(this);
            
            // do the magic
            // 
            // get $levels and use that as the limiter
                // 1. find all the H1 in the scope
                // 1.1. add the start number to it
                // 
                // 2. find all the H2 under H1 until next H1
                // 2.1. add the (H1 + index++) to it
                // 
                // ...
                // 
                // 6. find all the H6 under H5 until next H6
                // 6.1. add the (H1 + H2 + H3 + H4 + H5 + index++) to it
        }
    }
}( jQuery ));

The reason I want to do this is when I write documentation, I have a full page document (full.md) but I also split the sections into their own files (01-section.md, 02-section.md, etc.). The problem is when the file starts on 02-section.md the headings renumber at 1. rather than a variable.


Example .md file when processed as html

<article class="markdown-section" id="main">
    <h1 id="about-this-document">About this document</h1>
    <p>This is the online documentation of the <strong>Company Procedures</strong>.</p>

    <h1 id="everyone">Everyone</h1>

        <h2 id="logging-into-your-computer">Logging into your computer</h2>
            <p>These are the instructions on how to log into your computer.</p>

        <ol>
            <li>Step one</li>
            <li>Step two</li>
        </ol>

        <h2 id="programmes-on-taskbar">Programmes on taskbar</h2>

            <h3 id="microsoft-word">Microsoft Word</h3>
                <p>This is a word processor</p>

            <h3 id="mail">Mail</h3>
                <p>This is for your emails</p>

            <h3 id="document-management">Document management</h3>
                <h4 id="windows-explorer">Windows Explorer</h4>
                <h4 id="xyplorer">XYPlorer</h4>

    <h1 id="special-areas">Special areas</h1>

        <h3 id="on-the-road">On the road</h3>
        <h3 id="remote">Remote</h3>

</article>

and the output that's important to this question:

1. Everyone
1.1. Logging into your computer
1.2. Programmes on taskbar
1.2.1. Microsoft Word
1.2.2. Mail
1.2.3. Document management
1.2.3.1. Windows Explorer
1.2.3.2. XYPlorer

2. Special areas
2.1.1. On the road
2.1.2. Remote
2 Answers

I just created a .ol() method for jQuery. It creates an ordered list and returns a new instance of a constructor that has an .li() method. The .li() method returns a reference to the the ordered list. List away!

function Ol(j){
  const ol = $('<ol></ol>');
  j.append(ol);
  this.li = (title, noIncrement = false)=>{
    let n = noIncrement ? '' : ' '+(ol.children().length+1);
    let li = $('<li>'+title+n+'</li>');
    ol.append(li);
    return li;
  }
}
$(function(){
$.fn.extend({
  ol:function(){
    return new Ol(this);
  }
});
const test = $('#test'), titles = test.ol(), title1 = titles.li('Title'), sections = title1.ol();
for(let i=0,l=10; i<l; i++){
  for(let n=0,chapters=sections.li('Chapter').ol(),q=4; n<q; n++){
    for(let z=0,subs=chapters.li('Section').ol(),c=3; z<c; z++){
      subs.li('sub-section').ol().li('content would go here', true);
    }
  }
}
const title2 = titles.li('Title').ol(), chapter1 = title2.li('Chapter').ol();
const chapter2 = title2.li('Chapter').ol(), chap1Sec1 = chapter1.li('Section').ol();
const chap2Sec1 = chapter2.li('Section').ol();
chap2Sec1.li('sub-section'); chap2Sec1.li('sub-section');
chap1Sec1.li('sub-section').ol().li('This is how you might use outside a loop', true);
});
*{
  margin:0; padding:0;
}
ol{
  list-style:none;
}
ol li{
  margin-left:10px;
}
#test>ol>li{
  margin-left:0;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<div id='test'></div>

This is not perfect and will need to be developed further to account for sections with different variations of headings but should give you a good start point

Using nextUntil() it wraps groups of h1 in a section then loops over those sections to use their index as primary number, then loops over h2 to get secondary number etc

// wrap all h1 in a section
$('h1').each(function() {
  $(this).nextUntil('h1').add(this).wrapAll('<section>');
});
// loop over sections after first one
$('section:gt(0)').each(function(i) {
  const num = i + 1, $sect = $(this);
  $sect.find('h1').prepend(`${num} `);
  $sect.find('h2').each(function(i) {
    const subnum = i + 1;
    $(this).text(function(_, txt) {
      return `${num}.${subnum} ${txt}`
    });

    $(this).nextUntil('h2', 'h3').text(function(i, txt) {
      return `${num}.${subnum}.${i+1} ${txt}`
    });
  });
});
section { border: 2px solid #ccc}
h1 {color: red}
h2 {color: blue}
h3 {color: green}
h1,h2,h3,h4,p{margin:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<article class="markdown-section" id="main">
  <h1 id="about-this-document">About this document</h1>
  <p>This is the online documentation of the <strong>Company Procedures</strong>.</p>

  <h1 id="everyone">Everyone</h1>

  <h2 id="logging-into-your-computer">Logging into your computer</h2>
  <p>These are the instructions on how to log into your computer.</p>

  <ol>
    <li>Step one</li>
    <li>Step two</li>
  </ol>

  <h2 id="programmes-on-taskbar">Programmes on taskbar</h2>

  <h3 id="microsoft-word">Microsoft Word</h3>
  <p>This is a word processor</p>

  <h3 id="mail">Mail</h3>
  <p>This is for your emails</p>

  <h3 id="document-management">Document management</h3>
  <h4 id="windows-explorer">Windows Explorer</h4>
  <h4 id="xyplorer">XYPlorer</h4>

  <h1 id="special-areas">Special areas</h1>

  <h3 id="on-the-road">On the road</h3>
  <h3 id="remote">Remote</h3>

</article>

Related