Skip to main content

A neat little solution that uses Ajax to fetch definitions. Works with and without JavaScript available. Easily tailored to suit inclusion into a database.


An accessible AJAX glossary

author: mike foskett updated: 10th January 2008

The purpose of this project is to generically add a set of glossary terms to a web page. This should be achieved in an accessible and unobtrusive manner. The technique presented utlises AJAX to enhance user experience but does not have sole reliance upon them to display content.

A real world example demonstrating the glossary method may be found on the Next Generation Learning website.

A working example set of tests

The first word for testing, pedagogy, is a valid term.

The next term is a valid term but incorrectly marked-up by missing the class name Metadata instances. A class="glossary" is needed to fire the Ajax component but note that it still works by loading the glossary page then jumping to the term anchor.

The next example is a correctly coded non-existent term laoreet. Which should return a "term not found" message.

This test case is incorrectly coded (no class stated) and references a non-existent term Maecenas. It goes to the top of the full glossary page.

The next test demonstrates a poorly defined glossary term. It contains inapropriate block level elements, a ul list. beguile. Note in IE the HTML & AJAX version fails, but works in Firefox / Opera / Safari. In the PHP & AJAX version the term is parsed before delivery. Block elements are replaced with spans and thereby works as expected.

Finally further valid terms to prove multiple instances work: thesauri and taxonomies and just one more "lastChild" test vocabularies

How it works

Glossary links are distinguished by adding the class name class="glossary". Upon page load, JavaScript looks through each link ascertaining which are glossary links and attaches an alternate onclick action.

Without Javascript present the link simply goes to an anchor on a page displaying all the glossary terms.

With Javascript it fetches just the relevant term and places it in a pop-up box. Clicking the link a second time, or the pop-up, or another link removes the pop-up.

In the HTML the pop-up appears directly after the link thereby making it immediately available to screen readers et al.

On slow connections the pop-ups content is preceded by a "Please wait..." notification.

The link may be activated by keyboard and or mouse independently.

In the (X)HTML

Glossary definition coding

A glossary term must take the form:

<dt id="glossary_term">Glossary term</dt>
<dd>term definition one</dd>

A single term may have multiple definitions associated.

Each term is held in a separate html file eg glossary_term.html. This is to make data requests faster but it also simplifies the server-side inclusion into a database or CMS as required.

Note both the id and filename are the glossary term in lowercase with spaces replaced by underscores.

The HTML&AJAX version fails in IE if the term contains block level elements. While the PHP&AJAX version parses the definition replacing each with a span to prevent the issue.

Glossary link coding

A glossary link must take the form:

    <a class="glossary" href="glossary.php#glossary_term">Glossary term</a>

The JavaScript functions

onload handling

