Create a hierarchy of headers as an ordered list

I thought about this for a while, but I can’t find a working solution. I can't even pseudo encode it ...

Say, for example, you have a page with a heading structure like this:

<h1>Heading level 1</h1> <h2>Sub heading #1</h2> <h2>Sub heading #2</h2> <h3>Sub Sub heading</h3> <h2>Sub heading #3</h2> <h3>Sub Sub heading #1</h3> <h3>Sub Sub heading #2</h3> <h4>Sub Sub Sub heading</h4> <h2>Sub heading #4</h2> <h3>Sub Sub heading</h3> 

Using JavaScript (any infrastructure is fine), how would you start creating a list like this: (with nested lists)

 <ol> <li>Heading level 1 <ol> <li>Sub heading #1</li> <li>Sub heading #2 <ol> <li>Sub Sub heading</li> </ol> </li> <li>Sub heading #3 <ol> <li>Sub Sub heading #1</li> <li>Sub Sub heading #2 <ol> <li>Sub Sub Sub heading (h4)</li> </ol> </li> </ol> </li> <li>Sub heading #4 <ol> <li>Sub Sub heading</li> </ol> </li> </ol> </li> </ol> 

Every time I try to start with a certain methodology, it becomes very bloated.

The solution should cross each heading and put it on its corresponding nested list - I repeat it to myself, but I can’t sketch anything!

Even if you have a methodology in your head, but I don’t have time to code it, I still would like to know! :)

Thanks!

+6
javascript html methodology
source share
4 answers

Build a tree first. Pseudo code (because I don't own Javascript):

 var headings = array(...); var treeLevels = array(); var treeRoots = array(); foreach(headings as heading) { if(heading.level == treeLevels.length) { /* Adjacent siblings. */ if(heading.level == 1) { treeRoots[] = heading; // Append. } else { treeLevels[treeLevels.length - 2].children[] = heading; // Add child to parent element. } treeLevels[treeLevels.length - 1] = heading; } else if(heading.level > treeLevels.length) { /* Child. */ while(heading.level - 1 > treeLevels.length) { /* Create dummy headings if needed. */ treeLevels[] = new Heading(); } treeLevels[] = heading; } else { /* Child of ancestor. */ treeLevels.remove(heading.level, treeLevels.length - 1); treeLevels[treeLevels.length - 1].children[] = heading; treeLevels[] = heading; } } 

Then we flip it over, creating a list.

 function buildList(root) { var li = new LI(root.text); if(root.children.length) { var subUl = new UL(); li.children[] = subUl; foreach(root.children as child) { subUl.children[] = buildList(child); } } return li; } 

Finally, insert the LI returned by buildList in the UL for each treeRoots .

In jQuery, you can retrieve header elements this way:

 var headers = $('*').filter(function() { return this.tagName.match(/h\d/i); }).get(); 
+2
source share

The problem is that there is no better way to get the headers in the order of the document. For example, calling jQuery $('h1,h2,h3,h4,h5,h6') will return all your headers, but first will be <h1> , then <h2> s, etc. No main frame job returns elements in document order when you use a comma delimited selector.

You can overcome this problem by adding a generic class to each title. For example:

 <h1 class="heading">Heading level 1</h1> <h2 class="heading">Sub heading #1</h2> <h2 class="heading">Sub heading #2</h2> <h3 class="heading">Sub Sub heading</h3> <h2 class="heading">Sub heading #3</h2> ... 

Now the $('.heading') selector will get everything in order.

Here is how I will do it with jQuery:

 var $result = $('<div/>'); var curDepth = 0; $('h1,h2,h3,h4,h5,h6').addClass('heading'); $('.heading').each(function() { var $li = $('<li/>').text($(this).text()); var depth = parseInt(this.tagName.substring(1)); if(depth > curDepth) { // going deeper $result.append($('<ol/>').append($li)); $result = $li; } else if (depth < curDepth) { // going shallower $result.parents('ol:eq(' + (curDepth - depth - 1) + ')').append($li); $result = $li; } else { // same level $result.parent().append($li); $result = $li; } curDepth = depth; }); $result = $result.parents('ol:last'); // clean up $('h1,h2,h3,h4,h5,h6').removeClass('heading'); 

$result should now be your <ol> .

Also note that this will handle <h4> followed by <h1> (moving more than one level down immediately), but it will not process <h1> followed by <h4> (more than one level up at a time) .

+3
source share

I can imagine many situations where you can overdo it. For many situations, you really only need the appearance of the hierarchy, and not the regenerated HTML hierarchy itself, for which you can do something simple:

 #nav li.h1 { padding: 0 0 0 0px; } #nav li.h1:before { content: 'h1 '; } #nav li.h2 { padding: 0 0 0 10px; } #nav li.h2:before { content: 'h2 '; } #nav li.h3 { padding: 0 0 0 20px; } #nav li.h3:before { content: 'h3 '; } #nav li.h4 { padding: 0 0 0 30px; } #nav li.h4:before { content: 'h4 '; } #nav li.h5 { padding: 0 0 0 40px; } #nav li.h5:before { content: 'h5 '; } #nav li.h6 { padding: 0 0 0 50px; } #nav li.h6:before { content: 'h6 '; } 

 for (i=1; i<=6; i++) { var headers = document.getElementsByTagName('h'+i); for (j=0; j<headers.length; j++) { headers[j].className = 'h'; } } var headers = document.getElementsByClassName('h'); var h1 = document.getElementsByTagName('h1')[0]; h1.parentNode.insertBefore(document.createElement('ul'),h1.nextSibling); h1.nextSibling.id = 'nav'; for (i=0; i<headers.length; i++) { document.getElementById('nav').innerHTML += '<li class="'+headers[i].tagName.toLowerCase()+'">'+headers[i].innerHTML+'</li>'; } 
+1
source share

This will select all the h1-h6 tags in the doc section of the docElTgt document and the order header in the html document will be respected.

 var hItemsList = docElTgt.querySelectorAll('h1, h2, h3, h4, h5, h6'); 

examples of the value, which may be docElTgt:

 docElTgt = document.body; docElTgt = anyelement.id; 

After selecting the entire title, you can apply the algorithm to make the hierarchy an ordered list, as shown by other users.

0
source share

All Articles