Any good codes to generate TOC from HTML heading elements in JavaScript?
I'd like to generate TOC like below in JavaScript:
<ol>
<li>Heading 1</li>
<li>Heading 2
<ol>
<li>Heading 2-1</li>
<li>Heading 2-2</li>
</ol>
</li>
<li>Heading 3</li>
</ol>
And the HTML codes to generate the TOC above:
<section id="toc">
<p>This will be replaced with generated TOC.
</section>
<article>
<h1>Heading 1<h1>
<p>Bla bla bla.</p>
<h1>Heading 2<h1>
<p>Bla bla bla.</p>
<h2>Heading 2-1<h2>
<p>Bla bla bla.</p>
<h2>Heading 2-2<h2>
<p>Bla bla bla.</p>
<h1>开发者_如何学运维Heading 3<h1>
<p>Bla bla bla.</p>
</article>
I'm really stuck :( How do you write the code to generate the TOC? I prefer jQuery or pure JavaScript.
UPDATE
This was quite tough for me, but somehow I suppose I've done:
$(function () {
var assigned_level = 0,
current_level = 0,
id_number = 1,
parent_node = "article",
toc_html = '';
$(parent_node + " *").each(function () {
if (this.nodeName.length === 2 && this.nodeName.charAt(0) === "H") {
$(this).attr("class", "heading");
}
});
$(".heading").each( function () {
current_level = this.nodeName.charAt(1);
$(this).attr('id', "toc-" + id_number);
// Close a list if a same level list follows.
if (assigned_level !== current_level - 1) {
toc_html += "</li>"
}
// Open parent lists if a child list follows.
while (assigned_level < current_level) {
toc_html += "<ol>";
assigned_level += 1;
}
// Close child lists and the parent list if
// the same level parent list follows.
while (assigned_level > current_level) {
toc_html += "</ol></li>";
assigned_level -= 1;
}
toc_html +=
'<li><a href="#' + this.id + '">' + $(this).html() + "</a>";
id_number += 1;
});
// Close everything
while (assigned_level > 0) {
toc_html += "</li></ol>";
assigned_level -= 1;
}
$("#toc").html(toc_html);
});
I still don't understand what I've done :P Perhaps there's more sophisticated ways. Please point me out anything you've found.
Thanks.
first of all, you need to close your h1,h2 tags =)
if you do $("h1, h2, h3, h4, h5, h6")
you'll get the tags in the same order they appear on the document. So you could do a loop on that array and check the level. e.g: if the last tag was H1 and you find a H2, it means that you need to create a <ol>
. On the other hand, if you have a H3 and the next one is an H2, it means that you need to close the <ol>
.
The hardest part would be to close the remaining <ol>
in the end.
Hope this helps.
Don't want to go too into depth at the moment until we see what you've come up with so far.
However, look into .each() .children() and .find(). These should give you some ideas of how to accomplish what you're looking to do.
I know this question is 8 years old, but here's a proposition. Probably not the most efficient way to do it, nor the shortest, but it works.
- First I get an array of all the headings.
- Then I prepare that array by adding the level, id and parent of each heading.
- Then I transform that array into a recursive, hierarchical one.
- Then I generate the HTML ordered lists.
- Then add the list into the TOC.
In the end of your page, add the following JavaScript:
// prepare the array by adding level, ID and parent to each item of the array
function prepare( array ) {
let idt, level, t;
for( let i = 0, n = array.length; i < n; i++ ) {
t = array[ i ];
t.el = t;
level = parseInt( t.tagName[1], 10 );
t.level = level;
t.idt = i + 1;
if( level <= 1 ) t.parent = 0;
if( i ) {
if( array[ i - 1 ].level < level ) {
t.parent = array[ i - 1 ].idt;
} else if( array[ i - 1 ].level == level ) {
t.parent = array[ i - 1 ].parent;
} else {
for( let j = i - 1; j >= 0; j-- ) {
if( array[ j ].level == level - 1) {
t.parent = array[ j ].idt;
break;
}
}
}
}
}
return array;
}
// transform a flat array in a hierarchical array
function hierarchical( items ) {
let hashTable = Object.create( null );
items.forEach( item => hashTable[ item.idt ] = { ...item, subitems : [] } );
let tree = [];
items.forEach( item => {
if( item.parent )
hashTable[ item.parent ].subitems.push( hashTable[ item.idt ] );
else
tree.push(hashTable[ item.idt ]);
});
return tree;
}
// return an UL containing each title in a LI and possibly other items in UL sub-lists.
function add_list( titles ) {
let li, a, anchor;
let ol = document.createElement( "ol" );
if( titles && titles.length ) {
for( t of titles ) {
if( t.el.id ) anchor = t.el.id;
else anchor = t.el.textContent;
if( ! anchor ) anchor = "inconnu";
anchor = anchor.replace( /\W/g, "" );
t.el.id = anchor;
li = document.createElement( "li" );
a = document.createElement( "a" );
a.href = `#${anchor}`;
a.innerHTML = t.el.textContent;
li.append( a );
if( t.subitems && t.subitems.length ) {
li.append( add_list( t.subitems ) );
}
ol.append( li );
}
}
return ol;
}
//get the toc element
let divtoc = document.getElementById( "toc" );
// get the article element
let article = document.getElementById( "article" );
if( toc && article ) {
let titles = article.querySelectorAll( "h1, h2, h3, h4, h5, h6" );
titles = prepare( titles );
titles = hierarchical( titles );
let ol_racine = add_list( titles );
toc.append( ol_racine );
}
You'll get the intended result:
<section id="toc">
<ol>
<li><a href="#Heading1">Heading 1</a></li>
<li>
<a href="#Heading2">Heading 2</a>
<ol>
<li><a href="#Heading21">Heading 2-1</a></li>
<li><a href="#Heading22">Heading 2-2</a></li>
</ol>
</li>
<li><a href="#Heading3">Heading 3</a></li>
</ol>
</section>
精彩评论