Modelling the Document Objects

Release: 2010-04-03
Jump to Web Standards Articles TOC

Page 1,

DOM

 In order to access parts of a webpage in a scripting or programming language to be able to manipulate the structure, the scripting or programming language has a model of objects. These objects represent the elements, their attributes, the text values for attribute values and element content. Plus properties for the information of the relationship of the element within the document such as its parent element or next sibling. This model is the Document Object Model or DOM for short.

 There are a few main levels of the DOM, each are made up of modules. We are going to look at the basics of DOM 1 Core and HTML, DOM 2 Core, Events and HTML, DOM 3 Core and DOM 5 HTML.

 As the most typical scripting environment that uses the DOM is ECMAScript, the open standard that most web browsers use and is the base of Netscape/Mozilla's JavaScript and Microsoft's JScript (used in Internet Explorer); then this is the perspective I will have when providing examples. This article is not exhaustive.

 DOM is also used within many programming languages such as C++, Java and Perl.

 The core of the model is the node object which provides core properties like nodeName, nodeValue, parentNode, namespaceURI, attributes which contains an array of attribute objects, etc. Also includes these methods hasChildNodes(), appendChild(), removeChild(), hasAttributes(). This object's properties and methods are inherited by most other objects in the model. The starting point of the webpage's model is the document object. From here you can create elements (createElement() and createElementNS()), text nodes (createTextNdode()), set attributes (setAttribute() and setAttributeNS()) and append the text nodes to the element and the element to other elements (appendChild()) to build up new structure. You can identify certain element objects by using the getElementById(), getElementsByTagName() and getElementsByTagNameNS() methods.

 For instance we have a <div id="me"> </div> element directly within the body element and we want to dynamically add a <p class="highBlue" title="DOM Level 1">Modelling the Document Objects</p> element within it:

// Create the paragraph element
pEL = document.createElement("p");

// Create the text node
pText = document.createTextNode("Modelling the Document Objects");

// Appending the text node to the paragraph object
pEL.appendChild(pText);

// Set a couple of attributes
pEL.setAttribute("class", "highBlue");
pEL.setAttribute("title", "DOM Level 1");

// Append the paragraph object to the div object with id 'me'
mediv = document.getElementById("me");
mediv.appendChild(pEL);
Figure 1: Basic DOM 1 example

But you can't guarantee that an element will always have an id attribute. So you can walk the document structure starting from the body element and obtain an array (or nodeList) of elements with that name in the current level of child elements with the getElementsByTagName() method on the element object. Or you can use the getElementsByTagName() method directly on the document object which gets a nodeList of elements with that name from the entire document.

An item() method allows you to pick a single element from that nodeList with a number as the parameter. To reference the first element you use the number 0 (zero), second element would be the number 1, etc. To get the text value of the element you need to get the text node by using the firstChild property and then get the text by using the nodeValue property.

A note about getting to the body element: the body object property is available on the document object in HTML in text/html and XHTML in application/xhtml+xml documents in web browsers. But usually is not in XHTML in text/xml and application/xml documents. So you may need to check if it exists, if not walk the structure from the top.

// Get the body of the document
if (document.body) {
  docbody = document.body;
} else {
  docbody = document.getElementsByTagName('body').item(0);
}

// Get the third division block (div element) directly within the body element
divEL = docbody.getElementsByTagName('div').item(2);

// Get the first paragraph from that div element
para = divEL.getElementsByTagName('p').item(0);

// Get the paragraph's text
paraText = para.firstChild.nodeValue;
Figure 2: Walking the document structure

Once you have the particular element object such as divEL you can get its name by using divEL.nodeName; and checking what type of node it is with divEL.nodeType;. Node types are numbers for instance 1 is an element, 2 is an attribute and 3 is a text node.

 DOM 2 adds support for XML Namespaces and so can create namespaced elements and set namespaced attributes (mostly for native XHTML and XML in general):

// Save the XHTML namespace for improved readability
xhtmlns = "http://www.w3.org/1999/xhtml";

// Create the XHTML paragraph element
pELNS = document.createElementNS(xhtmlns, "p");

// Create the text node
pText = document.createTextNode("Modelling the Document Objects");

// Appending the text node to the XHTML paragraph object
pELNS.appendChild(pText);

// Set a couple of XHTML attributes
pELNS.setAttributeNS(xhtmlns, "class", "highBlue");
pELNS.setAttributeNS(xhtmlns, "title", "DOM Level 2");

// Append the XHTML paragraph object to the XHTML div object with id 'me'
mediv = document.getElementById("me");
mediv.appendChild(pELNS);
Figure 3: Basic DOM 2 example

Or if prefixed with xhtml: then include the prefix when naming the element or attribute when creating. Such as .createElementNS(xhtmlns, "xhtml:p") and .setAttributeNS(xhtmlns, "xhtml:class", "highBlue"). You don't put the prefix in when using getElementsByTagNameNS() or getAttributeNS() even if they are prefixed. But usually just the elements would be prefixed unless the attributes are from a different XML language to the current element.

