A JavaScript and jQuery tutorial

Hans's picture
Wed, 2009-08-26 17:48 by Hans · Forum/category:

Introduction

Table 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, combined with the jQuery library. 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 and jQuery as well.

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.

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 XHTML and JavaScript is required. For example, normally you should split up the code, put the JavaScript code into the <head>…</head> of your XHTML document, and the TOC div into the <body>…</body>. In a Content Management System (CMS), such as this here, you can get by with embedding both parts of the code into the inner article code.

If you only want to obtain ready-made code that you can put into your own web pages, read the article, Generate a table of contents instead and download the linked program. It is more powerful and flexible than the simpler examples described here.

But 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.

"HTML with Javascript is going to become the GUI technology of choice, killing off "rich client" and desktop apps written in languages such as C, C++, Java and C#."

Some good JavaScript references are:

What is jQuery?

jQuery is a lightweight JavaScript library that adds a functional DOM (Document Object Model) notation to the language. jQuery is a delight to use, but some of its functions fail unpredictably in older and even in newer browsers. If used carefully, it can be very helpful and can simplify and shorten your code tremendously.

jQuery home page, documentation, tutorials

When you replace pure JavaScript constructs with jQuery constructs, you can often reduce the size of a JavaScript program to one third of its original size, because jQuery is so powerful in its expressions. For example, it can replace many loop constructions with much shorter functional notations.

jQuery also attempts to shield JavaScript programs from browser dependencies, such that programs based on jQuery yield identical results on all major browsers. Unfortunately, it does not always succeed, so you still have to test as much as you can in different browsers and browser versions.

The latest minified version of jQuery can be downloaded from http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js

Very simple table of contents in pure JavaScript

The 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 <ul /> tag into the TOC, which will later be filled with <li>…</li> tags by our forthcoming JavaScript program, each containing one TOC element.

A pure, W3C DOM standards-conformant JavaScript program to fill our TOC could look like this:

<script type="text/javascript">//<![CDATA[

// 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 would have to insert this before the closing body tag, which is not standards-conformant (scripts belong in the head tags), but it works in most browsers.

Show sample page in a separate tab or window

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.

Very simple table of contents with jQuery

This article does not explain the basics of jQuery, but it links to them here. If you have never used jQuery before, go at least through one good tutorial, several of which can be found on the jQuery home page. Then use the documentation on that page.

To use jQuery, you have to load the library first. This can be done with the following HTML code, all in one line:

<script type="text/javascript"
    src="http://ajax.googleapis.com/ajax
    /libs/jquery/1/jquery.min.js"></script>

For production pages you should put jQuery on your web server and use it from there, which is faster.

Then we need just one line of code to fill the TOC. Only for better readability this one line of code has been split into several lines here:

<script type="text/javascript">//<![CDATA[
$(function () {
  $("h1").each(function (i) {
    $("#inner-toc ul").append(
        "<li>" + $(this).text() + "</li>"
    );
  });
});
//]]></script>

Translated into English, the code means this:

When the page is loaded and ready,
  find all h1 elements and for each of them
    to the id = inner-toc ul element append
      an li element containing the text
      of this h1 element.

The line:

