Definitions and Definition Lists Revisited

Feel free to leave comments here.

Ever since playing with the definition list tags (dl, dt, dd) some years ago, I've been looking for good uses. I often write documents (design docs, API docs, etc) in HTML and usually add a glossary section at the end of the document. The definition lists have been used there but, with no styling or presentational flair.

I saw this the other day and thought it was pretty amazing in its simplicity. I've also liked the sites where certain terms show a definition on mouseover but, I've disliked the abuse of using the title attribute to produce tooltips. I also don't like the duplication that can occur when a term appears multiple times in a page.

So, I thought, why not combine the real contextual meaning of some tags with a little javascript (JScript, ECMA Script, whatever). So, words that should get definitions can be wrapped in definition (dfn) tags, and with some event binding magic, the corresponding definition, dl, can pop-up near the word providing a contextual definition. Page readers will still find the glossary section, so section 508 accessibility should be covered as well.

In order to keep the markup simple there should be no special attribute setting to make this work. I'll write some code that will traverse the DOM finding cases where the text of the dfn tag matches the text of a dt tag. It will then bind an event to the dfn to catch mouseovers and position and show the dl parent of the corresponding dt. Anyway, that's the theory. Another way may be to just find all of the words in the pages that have definitions but, that could end up binding the wrong words in cases where there are multiple uses. Now if I were really smart, I would make this show language specific definitions. Making it keyboard accessible would be pretty sweet too. Maybe I'll write a part 2 to do these.

To reduce bandwidth, the dl sections could be generated using some AJAX pixie dust, either on demand or in the background. Good use of header settings could exploit browser caching as well. Of course, building the definition lists on the client would probable violate the semantics of the definitions.

Enough talk, lets get started.

I'm going to start by creation some definitions for some words in this page, I've already marked them with dfn tags.


<dl>
    <dt lang="en">definition</dt>
    <dd lang="en">A statement of the meaning of a word,
        phrase, or term, as in a dictionary entry.</dd>
    <dd lang="en">The act or process of stating a precise
        meaning or significance; formulation of a meaning.</dd>
	
    <dt lang="es">definici&#x00F3;n</dt>
    <dd lang="es">Acci&#x00F3;n de definir una palabra o un concepto:
        hoy nos detendremos en la definici&#x00F3;n del concepto de
        relatividad.</dd>
    <dd lang="es">Enunciado con que se define una palabra
        o un concepto: una palabra con tres definiciones.</dd>
</dl> 

(Note the entity values for non 7-bit characters. This helps to ensure the character renders correctly when the user is over-riding the page character set)

CSS

<style type="text/css" media="all" title="default">
h1 {
 font-size: 1.1em;
 font-weight: bold;
}
dfn {
 background-color: #eee;
 cursor: default;
}
div.terms dl{
 display:none;
 position: absolute;
 background-color: #edf;
 border: solid #00f 1px;
 width: 20em;
 font-size: 0.7em;
 margin: 0;
 padding: 0;
}

div.terms dl dt{
 display: block;
 float: left;
 width: 100%;
 margin-bottom: -1px;
 background-color: #eee;
 border-bottom: solid #00f 1px;
 border-top: solid #00f 1px;
 overflow: hidden;
}
div.terms dl dd{
 float: left;
 display: block;
 border-top: solid #00f 1px;
 margin: 0;
 padding: 0;
}
</style>

The ECMAScript (JavaScript) to drive it

String.prototype.trim = function() {

   return this.replace(/^\s+/,'').replace(/\s+$/,'');
};

var ELEMENT_NODE                = 1;
var ATTRIBUTE_NODE              = 2;
var TEXT_NODE                   = 3;

var CDATA_SECTION_NODE          = 4;
var ENTITY_REFERENCE_NODE       = 5;
var ENTITY_NODE                 = 6;

var PROCESSING_INSTRUCTION_NODE = 7;
var COMMENT_NODE                = 8;
var DOCUMENT_NODE               = 9;

