Say I have a basic page like so:
<custom:TableOfContents />
<h1>Some Heading</h1>
<h2>Foo</h2>
<p>Lorem ipsum</p>
<h2>Bar</h2>
<p>Lorem ipsum</p>
<h2>Baz</h2>
<p>Lorem ipsum</p>
<h1>Another Heading</h2>
<h2>Qux</h2>
<p>Lorem ipsum</p>
<h2>Quux</h2>
<p>Lorem ipsum</p>
Assume all the header tags exist as server side controls. Is there some web control <custom:TableOfContents />
for ASP.NET webforms that will dynamically generate a table of contents that looks something like the following (when rendered to the screen):
1. Some Heading
1.1. Foo
1.2. Bar
1.3. Baz
2. Another Heading
2.1. Qux
2.2. Quux
Ideally, each entry in the table of contents would be a hyperlink to a dynamically generated anchor at the appropriate place on the page. Also, it would be nice if the text of each header tag could be prefixed with its section number.
If not a web control, is there some easier way of doing this? Keep in mind that many of the header tags are going to be created by data bound controls, so manually maintaining the table of contents is not an option. It seems like the webforms model is ideally suited to creating such a control, which is why I'm surprised I haven't yet found one.
I needed to do a similar thing a few days ago and, though not a webcontrol, used jQuery.
$(document).ready(buildTableOfContents);
function buildTableOfContents() {
var headers = $('#content').find('h1,h2,h3,h4,h5,h6');
var root, list;
var previousLevel = 1;
var depths = [0, 0, 0, 0, 0, 0];
root = list = $('<ol />');
for (var i = 0; i < headers.length; i++) {
var header = headers.eq(i);
var level = parseInt(header.get(0).nodeName.substring(1));
if (previousLevel > level) {
// Move up the tree
for (var L = level; L < previousLevel; L++) {
list = list.parent().parent();
depths[L] = 0;
}
} else if (previousLevel < level) {
// A sub-item
for (var L = previousLevel; L < level; L++) {
var lastItem = list.children().last();
// Create an empty list item if we're skipping a level (e.g., h1 -> h3)
if (lastItem.length == 0)
lastItem = $('<li />').appendTo(list);
list = $('<ol />').appendTo(lastItem);
}
}
depths[level - 1]++;
// Grab the ID for the anchor
var id = header.attr('id');
if (id == '') {
// If there is no ID, make a random one
id = header.get(0).nodeName + '-' + Math.round(Math.random() * 1e10);
header.attr('id', id);
}
var sectionNumber = depths.slice(0, level).join('.');
list.append(
$('<li />').append(
$('<a />')
.text(sectionNumber + ' '+ header.text())
.attr('href', '#' + id)));
previousLevel = level;
}
$('#table-of-contents').append(root);
}
This will make an ordered list and append it to #table-of-contents
with appropriate numbering (e.g., 1.1). Just a little bit of CSS is needed to hide the lists' built in numbering: #table-of-contents ol { list-style:none; }
.