Navigation
Popular content
Today's:Active forum topics
|
A JavaScript tutorial![]() IntroductionTable of contents for this article This article describes the automatic construction of a table of contents (TOC) in web pages, and it is also a tutorial for the programming language JavaScript. Beginners may have to follow the included links to good introductory tutorials first. This article is most useful for programmers who already have some knowledge of JavaScript. If you are not interested in the programming and just want to have a ready-made table of contents, please read Generate a table of contents instead, which offers a more powerful and flexible program than the simple versions shown here in the tutorial and that can be used instantly. If you want to use the jQuery JavaScript library, read A JavaScript and jQuery tutorial, which is similar, but involves jQuery. At the end of each tutorial chapter you find a download link for a complete example page. If you are interested only in the functioning code, you can copy it into your pages, but minor adaptations are necessary, so some minimal knowledge of HTML and JavaScript is required. For example, normally you should split up the code, put the JavaScript code into the If you want to obtain code you can understand and modify, the chapters Stable reach from the outside (without chapter numbering) and Numbering (with chapter numbering) have the most useful code to integrate into your own pages. The easiest way is probably to use the sample page link at the end of either of these two chapters, download the source, adapt it, and put it in your page. in a normal, non-CMS environment you can remove all references to "inner-doc", including the div closing tag at the very end, and keep only the "inner-toc" div. What is JavaScript?JavaScript, also known as ECMAScript and JScript (Microsoft's name for JavaScript), is a relatively small language. Nonetheless it is one of the most advanced and powerful languages of our time. It is a script language, but the first compilers are appearing that take the language quite a bit further along the way to compete with established languages like C# or Java. One of its major advantages is that it is available in all major web browsers, which makes it the primary choice for programs that are loaded within a web page and run on the client computer. In theory, it should be desirable to offload as much of the work of any computer system to the clients as possible, leaving the central server to provide data and any special service that the client computer, for any reason, cannot deliver. Unfortunately this does not happen to the extent that it should happen. The reason is that the power of JavaScript is widely unknown and extremely underestimated. Some reasons for that, in turn, are that JavaScript differs from classic programming languages and that many programmers believe that it is lacking many features. For example, JavaScript has no classes like Java or C#. Yet it does have a very flexible inheritance concept and is considerably more powerful than Java in its expressiveness. Unlike Java is has Lambda capability (methods can be first-class objects) and it can add methods to objects and remove them from objects even at runtime, which Java cannot even do at compile time, unless you have access to and alter the source code of the class. Many features of other languages, for example, namespaces, private variables, modules or packages, do not explicitly exist in JavaScript, but can easily be emulated through the very powerful and flexible constructs of the language.
Some good JavaScript references are:
Very simple table of contentsThe single example we will use in this article is an automatically generated table of contents. The idea is that you add a short piece of JavaScript code to a web page, after loading the code will scan the page for headings and will automatically construct and display a table of contents. To display the table of contents (TOC), we will use an HTML div that floats in the top right of the page, but of course you can display the TOC wherever you like and format it to your liking. Here is the HTML code for the empty TOC: <div id="inner-toc" style="float: right; width: 45%; border: 1px solid #8ad; margin: 0.3em 0 0.2em 0.2em; padding-right: 0.2em; padding-bottom: 0.7em;"> <p style="margin-bottom: 0; text-align: center; font-weight: bold; color: darkblue">Table of contents</p> <ul /> </div> The id, "inner-toc", is named inner because it is meant to be embedded in a Content Management System (CMS) like this here, where there may already be an outer TOC and we want to add one only for the (inner) article itself. We put an empty A pure, W3C DOM standards-conformant JavaScript program to fill our TOC could look like this:
<script>
// Get the ul element from inner-toc:
var tocList = document.
getElementById("inner-toc").
getElementsByTagName("ul")[0];
// Get all h1 headings:
var headings = document.
getElementsByTagName("h1");
// Loop through all the h1 headings:
for (var i = 0; i < headings.length; i++) {
var heading = headings[i];
// Create a new li TOC entry:
var li = document.createElement("li");
// Take the text node out of the heading
// and put it into the new li TOC entry:
li.appendChild(
heading.firstChild.cloneNode()
);
// Insert the li at the end of the TOC:
tocList.appendChild(li);
}
</script>
To activate it after the content of the HTML page is loaded, you have three choices:
To see the complete HTML/JavaScript source code, use your browser's function to show the source or download the file and inspect or modify it at your leisure. Note that these samples are written in HTML 5. In XHTML the script tags should look like this: <script type="text/javascript">//<![CDATA[ // JavaScript code goes here. //]]></script> Clickable table of contentsAs nice and short as the previous example is, it has a few obvious problems. The first one is that the TOC is not clickable. What we really want is that the user clicks on one of the items in the TOC and is instantly beamed there. To achieve that, we need to give each h1 header an id with the statement: heading.id = "heading" + (i + 1); And we also add an anchor link to that id to each TOC item. With JavaScript this is not a big problem. This piece of code does it:
// Create a new li TOC entry:
var li = document.createElement("li");
// Take the text from the heading and
// put it into the new li TOC entry:
li.innerHTML =
'<a href="#' + heading.id + '">' +
heading.innerHTML + '</a></li>';
// Insert the li at the end of the TOC:
tocList.appendChild(li);
To create proper id names, which, according to the standard, have to be unique and begin with a letter, we put "header" in front of the counter and add the thus created id to each h1 element. We also give the first heading the id "heading1", rather than "heading0", for neatness. When we add the list item (li) to the TOC, we wrap it in an anchor like this: <a href="#headingx">heading text</a> where x is the number of the current heading. This makes the heading clickable and leading to the chapter with the id headingx. So this is the entire JavaScript code for the clickable TOC:
<script>
window.onload = function () {
// Get the ul element from inner-toc:
var tocList = document.
getElementById("inner-toc").
getElementsByTagName("ul")[0];
// Get all h1 headings:
var headings = document.
getElementsByTagName("h1");
// Loop through all the h1 headings:
for (var i = 0; i < headings.length; i++) {
var heading = headings[i];
// Give each heading a unique id:
heading.id = "heading" + (i + 1);
// Create a new li TOC entry:
var li = document.createElement("li");
// Take the text from the heading and
// put it into the new li TOC entry:
li.innerHTML =
'<a href="#' + heading.id + '">' +
heading.innerHTML + '</a></li>';
// Insert the li at the end of the TOC:
tocList.appendChild(li);
}
}
</script>
The sample file has some additional JavaScript code near the end that puts some text paragraphs after each heading to make the chapters longer, so you can actually see the chapter jump when you test the clickable TOC. That code could just as well reside inside our window.onload function. It has been put at the end only to have it out of the way, so the actual function remains free of code that is only used for the demo. Reach it from the outsideOur program is already quite usable, but it still has a few problems. The next one we will tackle is that the chapter can be reached by clicking in the TOC, but when you use the usual method to add the chapter id to the URL, so somebody can call up that URL and go directly to the chapter from the outside, it does not always work, depending on various circumstances like the presence of other JavaScript libraries or the function inside a Content Management System. For example, when you have a web page http://example.com/ and want to jump directly to chapter heading2, the URL http://example.com/#heading2 does not reliably take you there. In a static page this works, but since our TOC is constructed dynamically, it may, depending on circumstances, come too late for the URL evaluation, which may take place before the JavaScript program is executed and the TOC built. But even this is not a big problem for us. We can tell the JavaScript program to look for the hash (#) character in the URL path and do the jumping later, when it discovers the hash and the chapter id. The following bit of code solves this problem.
// If the URL path contains a hash (#)
// character, go to the local link:
var el;
if (location.hash &&
(el = document.getElementById(
location.hash.substr(1)))
) scrollTo(0, absTop(el));
// Determines the absolute top position
// of an element:
function absTop(el) {
var t = 0;
do t += el.offsetTop;
while (el = el.offsetParent);
return t;
}
The scrollTo(0, absTop(el))
method scrolls down, where the first parameter, Same demo, but this time go directly to #heading2 Stable reach from the outsideOne remaining problem with the previous example is that somebody may create a link to one of the chapters, say, to #heading7, but the original article may change. Chapters may get inserted, deleted, or shifted, and so their numbers would change. The link would then point to the wrong chapter, because the chapter may now carry the id heading8. A nice method to work around this is to give each chapter an id that is derived from the chapter heading. What we will do is devise a function that turns the chapter heading text into an id by taking the first letter from each of the first five words (or fewer if there are fewer than five words) in lower case. For example, from the heading, "Stable reach from the outside" we would derive the id "srfto". That is also not always guaranteed to work and will fail if the heading itself changes, but that is much less likely than a chapter shift, and the id is easy to derive and remember. It will even survive the occasional typo correction. To facilitate this, we invent a new function
for (var i = 0; i < headings.length; i++) {
var heading = headings[i];
// Give each heading a unique id:
var id = textToId(heading.innerHTML);
heading.id = id;
// Create a new li TOC entry:
var li = document.createElement("li");
// Take the text from the heading and
// put it into the new li TOC entry:
li.innerHTML =
'<a href="#' + id + '">' +
heading.innerHTML + '</a></li>';
// Insert the li at the end of the TOC:
tocList.appendChild(li);
}
Now let us create the function But there is a relatively simple solution. The But what if there are two chapter headings that yield the same id? In that case we will simply append an additional letter i to the second heading, similar to a Roman number, until we get a unique id. The first time we will append two i letters, to make it more similar to a Roman counter, as the first similar chapter heading gets no additional i at all in the end. To do this we remember all ids by pushing each id into a static array and test every new id against all ids that are already there. But waitJavaScript doesn't have static variables, or does it? This is a good example for the power of JavaScript. The language doesn't have as many explicit features as some classic programming languages, but it is always fairly easy to emulate them. Instead of We have to initialize the array once as an empty array, to be able to push values into it. We do this with the line: textToId.ids = textToId.ids || []; which has the same meaning as: if (!textToId.ids) textToId.ids = []; as a Java programmer would clumsily write. Or no, a Java programmer would probably write even more clumsily:
if (textToId.ids === undefined)
textToId.ids = new Array();
Actually, I fear that a Java programmer might write:
if (textToId.ids == null)
textToId.ids = new Array();
which does work, but not how the Java programmer believes it works. Replacing In short,
if (typeof textToId.ids === "undefined")
textToId.ids = new Array();
But how very clumsy this is! No real JavaScript programmer would ever write such heavy-handed stuff. Let's stick to the elegant form of: textToId.ids = textToId.ids || [];
and remember that the logical or operator Here is the complete function with comments:
function textToId(s) {
// Creates a unique id from the first
// letters of the first 5 words.
var id = "";
var precedingCharIsLetter = false;
for (var i = 0; i < s.length; i++) {
var c = s.charAt(i);
var charIsLetter =
c.toLowerCase() !== c ||
c.toUpperCase() !== c;
// Look for a letter
// following a non-letter:
if (!precedingCharIsLetter &&
charIsLetter)
id += c.toLowerCase();
if (id.length >= 5) break;
precedingCharIsLetter =
charIsLetter;
}
// If there are no letters at all,
// use the id "ci":
if (!id) id = "ci";
// One-time array initialization:
textToId.ids = textToId.ids || [];
// If an earlier chapter has
// the same id, append an i:
while (inArray(id, textToId.ids)) {
id += "i";
// If there is only one i, add another:
if (id.charAt(id.length - 2) != "i")
id += "i";
}
// Append the resulting id to the array:
textToId.ids.push(id);
return id;
}
To check whether a certain value is in an array, we use this little function:
function inArray(v, a) {
// Checks whether the array a contains v.
for (var i = 0; i < a.length; i++)
if (v === a[i]) return true;
return false;
}
NumberingFinally we can add numbering to the chapter headings and to the table of contents. By the way, this article itself uses the same code. You can see the resulting TOC at the top. In the TOC we will leave the numbering to HTML by means of the
var toc = document.
getElementById("inner-toc");
var tocList =
toc.getElementsByTagName("ul")[0] ||
toc.getElementsByTagName("ol")[0];
The actual chapter headings, however, will need to be changed. Our JavaScript program will count them and insert the counter, followed by a period (.) and a space, in front of the headings. The following statement does it:
if (tocList.tagName === "OL")
heading.innerHTML = (i + 1) + ". " +
heading.innerHTML;
The following is the final code for a table of contents including chapter numbering. To test it on one of your own web pages, just copy the JavaScript code into the The inner-doc div is not needed for a standalone web page. It is only needed when the document is embedded somewhere, for example in a Content Management System like this one here. If you don't need that, you can remove the inner-doc div tags and the reference to it. JavaScript code in script element, to put into head:
<script>
window.onload = function () {
// Get the ul or ol element from
// inner-toc:
var toc = document.
getElementById("inner-toc");
var tocList =
toc.getElementsByTagName("ul")[0] ||
toc.getElementsByTagName("ol")[0];
// Get all h1 headings:
var headings = document.
getElementsByTagName("h1");
// Loop through all the h1 headings:
for (var i = 0; i < headings.length; i++) {
var heading = headings[i];
// Give each heading a unique id:
var id = textToId(heading.innerHTML);
heading.id = id;
// Create a new li TOC entry:
var li = document.createElement("li");
// Take the text from the heading and
// put it into the new li TOC entry:
li.innerHTML =
'<a href="#' + id + '">' +
heading.innerHTML + '</a></li>';
// Insert the li at the end of the TOC:
tocList.appendChild(li);
// If the user provided an ol tag,
// number the headings themselves also:
if (tocList.tagName === "OL")
heading.innerHTML = (i + 1) + ". " +
heading.innerHTML;
}
// If the URL path contains a hash (#)
// character, go to the local link:
var el;
if (location.hash &&
(el = document.getElementById(
location.hash.substr(1)))
) scrollTo(0, absTop(el));
// Determines the absolute top position
// of an element:
function absTop(el) {
var t = 0;
do t += el.offsetTop;
while (el = el.offsetParent);
return t;
}
function textToId(s) {
// Creates a unique id from the first
// letters of the first 5 words.
var id = "";
var precedingCharIsLetter = false;
for (var i = 0; i < s.length; i++) {
var c = s.charAt(i);
var charIsLetter =
c.toLowerCase() !== c ||
c.toUpperCase() !== c;
// Look for a letter
// following a non-letter:
if (!precedingCharIsLetter &&
charIsLetter)
id += c.toLowerCase();
if (id.length >= 5) break;
precedingCharIsLetter =
charIsLetter;
}
// If there are no letters at all,
// use the id "ci":
if (!id) id = "ci";
// One-time array initialization:
textToId.ids = textToId.ids || [];
// If an earlier chapter has
// the same id, append an i:
while (inArray(id, textToId.ids)) {
id += "i";
// If there is only one i, add another:
if (id.charAt(id.length - 2) != "i")
id += "i";
}
// Append the resulting id to the array:
textToId.ids.push(id);
return id;
}
function inArray(v, a) {
// Checks whether the array a contains v.
for (var i = 0; i < a.length; i++)
if (v === a[i]) return true;
return false;
}
}
</script>
Empty table of contents div to put into body:
<div id="inner-toc"
style="float: right; width: 45%;
border: 1px solid #8ad;
margin: 0.3em 0 0.2em 0.2em;
padding-right: 0.2em;
padding-bottom: 0.7em;">
<p style="margin-bottom: 0;
text-align: center; font-weight: bold;
color: darkblue">Table of contents</p>
<ol />
</div>
To avoid numbering, replace the |
User login
Donations If this web site has helped you, please help us too! Recent blog posts
Windows news ticker
Who's new
Who's online
There are currently 0 users and 7 guests online.
hits since 2010-03-12 · Statistics |
1 day 14 hours ago
1 day 23 hours ago
2 days 12 hours ago
3 days 22 hours ago
6 days 18 hours ago
1 week 1 hour ago
1 week 1 hour ago
1 week 4 hours ago
1 week 5 hours ago
1 week 1 day ago