function addLoadEvent(func){
// author: Simon Willisons -
  if(!document.getElementById || !document.getElementsByTagName)return
  var oldonload=window.onload
  if(typeof window.onload!='function')window.onload=func
  else window.onload=function(){oldonload();func()}

Standard multiple onload function handler which allows multiple functions to be started upon fully loading the web page. Further information is available from the author Simon Willisons.

Check id exists, and replace content functions

function idExists(id){
  return (document.getElementById(id) ? true : false)

function replaceContent(id,content){
  if (idExists(id))
    document.getElementById(id).innerHTML = content

The CSS switching / checking / replacing function

function jsCSS(action,obj,class1,class2){
// author: Christian Heilmann -
  switch (action){
    case 'swap':
        obj.className=!jsCSS('check',obj,class1)?obj.className.replace(class2,class1): obj.className.replace(class1,class2)
    case 'add':
        if(!jsCSS('check',obj,class1)){obj.className+=obj.className?' '+class1:class1}
    case 'remove':
        var rep=obj.className.match(' '+class1)?' '+class1:class1
    case 'check':
        return new RegExp('\\b'+class1+'\\b').test(obj.className)
  return false

Further details from the author Christian Heilmann.

XHTTPRequest function

// XMLHttpRequest methods:
// author Jim Ley -
var xmlhttp=false
/*@cc_on @*/
/*@if (@_jscript_version>=5)
    try{xmlhttp=new ActiveXObject("Msxml2.XMLHTTP")}
      try{xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")}
  @end @*/
if (!xmlhttp && typeof XMLHttpRequest!='undefined'){
  try{xmlhttp=new XMLHttpRequest()}
if (!xmlhttp && window.createRequest){

Further details fropm the author Jim Ley.

Insert a HTML object after the current object

function insertAfter(targetElement, newElement){
  var parent = targetElement.parentNode
  if (parent.lastChild == targetElement)
    parent.insertBefore(newElement, targetElement.nextSibling)

Find the screen position of current HTML object

function findPos(obj){
// author: Peter-Paul Koch -
  var curleft=0, curtop=0
  if (obj.offsetParent){
    curleft = obj.offsetLeft
    curtop = obj.offsetTop
    while(obj = obj.offsetParent){
      curleft += obj.offsetLeft
      curtop += obj.offsetTop
  } }
  return [curleft, curtop]

Further details fropm the author Peter-Paul Koch.

Browser specific placement adjustments

function browserOffset(){
  var agt = navigator.userAgent.toLowerCase()
  if (agt.indexOf("msie") != -1) return 26
  if (agt.indexOf("firefox") != -1) return 26
  return 0

This function merely adjusts the display position slightly for certain browsers.

The main routine

function getAjaxObject(obj){
  // please wait notice
  obj.nextSibling.appendChild(document.createTextNode(" Please wait..."))

  // get & set x y position = "term"
  document.getElementById('term') = findPos(obj)[1] + browserOffset() + 'px'
  document.getElementById('term').style.left = findPos(obj)[0] + 'px'

  // this version calls glossary.php?term=glossary_term
  // parses and cleans the glossary term html via php
  var contentFile = obj.href.replace(/#/g, "?term=")
  if (xmlhttp){"GET", contentFile, true)
    xmlhttp.onreadystatechange = function(){
      if (xmlhttp.readyState == 4)
        replaceContent('term', xmlhttp.responseText)
  // this version fetches terms/glossary_term.html directly
  var contentFile = obj.href.replace(/glossary.php#/g, "terms/") + '.html'
  if (xmlhttp){"GET", contentFile, true)
    xmlhttp.onreadystatechange = function(){
      if (xmlhttp.readyState == 4)
        replaceContent('term', xmlhttp.responseText.replace(/<dt /, '<span class="dt" ').replace(/<\/dt>/, '</span>').replace(/<dd/g, '<span class="dd"').replace(/<\/dd>/g, '</span>'))
        // note: IE will not insert block code into an inline element
  return false

Reset the glossary link titles

function resetTitles(){
  for (var i = 0; i < glossaryLinks.length; i++)
    glossaryLinks[i].title = openText

Close all open glossary terms

function closeGlossaries(){
  for (var i = 0; i < glossaryLinks.length; i++){
    glossaryLinks[i].nextSibling.innerHTML = ""
    glossaryLinks[i] = ""
} }

Achieved by removing content from all spans and by removing the id name.

A glossary link is clicked

function clickedTerm(obj){
  // If the term is closed it will have an "open me" title
  if (obj.title == openText){
    obj.title = closeText
  obj.onclick = function(){
    return false
  obj.nextSibling.onclick = function(){
    return false
  return false

Initialise the glossary

function setupGlossary(){
  // Get a list of all links in the content div
  var links = document.getElementById('content').getElementsByTagName('a')
  // work through each link
  for(var i = 0; i < links.length; i++){
    // if link's of glossary class
    if (jsCSS('check', links[i], 'glossary')){
      // add link to global array of glossay links
      glossaryLinks[glossaryLinks.length] = links[i]
      // insert a span (for the term) directly after the link
      insertAfter(links[i], document.createElement('span'))
      // change the links title attribute
      links[i].title = openText
      // add an onclick behaviour
      links[i].onclick = function(){
        return false
} } } }

Initialise global variables

var waitImg = document.createElement('img')
waitImg.width = "32"
waitImg.height = "32"
waitImg.src = "please_wait.gif"
waitImg.alt = ""
var glossaryLinks = new Array()
var openText = "Expand glossary term"
var closeText = "Contract glossary term"

Initialise on page load


CSS styling

In page glossary links

/* css for glossary link */
a:visited.glossary {
  border-bottom:1px dotted #090;
a:hover.glossary {color:#0f0}

The glossary definition block

/* css for glossary term */
#term {
  padding:0.25em 0.5em;
  background:url(close.gif) #fafffa no-repeat top right;
  border:1px solid green;
#term span {
  margin:0.5em 0 0.5em 1em;
#term span.dt {

CSS for the back-up glossary page

/* css for glossary page */
dl {margin:0}
dt {
  margin:0.5em 0 0.25em 0
dd {margin:0.25em 0 0.25em 1em}

The glossary of terms page

Ajax version first

On calling the page a php script runs and checks the url for $_GET data. If found it the page outputs the AJAX response of a single definition as held in the terms directory.

Otherwise the page outputs all glossary definitions and focuses on any URL passed anchor.



  if ($_GET['term']){
    if (file_exists(DATA_PATH)){
      $folder = opendir(DATA_PATH);
      $count = 0;
      $contents = '<dt id="' . $_GET['term'] . '">' . $_GET['term'] . '</dt><dd>Term not found in glossary</dd>';
      while($file = readdir($folder)){
        if ($file == $_GET['term'].'.html'){
          $filename = DATA_PATH.'/'.$file;
          $fd = fopen ($filename,'r') or die('Cannot open file '.$filename);
          $contents = fread ($fd,filesize($filename));
          fclose ($fd);

Parse definition file to remove block level objects

The glossary_term.html file may contain html elements other than the expected <dt> and <dd>s. It is therefore parsed to replace any block level components with inline <span> elements. Besides being best practice it prevents IE throwing a wobbler when using innerHTML to insert illegal block elements inside an inline element.

// five conversions. Note each glossary term should only contain one dt and one or more dd's
// 1. convert expected open dt and dd's to span with class
      $contents = str_replace("<dt", ' <span class="dt"', $contents);
      $contents = str_replace("<dd", ' <span class="dd"', $contents);

// 3. convert open headings
      $headingElements = array("<h1", "<h3", "<h4", "<h4", "<h5", "<h6");
      $contents = str_replace($headingElements, '<span style="font-weight:bold"', $contents);

// 4. convert other open block elements
      $openBlockElements = array("<div", "<ul", "<ol", "<dl", "<li", "<p", "<blockquote", "<fieldset");
      $contents = str_replace($openBlockElements, "<span", $contents);

// 5. convert close block elements
      $closeBlockElements = array("</div", "</ul", "</ol", "</dl", "</dt", "</dd", "</li", "</p", "</h1", "</h3", "</h4", "</h4", "</h5", "</h6", "</blockquote", "</fieldset");
      $contents = str_replace($closeBlockElements, "</span", $contents);

      echo $contents;

Followed by the normal HTML page version which is served if the page is called without a term=glossary_term attached to the url.

Read the "terms" directory and include all definitions

The content area has a simple routine which reads the filenames of all files in the DATAPATH (terms) directory. The data from each file is included into a definition list and output.

    if (file_exists(DATA_PATH)){
        $folder = opendir(DATA_PATH);
        $count = 0;
        while($file = readdir($folder)){
          if ($file[0] != "." && $file[0] != ".."){
            include(DATA_PATH . '/' . $file);

Note in this simple version no sorting has been applied.

Downloadable files

Sorry, it appears I've accidentally deleted the download zip file associated with this project. I'll recreate them when time allows.

All I have available is the annotated JavaScript file.