Enhancing Structural Markup with JavaScript

Share this article

Just a few years ago, the key skill involved in writing HTML was knowing enough table tricks to persuade the two major browsers to do more or less what you wanted them to. The modern Web is a very different beast, where the quality of your markup is judged on the basis of how well you use structural elements such as headers, paragraphs and lists to describe your content.
The benefits of this approach have been explained many times before: more maintainable code, smaller file sizes, better accessibility and the ability to control your site’s look and feel from a single style sheet rather than hacking away at sprawling chunks of markup spread across multiple pages. An advantage that is not so frequently discussed is that well structured markup opens the door for additional site enhancements based around that long-abused third leg of the client-side Web, Javascript. This article will look at two ways in which Javascript and well structured markup can work together. The first example will show how Javascript can enhance a blockquote by hooking into its cite attribute. The second will demonstrate a “best of breed” script for building links that toggle which panel is visible on a page.
Blockquote Citations
For our first example, let’s take a look at the humble blockquote element. Often misused to apply indentation, the correct usage of this element is to mark up quotations that should appear visually separate from surrounding text. The opening blockquote tag can take an optional cite attribute, which should contain the URL of the page on which the quotation originated. The only problem with the cite attribute is that browsers completely ignore it. Markup purists may appreciate it, but, from a purely practical point of view, nothing is gained by using it save a feeling of smug satisfaction at using the correct markup. This is where Javascript comes in. Using the DOM, it is possible to add a link to the quotation source at the bottom of any blockquote that has a cite attribute. Here’s the code for a function that does just that:
function extractBlockquoteCitations() { 
  var quotes = document.getElementsByTagName('blockquote'); 
  for (var i = 0; i < quotes.length; i++) { 
    var cite = quotes[i].getAttribute('cite'); 
    if (cite != '') { 
      var a = document.createElement('a'); 
      a.setAttribute('href', cite); 
      a.setAttribute('title', cite); 
      a.appendChild(document.createTextNode('Source')); 
      var p = document.createElement('p'); 
      p.className = 'blockquotesource'; 
      p.appendChild(a); 
      quotes[i].appendChild(p); 
    } 
  } 
}
Let’s take a close look at the body of the function.
var quotes = document.getElementsByTagName('blockquote');
This line uses the DOM method, getElementsByTagName, to find all of the blockquote elements in the current page, assigning them to an array (it’s actually an HTMLCollection, but conveniently this is a data structure that behaves like an array) called quotes.
for (var i = 0; i < quotes.length; i++) { 
  var cite = quotes[i].getAttribute('cite'); 
  if (cite != '') {
Now we’re looping through the gathered blockquote nodes. Each time, we use the getAttribute method to retrieve the cite attribute from the element. If the cite attribute has been set, we go on to the fun part: creating a “Source” link at the bottom of the quotation.
    var a = document.createElement('a'); 
    a.setAttribute('href', cite); 
    a.setAttribute('title', cite);
When we want dynamically to add new HTML elements to a page using the DOM, the correct way of doing so is to create the elements programmatically using the createElement method. The above lines create a new ‘a’ element and assign it href and title attributes, both set to the URL of the citation. a.appendChild(document.createTextNode('Source')); We want the link element to contain some text that the user can click on to activate the link. Raw text nodes are created using the createTextNode method. The DOM treats HTML elements as forming a tree, so to add the text to our newly created link we need to call its appendChild method.
    var p = document.createElement('p'); 
    p.className = 'blockquotesource'; 
    p.appendChild(a);
To allow us to flexibly style the new link using CSS, we can wrap it in a paragraph element. The above code creates such an element, sets its class to ‘blockquotesource‘ to provide a hook for our CSS, then adds the link to it using appendChild. At this point, the new document fragment we have constructed is equivalent to the following HTML:
<p class="blockquotesource"> 
<a href="[cite URL]" title="[cite URL]">Source</a> 
</p>
At the moment, out fragment remains invisible because, while we have created it in memory, we have not yet attached it to our document. The last line in the function does just that:
    quotes[i].appendChild(p);
quotes[i] is the blockquote element we’re currently processing. appendChild attaches our new paragraph to the blockquote, making it visible in the process. There are two more steps. Firstly, we need the above function to run when the page is first loaded. There are a number of ways of achieving this. The most simple is to add a call to the function to the onload attribute of the document’s body element:
<body onload="extractBlockquoteCitations();">
This works just fine, but we can do better. Since our Javascript function will be hosted in an external file, wouldn’t it make sense for the external file to cause the function to run as well? The naive way of doing that is with the following line of Javascript:
window.onload = extractBlockquoteCitations;
//
Note that we have provided the name of the function, but neglected the ()
at the end, which would cause the function to execute. Javascript supports the functional programming style, which means that functions can be treated just like any other data object and passed around as arguments, stored in data structures, or even returned from other functions. I will talk about this topic more in a future article, but the upshot of it is that assigning a function to window.onload will cause it to execute once the page has finished loading. This solution, however, also has a drawback. If you want to use on a given page multiple scripts that execute when the page has finished loading, the last script to register itself with window.onload will be the only script to execute. What’s really needed is a way to attach our function to the onload handler of the window object without overwriting what’s already there. Unfortunately, Internet Explorer and other browsers differ on how this kind of dynamic event attachment should be handled; fortunately, Scott Andrew has released a function that handles these differences for you. Here’s the function:
function addEvent(obj, evType, fn){ 
  if (obj.addEventListener){ 
    obj.addEventListener(evType, fn, false); 
    return true; 
  } else if (obj.attachEvent){ 
    var r = obj.attachEvent("on"+evType, fn); 
    return r; 
  } else { 
    return false; 
  } 
}
And here’s the code to add our blockquotes function to the load event of the window object:
addEvent(window, 'load', extractBlockquoteCitations);
The final step is to style our quotations using CSS. Here’s a relatively simple CSS snippet for handling blockquotes:
blockquote { 
  border-left: 0.25em solid navy;  
  padding: 0 0.5em;  
  margin: 0.5em 1.5em 0.5em 2.5em;  
} 
blockquote p.blockquotesource { 
  font-weight: bold; 
  font-size: 0.8em; 
  text-align: right; 
  padding-top: 0.5em; 
}
The finished product can be viewed here.    
Panel Switching
Now, let’s consider a more advanced dynamic effect — a panel switcher. The objective here is to have a number of panels (marked up using divs) on a page, of which only one is visible at a time. A set of links that remain constantly visible can be used to select which of the panels is currently on display. This could be useful for building something like a tabbed interface for browsing a series of related screens without requiring a page refresh every time one of the tabs is selected. A good rule to keep in mind whenever Javascript is used to enhance a page is that the page must still be usable with Javascript disabled. In this case, this means that the ideal solution would work as advertised with Javascript turned on, but would display all of the panels on the page in a non-Javascript environment, with each of the links linking directly to the relevant panel, using a URL fragment. Here, then, is the simplest markup that could possibly work:
<a href="#p1">Panel 1</a> | <a href="#p2">Panel 2</a>  

<div id="p1">This is Panel 1</div>  
<div id="p2">This is Panel 2</div>
Surprisingly, the above is nearly all the markup we need in order to hook in some Javascript to create the desired effect. We could just go ahead and use the above code, but let’s add a class to the links to explicitly state that we wish to do something special with them:
<a href="#p1" class="toggle">Panel 1</a> |   
<a href="#p2" class="toggle">Panel 2</a>
//
Here’s how the Javascript is going to work. When the page loads, the script will scan through all the links on the page looking for any that have “toggle” in their class. For any that are found, the href attribute will be examined, and the element with the ID specified there will be located and added to an array of targeted elements. All but the first of these elements will be “switched off”, so when the page loads, only the first panel will remain visible. The links themselves will have Javascript event handlers attached to them so that, when they’re activated, their corresponding panel can be displayed. The full script can be viewed here . There follows a walk through of how the code works.
var et_toggleElements = [];
This first line creates a global empty array, which will hold references to the panel elements on the page. Because this script has a global variable and a number of functions, we will be prefixing each with “et_” (for “easy toggle”) — this lowers the chance of our functions suffering name clashes with other scripts loaded by the same page.
/* Initialisation */  
function et_init() {  
  var i, link, id, target, first;  
  first = true;  
  for (i = 0; (link = document.links[i]); i++) {
So far, we’ve initialised some variables, set the first flag to true and started to iterate over all the links in the document. Declaring the variables using var is important because it ensures the variables are local to the function. Without this step, they would be globally accessible and could interfere with other scripts.
    if (/btoggleb/.exec(link.className)) {
This conditional checks that the current link has ‘toggle’ in its class. We’re using a regular expression, rather than just checking if link.className == 'toggle', because the class attribute can contain more than one class, separated by spaces. /btoggleb/ is the regular expression; the b parts match a “word boundary”, which could be a space or the beginning or end of the string.
      id = link.href.split('#')[1];

If the link has toggle in its list of classes, we assume that the target of the link is a URL fragment.
link.href.split('#') splits the link href at the # mark -- we know that the part we're interested in comes after the #, so we directly index the resulting array with [1] to pull out the targeted ID.
      target = document.getElementById(id);  
      et_toggleElements[et_toggleElements.length] = target;
Here, we make another assumption — that the element indicated by the link actually exists. We grab that element using the getElementById() method, then add it on to our array of elements by assigning it to the array index that equals the current length of the array. This works because arrays are indexed from 0, but the array length counts starting from 1; hence, the length of the array is also the index of the next empty slot in the array.
      if (first) {  
        first = false;  
      } else {  
        target.style.display = 'none';  
      }
This is where the first flag we defined earlier makes itself useful. We want the first panel on the site to remain visible, while the others are hidden using the Javascript equivalent of ‘display: none‘ in CSS. The flag allows us to do this.
      link.onclick = et_toggle;  
    }  
  }  
}
    Finally, we assign the et_toggle function to the link’s onclick event, causing the function to be called whenever the links is activated. The next step is to define that function.
function et_toggle(e) {   
  if (typeof e == 'undefined') {   
    var e = window.event;   
  }   
  var source;   
  if (typeof e.target != 'undefined') {   
    source = e.target;   
  } else if (typeof e.srcElement != 'undefined') {   
    source = e.srcElement;   
  } else {   
    return true;   
  }
This first block of code exists to make up for another difference between the way Internet Explorer and other browsers handle events. We need to know which link was activated when the function was called, as this will allow us to identify the panel which should be displayed. The above code identifies the element from which the event originated and places it in the source variable, using code adapted from Peter-Paul Koch’s excellent explanation of the issue on QuirksMode:
  if (source.nodeType == 3) {   
    source = targ.parentNode;   
  }
This code is there for compatibility with Safari. All other browsers that I tested returned the actual link element as the source of the click event, but Safari returned instead the text node contained inside the link. Text nodes have their nodeType set to 3 (a constant defined by the W3C DOM), so, by checking for this, we can identify this problem and reset the target to the parent of the text node, which should be the link element.
  var id = source.href.split('#')[1];
//
Again we extract the ID from the link using the split method. var elem; for (var i = 0; (elem = et_toggleElements[i]); i++) { if (elem.id != id) { elem.style.display = 'none'; } else { elem.style.display = 'block'; } } Now that we know which panel we wish to display, we can cycle through our array of elements, switching off everything except the one with an ID that matches the ID of the required panel.
  return false;   
}
By returning false, we prevent the link from actually being followed when it is activated, which could result in an undesirable jump down the page viewed in the browser. The final step is to register the et_init function with the load event of the window, using the addEvent function described earlier.
addEvent(window, 'load', et_init);
You can see the finished code in action here.
Conclusion
In this article, we have seen two ways in which well structured markup can be paired up with Javascript and the W3C DOM to achieve a useful effect. I hope this article has inspired you to seek new ways of using Javascript and intelligent markup.
Further Reading
There are many other excellent examples of Javascript effects based on structural markup. Here are just a few that are worth checking out:

Frequently Asked Questions about Structural Markup in JavaScript

What is structural markup in JavaScript?

Structural markup in JavaScript refers to the use of HTML elements to describe both the content and the structure of a web document. These elements include headers, paragraphs, lists, and more. JavaScript can manipulate these elements to create dynamic and interactive web pages. For example, JavaScript can change the content of an HTML element, change the style (CSS) of an HTML element, or even add new HTML elements.

How does JavaScript interact with HTML elements?

JavaScript interacts with HTML elements through the Document Object Model (DOM). The DOM represents the structure of a web page and can be manipulated by JavaScript. For instance, JavaScript can create a new text node using the ‘document.createTextNode()’ method and then append it to an existing HTML element.

What is a text node in JavaScript?

A text node in JavaScript is a type of node that represents the textual content of an element or attribute. It can be created using the ‘document.createTextNode()’ method. This method creates a new text node that can be appended to an existing HTML element, allowing you to dynamically add text to a web page.

How do I add JavaScript to HTML?

JavaScript can be added to HTML in several ways. You can include it directly in your HTML file using the ‘script’ tag, or you can link to an external JavaScript file using the ‘src’ attribute of the ‘script’ tag. You can also use event attributes like ‘onclick’ or ‘onload’ to run JavaScript code when a specific event occurs.

How do I use the ‘document.createTextNode()’ method?

The ‘document.createTextNode()’ method is used to create a new text node. You first call this method with the text you want to add as an argument. This will return a new text node, which you can then append to an existing HTML element using the ‘appendChild()’ method.

What is the Document Object Model (DOM)?

The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the structure of a document as a tree of objects, with each object representing a part of the document. JavaScript can interact with the DOM to manipulate the content and structure of a web page.

How do I change the content of an HTML element using JavaScript?

You can change the content of an HTML element using the ‘innerHTML’ property in JavaScript. This property can be used to get or set the HTML content of an element. For example, if you have an element with the id ‘myElement’, you can change its content like this: ‘document.getElementById(‘myElement’).innerHTML = ‘New content’;’.

How do I change the style of an HTML element using JavaScript?

You can change the style of an HTML element using the ‘style’ property in JavaScript. This property is an object that contains all the CSS properties of an element. For example, if you have an element with the id ‘myElement’, you can change its color like this: ‘document.getElementById(‘myElement’).style.color = ‘red’;’.

How do I add a new HTML element using JavaScript?

You can add a new HTML element using the ‘createElement()’ method in JavaScript. This method creates a new element, which you can then append to an existing element using the ‘appendChild()’ method. For example, you can create a new paragraph and add it to the body of your document like this: ‘var p = document.createElement(‘p’); document.body.appendChild(p);’.

What are event attributes in JavaScript?

Event attributes in JavaScript are used to define the JavaScript code to be run when a specific event occurs. These events can include things like a button being clicked (‘onclick’), the page loading (‘onload’), or the mouse moving over an element (‘onmouseover’). The code for the event is defined directly in the attribute in the HTML element.

Simon WillisonSimon Willison
View Author
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week