Also DOM 3 Core adds the textContent property to node objects so that they can retrieve text from and set text to elements.

// Get the body of the document
if (document.body) {
  docbody = document.body;
} else {
  docbody = document.getElementsByTagNameNS(xhtml, 'body').item(0);
}

// Get the third division block (div element) directly within the body element
divELNS = docbody.getElementsByTagNameNS(xhtml, 'div').item(2);

// Get the first paragraph's text from that div element
paraText = divELNS.getElementsByTagNameNS(xhtml, 'p').item(0).firstChild.nodeValue;
// or
paraText = divELNS.getElementsByTagNameNS(xhtml, 'p').item(0).textContent;
Figure 4: Walking the namespaced document structure

So far Mozilla based, WebKit based, Chromium based, Presto based and KHTML based web browsers such as Firefox, Safari, Chrome, Opera and Konqueror respectfully, all support DOM 3 Core's textContent property. Microsoft's Trident based like Internet Explorer does not.

 When using the DOM in ECMAScript / JavaScript it is best to test if the objects are supported as not all web browsers or other environments that use scripting may have DOM support:

if ((document.getElementById) && (document.createElementNS)) {
  // Do DOM 2 stuff
} else if ((document.getElementById) && (document.createElement)) {
  // Do DOM 1 stuff
} else {
  // Do non DOM stuff
} // end if DOM 2 stuff
Figure 5: Using the latest DOM code

Events

 Instead of attributes such as onclick, onload, onkeyup, an unobtrusive way to get elements to listen for events is by using the addEventListener() method from DOM 2 Events.

The first parameter is a string with the name of the event such as 'click', 'load', 'keyup'. Also the second parameter is either an anonymous function or the name of the separate function (no parenthesis or brackets) that will be run when that event is received. Referring to a separate function is better especially if more than one event will run the function.

Plus the third and final parameter is whether or not the function will be run when the event is received during the capture phase ( true ) or during the bubbling phase ( false ).

For instance when you activate a button or link, or an element within a button or link, the event (keydown, keypress, keyup or mousedown, mouseup, etc) travels from the top of the document, down the structure, to the target element that you activated and any of its descendant elements if any. This is the capture phase. Any ancestor elements listening with the third parameter as true will trigger their functions before reaching the target element.

Then the event bubbles back up the document structure until it hits the top. Any of the target element's descendant elements and ancestor elements that are listening for any of the appropriate events with the third parameter as false will run their functions

You can have an element listening for several events or even some same events but different functions by applying more than one addEventListener() method.

 To make the element stop listening for the event you use the removeEventListener() method with exactly the same parameter values as you did for adding it. This ensures you stop listening for the right event, during bubbling or capture phase and which function is associated.

divEL.addEventListener("keyup", doIt, false);
divEL.addEventListener("click", doIt, false);
Figure 6: Adding some events

 Microsoft's Trident 4 and under Web Platform (in Internet Explorer (IE) 8 and under) uses a Microsoft variation. You use attachEvent() and dettachEvent() methods to add and remove events. The first parameter is similar but with the 'on' prefix in the event name and the second parameter is the same as the main DOM. There is no third parameter as events are only handled within the bubbling phase.

if (divEL.addEventListener) {
  divEL.addEventListener("keyup", doIt, false);
  divEL.addEventListener("click", doIt, false);
} else if (divEL.attachEvent) {
  divEL.attachEvent("onkeyup", doIt);
  divEL.attachEvent("onclick", doIt);
}
Figure 7: Adding events for old IE and other browsers

 The function should have at least a parameter to receive the event. This parameter is usually called evt or e for simplicity but you can call it whatever fits the nature of the script or program.

Then you can access information about the event within the function. Such as the target element node that receives the event is obtained by the evt.target property ( or IE 8 and under's version is evt.srcElement ) and the type of event (click, keyup, etc) is obtained by the evt.type property (even in IE).

For getting which keyboard or keypad key was used, the evt.keyCode property provides the numeric representation of that key such as 9 is the tab key, 13 is the enter or return key and 32 is the spacebar key.

If the event was a pointer event such as a mouse event then using evt.clientX and evt.clientY will give you the x and y coordinates of the mouse click within the webpage. Also the evt.button property will provide the number corresponding to the mouse button used: 0 (zero) for the primary button (left in right-handed mode, right in left-handed mode), 1 is the middle or scroll button and 2 is the secondary button (right in right-handed mode, left in left-handed mode).

function eInfo (evt) {
  pReport = document.getElementsByTagName('p').item(4);
  if (evt.target) {
    evtEL = evt.target;
  } else if (evt.srcElement) {
    evtEL = evt.srcElement;
  }
  
  pReport.firstChild.nodeValue = 'The point of activation was at ' + evt.clientX + ' by ' + evt.clientY + '. Key pressed was ' + evt.keyCode + '. Event type was ' + evt.type + '. The target element was ' + evtEL + '.';
}
Figure 8: A function providing event information

Continue to Page 2

Page 1,

Copyright ©2005-2010 Legend Scrolls and Peter Davison. All rights reserved.