var DOCUMENT_TYPE_NODE          = 10;
var DOCUMENT_FRAGMENT_NODE      = 11;
var NOTATION_NODE               = 12;

function getNodeTextTrimed(node){
 var text = "";
 var children = node.childNodes;

 for(var j=0; j<children.length;j++){
  var child = children[j];

  if(child.nodeType==TEXT_NODE){
   text += child.nodeValue;
  }
 }

 return text.trim();
}
function getTagsByName(tagName){
 var body = document.body;

 /* lowercase for xhtml proper */
 var elems = body.getElementsByTagName(tagName.toLowerCase());

 if(elems.length==0){
  /* Uppercase when html4 */
  elems = body.getElementsByTagName(tagName.toUpperCase());
 }

 return elems;
}
/**
 * Find all of the dfn tags.
 * returns a map using the text value as a key and an array of
 * elements for the value.
 */
function findDefs(){
 var dfns = getTagsByName("dfn");

 var DFNMAP = new Array();
 for(var i=0;i<dfns.length;i++){

  var curDfn = dfns[i];
  var text = getNodeTextTrimed(curDfn);

  if(!DFNMAP[text]){
   DFNMAP[text] = new Array();
  }

  DFNMAP[text][DFNMAP[text].length] = curDfn;
 }
 return DFNMAP;
}

/**
 * Find all of the dt tags.
 * returns a map using the text value for the key, and the parent dl
 * element as the value.
 */
function findDefinitions(){
 /* lowercase for xhtml proper */
 var terms = getTagsByName("dt");

 var dlmap = new Array();
 for(var i=0;i<terms.length;i++){

  var cTerm = terms[i];
  var text = getNodeTextTrimed(cTerm);

  dlmap[text] = cTerm.parentNode;
 }
 return dlmap;
}

function showDef(){
 var left = this.offsetLeft;
 var top = this.offsetTop;

 var height = this.offsetHeight;
 var width = this.offsetWidth;

 var defElem = this.def;
 defElem.style.display = "block";

 defElem.style.top = (top + height+10) +"px";

 defElem.style.left = (left+width) + "px";
}
function hideDef(){

 var defElem = this.def;
 defElem.style.display = "none";
}

/**
 * Tie the dfn tags and the dl tags together
 */
function bindDefs(){
 var dfns = findDefs();
 var dls = findDefinitions();

 for( i in dfns ){
  if(dfns[i]["length"] && dls[i]){

   var elems = dfns[i];
   for(var j=0; j<elems.length;j++){

    elems[j].def = dls[i];
    elems[j].onmouseover = showDef;

    elems[j].onmouseout = hideDef;
   }
  }
 }
}

Now, to make all of this work, we just need to call the bindDefs() function after the page loads. A body.onload=bindDefs; should do it. You can also use the trusty setTimeout technique for error isolation.

definition
A statement of the meaning of a word, phrase, or term, as in a dictionary entry.
The act or process of stating a precise meaning or significance; formulation of a meaning.
definición
Acción de definir una palabra o un concepto: hoy nos detendremos en la definición del concepto de relatividad.
Enunciado con que se define una palabra o un concepto: una palabra con tres definiciones.
HTML
Hyper Text Markup Language
HTML
Hyper Text Markup Language (lenguaje de etiquetado de documentos hipertextual)
ECMA Script
A scripting language standardized by the European Computer Manufacturers Association in ECMA-262. see Wikipedia for more information.
ECMA Script
Una especificación de lenguaje de programación publicada por El Asociación Europea de los Fabricantes de Computadoras (Ordenadoras) International. Puedes ver Wikipedia por mas informacion.
Note: The some of the definitions here were blatantly ripped off from dictionary.com and from diccionarios.com.

Color coding generated using cpp2html by Jasper Bedaux.



Sponsors:

About willCode4Beer