$(function () {

is only a shorter convenience notation for:

jQuery(document).ready(function () {

Both have exactly the same meaning, namely, execute this function as soon as the document is loaded and ready.

The notation $("h1").each(function (i) { … }); means that for each h1 element the nameless function (i) is called.

That inner callback function gets some useful information to work with. It gets a counter i for each h1 element, which we do not need yet. (You can actually leave out the i, as JavaScript doesn't care much about how many parameters you feed into a function, but we will need it later when we will do numbering.) And it gets this, which is the respective h1 element itself. (It also gets all variables and objects of the containing function in closure fashion, which is how JavaScript works, but there aren't any in this case.)

$("#inner-toc ul") creates a jQuery object encapsulating all elements found with this CSS selector, i.e. it looks for the HTML element with the id inner-toc and looks inside that for all elements with the tag name ul, of which there is only one.

The .append(…) method is very flexible and inserts whatever recognizable parameter it is handed as the new last child into the element that is encapsulated in the jQuery object, here the ul. For simplicity and brevity we hand it an HTML string, which represents an li element and gets inserted into the ul. This looks a bit crude, but is easier and shorter than adding JavaScript code to construct DOM elements and thus saves us all the lengthy code we needed in the first, pure JavaScript example. jQuery could shrink our 7 or more lines of code into one.

Show sample page in a separate tab or window

Clickable table of contents

As 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 and add an anchor link to that id to each TOC item. With JavaScript and jQuery this is not a big problem:

$(function () {
  $("h1").each(function (i) {
    this.id = "c" + i;
    $("#inner-toc ul").append(
      '<li><a href="#' + this.id + '">' +
      $(this).text() + '</a></li>'
    );
  });
});

To create proper id names, which, according to the standard, have to begin with a letter, we put a "c" in front of the counter and add the thus created id to each h1 element.

When we add the list item (li) to the TOC, we wrap it in an anchor like this: <a href="#cx">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 cx.

Show sample page in a separate tab or window

The sample file has an additional line of code that fills the two test chapters with some lengthy text, so you can actually test the clickable TOC and see the chapter jump.

Reach it from the outside

Our 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 work.

For example, when you have a web page http://example.com/ and want to jump directly to chapter c1, the URL http://example.com/#c1 does not take you there (but in this example it does, because we have fixed it already, as described below).

In a static page this works, but since our TOC is constructed dynamically, it comes too late for the URL evaluation, which takes 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:

  if (location.hash) scrollTo(
      0, $(location.hash).offset().top
  );

location.hash, a property of the web browser's DOM (Document Object Model) window object, is the part of the URL of the current web page that begins with the hash character (#) and is followed in our case by the chapter id, for example: #c1

That is exactly the id selector we need to find the chapter heading. $(location.hash) is that heading element, wrapped in an array in a jQuery object. Since jQuery does not offer to scroll or set the focus to that element, but the window object does, we have to apply the scrollTo(0, $(…).offset().top) method, where the first parameter, 0, is the x-coordinate and the second parameter the y-coordinate to scroll to, both in pixels. $(…).offset() is a jQuery method that delivers an object with just the two properties left and top.

Show sample page in a separate tab or window

Same, but go directly to #c1

Stable reach from the outside

One remaining problem with the previous example is that somebody may create a link to one of the chapters, say, to #c7, 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 c8.

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 textToId(…) and call it from our startup function, rather than appending the counter, thus:

$(function () {
  $("#inner-doc h1").each(function (i) {
    var t = $(this);
    var id = textToId(t.text());
    t.attr("id", id);
    $("#inner-toc ul").append(
        '<li><a href="#' + id + '">' +
        t.text() + '</a></li>');
  });

  // If the URL path contains a hash (#)
  // character, scroll to the local link:

  if (location.hash) scrollTo(
      0, $(location.hash).offset().top
  );
});

Now let us create the function textToId(s). Normally functions like this can be constructed with regular expressions, but these cannot distinguish foreign letters like ÄÖÜäöüß. We cannot use \w or [A-Za-z] in a regular expression, because then we would have to list all imaginable foreign letters as well.

But there is a relatively simple solution. The .toUpperCase() and .toLowerCase() string methods have that knowledge. We simply have to test whether a character c is equal to c.toLowerCase() and also equal to c.toUpperCase() to know that it is not a letter.

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 wait—JavaScript 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 static (wrong in JavaScript) we only have to make the variable a member of the function, i.e. in this case write textToId.ids. This makes this variable, actually an array, a member or property of the function object. Remember, in JavaScript functions are objects mostly like every other object.

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 == with === would really not work and make the misconception visible. We should feel sorry for those clueless Java folks until they learn that JavaScript is not Java.

In short, null is an object that evaluates to undefined. undefined is not an object, it is a datatype, but it also evaluates to undefined. A reasonably correct, but even more wordy Java-like formulation would be:

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 || is actually more like a default value operator. If the first value evaluates to a Boolean true, use it, otherwise give out the second value.

Here is the complete function with comments:

function textToId(s) {
  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.
  // Use a jQuery utility function:

  while ($.inArray(id, textToId.ids) >= 0) {
    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;
}

Show sample page in a separate tab or window

Numbering

Finally 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 <ol>…</ol> (ordered list) tag, as opposed to the ul (unordered list) tag we used before.

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 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 <head>…</head> of your XHTML document, the TOC div into the <body>…</body>, and try for yourself.

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.

<script type="text/javascript">//<![CDATA[
$(function () {
  $("#inner-doc h1").each(function (i) {
    i++;
    var t = $(this);
    var id = textToId(t.text());
    t.attr("id", id);
    $("#inner-toc ol").append(
      '<li><a href="#' + id + '">' +
      t.text() + '</a></li>'
    );
    t.prepend(i + ". ");
  });

  // If the URL path contains a hash (#)
  // character, go to the local link:

  if (location.hash) scrollTo(
      0, $(location.hash).offset().top
  );
});

function textToId(s) {
  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;
    if (!precedingCharIsLetter &&
        charIsLetter)
            id += c.toLowerCase();
    if (id.length >= 5) break;
    precedingCharIsLetter = charIsLetter;
  }
  if (!id) id = "ci";
  textToId.ids = textToId.ids || [];
  while ($.inArray(id, textToId.ids) >= 0) {
    id += "i";
    if (id.charAt(id.length - 2) != "i")
        id += "i";
  }
  textToId.ids.push(id);
  return id;
}
//]]></script>

<div id="inner-doc">

<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>

<!-- Sample body of document -->

<h1>A Chapter Heading</h1>

<p>Some content text</p>

<h1>Another Chapter Heading</h1>

<p>Some more content text</p>

</div>

Show sample page in a separate tab or window

Average: 4 (2 votes)