Clean Up Your Page, Removing Bad Behaviour

Cliché, cliché, cliché… What old analogy can I use to sum up this little tutorial? I think I’ve decided on one.
Would you wear walking, speak T-shirts or skull around all day? I think not. Actually, I will make a fairly confident statement and say that your eyebrows made a slight movement towards the ceiling while reading those questions, or at the very least you attempted to put on a pair of walking or got up and tried to skull around.
As cryptic as the analogy is, it makes perfect sense for this tutorial. You do not want to mix behaviour with your presentation. You want to wear something designed for the task like a T-Shirt. You do not want your behaviour jumping in amongst your structure either; we do not have a pirouette bone in our body because this behaviour is not part of our structure. Our structure, presentation and behaviour all work together but are separate parts of us as a whole.
I know, I know… I have not told you what it is this tutorial is about yet and judging by your general restlessness and tooth grinding, you are dying to find out. Well, I have always been terrible at keeping secrets so I will spill the beans.
Separating Behaviour (JavaScript) From Structure and Presentation
As frustrating as it can sometimes be to code your websites to comply with some of the recommendations and specifications set down by the World Wide Web Consortium (W3), these standards do have your best interests at heart. Sure, it may take a little longer to learn the correct way and in some cases it may take a little longer to write the mark-up but their recommendations really do make things easier, not only for you, but also for your users.
If you are like me and learned HTML before anything else in the WWW, you would probably have spent a large amount of time learning to use the archaic HTML presentational elements to create your pages. Something as simple as a colourful paragraph would require ludicrous overuse of <font> tags and color="" attributes. HTML was suffering badly, my website source-code was a sickening tag soup and the humble HTML, a procedural, structural, mark-up language was being asked to do much more than it was designed to do.</font>
My battle – and the long-suffering HTML’s battle-- with these elements ended when I discovering the heroic Cascading Style Sheet. What was so special about CSS that it was able to make my life as a web designer that much easier? It took the presentation of web documents and separated it from the structure. No longer was it necessary to fiddle about inside ridiculous numbers of presentational tags to get the look and feel required by my creative mind. I was able to shift the presentation to a document and format designed exactly for the cause and in doing so I was able to relieve the burden placed on the structure of my pages.
It did not take long for the benefits of CSS to become clear -- portable, abstracted presentation allowing for site-wide layout modifications by changing a single file.
It was not perfect though; there are three aspects to a website -- structure, presentation and behaviour. CSS had taken the presentation out of the equation but my HTML was left littered with behavioural modifiers. My growing skills with Dynamic HTML gave me motivation to enhance my pages but the gap left by presentational code I had moved to CSS was quickly filling with behavioural JavaScript code. This last bit of separation anxiety was extinguished when I became aware of yet another W3 recommendation, The Document Object Model, giving me a way to isolate behaviour leaving HTML to do what it is best at, structuring content.
The Document Object Model
So what is the Document Object Model (DOM) and what tricks does it bring to the party that earns it such following of eager young lasses web designers?
The DOM is an Application Programming Interface designed to access and manipulate objects contained within documents of a hierarchal nature... What is that you say? You just want to mess about with a web page not start flipping around in hierarchy nonsense?
Well that is the thing. The DOM gives client side developers a way to reach any element in a (x)HTML page from an abstracted layer outside of the structure. In the same way as CSS removed website presentation from the HTML structure, the DOM removes website behaviour from the structure. From a specialised document, you can control the behaviour of a webpage and finally leave HTML to control the structure – the job HTML was created to perform.
Strictly coded HTML and xHTML implements a logical structure and has a hierarchy whereby every element has a parent element all the way up to the root element – the <html> element is the root of every HTML page. So using this HTML family tree, you can traverse your way to any element simply by looking down through the generations of children. The first <div> element of your web page would be the great-grandchild of the <html> element.
<html> --> <body> --> <div>
Okay, so you can see now that it is possible to drill your way down to any element you desire, let’s start having a look at what problems the DOM solves and some of the methods available for us to achieve them.
JavaScript, unbelievably, is a very powerful Object Oriented language that allows many of the OOP philosophies to be brought across to client side coding. The major philosophy here is abstraction – moving the behavioural logic into a layer above the structure and presentation.
The examples below will all be manipulating one simple HTML document from an external .js file. The HTML document follows – you may use any HTML page for these examples as the code can simply be attached to another page, nothing is hard-coded into the HTML structure.
Please take the time to read the points described in the HTML file to understand the benefits of coding with the DOM.
As discussed above, the root or top-level parent of a HTML document is the <html> tag. The JavaScript keyword document can be considered "The <html>" tag for purposes of understanding the correct hierarchy. Just about all of the DOM traversing methods available for document can be used on more specific child elements as well.
www.lukedingle.com/scripts/dom.php is where you can see and download the HTML. Note that this page is just a normal functioning HTML page. There will be no changes made to the HTML at all.
Traversing the DOM
Accessing a Single Element
Something that you will want to do again and in your JavaScript’s is isolate a single element on your page and perform some sort of action with it. It could be any element, and you can perform any JavaScript actions on it. The easiest and most direct method to do this is with getElementById.
The getElementById method takes a single string as its argument. This string is the id="" attribute of a HTML element. If the given string matches an id attribute within the document, the function will return a reference to that element. If it does not match, this function will return null.
Let’s have a look at an example usage. The following code will get a reference to the #header div in our document. It will then change the background colour when the user moves their mouse over the div.
// This function will be added to throughout the tutorial
// This function will be run when the a page loads and will set up
// The various things using the DOM
var backgroundColour = '#EEEEEE'; // This is the colour the background will turn on mouseover
var date = new Date(); // a date object to use later
var infoDiv = 'border'; // This is the div we will be adding text to later
var externalFlag = 'external'; // The class name given to links to external sites
var message = 'Oh! Please don\'t leave so soon! By clicking okay, you will go to another site. Are you sure you want to?'; // Message on leaving
function initiatePage ()
{
// Let's get a reference to the #header div in our document.
var header = document.getElementById('header');
// Now, we'll attach the background colour changing function
// to the header div's onmouseover and onmouseout event handlers
header.onmouseover = changeBackground;
header.onmouseout = changeBackground;
}
function changeBackground()
{
if (this.style.backgroundColor == '')
{
this.style.backgroundColor = backgroundColour;
} else {
this.style.backgroundColor = '';
}
}
// Attach the initiatePage function to the onload event handler of the window object
window.onload = initiatePage;
See results at : www.lukedingle.com/scripts/dom.php?getCode
Pretty simple hey? Without a single line of code in the HTML page we are able to eliminate an onmouseover="" onmouseout="" and perhaps even a <script></script> tag to declare the function.
A few things to note from the preceding code that may seem a little different to what you’re used to.
- Once you have a reference to a HTML node stored in a variable, you can treat it like any other JavaScript object. This includes adding custom properties and methods. You just get the benefit of having all the default properties and methods for the type of element.
- You can assign a function to a method in two ways. You can define a function and add it using the function name (note that you don’t use parentheses as this will execute the function) or you can add it as an inline function declaration.
var.method = function () { /* function code here */}; - When you are creating functions to be assigned to objects, you can use the this special variable. The this variable can basically be replaced with "The object that this function is attached to". It gives you a way of attaching the same function to as many objects as you desire without having to worry about variable names.
Accessing Multiple Elements
So we’ve seen how easy it is to hunt down a single element and apply some code to it but of course we don’t want to have to give every element a unique id and code them all by hand. We want to be able to 'batch process' a whole lot of elements.
Perhaps we want to access all of the images in the document and add a function that alerts the alt="" text each time a user clicks the image (not a very useful real life example).
To access either all elements or all elements of a single type, we can use getElementsByTagName. This function takes a string as an argument and with the string looks for all of the HTML tags that match. The function will return a list of all matching nodes within the document. If you wanted to access all of the nodes in the document, regardless of the tag type you can send the string '*'. getElementsByTagName will return an array of everything.
The below will tackle the example above and add an alert to all of the images along with a copyright notice. Add the following code to the initiatePage function you set up before – add just below the } closing brace.
// Get references to all of the images in the document using getElementsByTagName
// The results are returned in an array
var imgs = document.getElementsByTagName('img');
// count how many images there are and loop through adding a simple onclick function
// to alert the alt text to the user...
nm = imgs.length;
for (i=0; i<nm; i++)
{
// date.getFullYear() is using the date object declared earlier
imgs[i].onclick = function () { alert(this.getAttribute('alt') + '\n\n \t\t(c) ' + date.getFullYear());};
}
See results at : www.lukedingle.com/scripts/dom.php?getCode
Again, this was a simple thing to perform. The last code used the inline function declaration explained earlier and depending on how many images this was to be applied to, potentially saved 10’s of onclick="" inline event handlers from clogging up the page.
So the above example will track down every single image on the page but what if you wanted to focus on a specific group of elements in the document rather than all of them?
There are a few ways to go about this:
The first way is to use the getElementsByTagName on a node that you have stored in a variable. This will return all of that type of element that are child elements of the node stored in the variable. Confused? Never mind, I’ll explain this further.
In our working HTML document, I want to access all of the links that are in the #header of the document. I don’t want to change any of the other links, just those. The following code – which will be added to the initiatePage function will use the header variable we stored earlier when adding the background effects and retrieve all of the links inside of it. To these links, we will add a function that takes the title text of the link and displays it underneath the menu when the user holds their mouse over it.
Add the following code into the initiatePage function just after
header.onmouseover = changeBackground;
header.onmouseout = changeBackground;
//let's get all of the links found inside the header div
// we will add the showInfo function to the mouseover eventHandler
// to hide the details
var headerLinks = header.getElementsByTagName('a');
// count how many there are and add the function to them
nm = headerLinks.length
for (i=0; i<nm; i++)
{
headerLinks[i].onmouseover = showInfo;
headerLinks[i].onmouseout = hideInfo;
}
Okay, the above code is pretty simple again. What we are doing is getting an array of all the links inside the #header div. Then we loop through adding the two functions to onmouseover and onmouse out.
Now for the showInfo and hideInfo functions. Don’t get too frightened by as the details will be explained afterwards:
// Store a reference to the div that we will add the content to
// the id is given in the global variable at the top of this script
var workingDiv = document.getElementById(infoDiv);
// We want to clear all the content from the div before we add the new
// content...
var emptyDiv = workingDiv.cloneNode(false);
// This new empty div now goes before the old one
workingDiv.parentNode.insertBefore(emptyDiv, workingDiv);
// Now get rid of the old one all together
workingDiv.parentNode.removeChild(workingDiv);
// Insert the new text into the new div
emptyDiv.appendChild(document.createTextNode(this.getAttribute('title')));
}
function hideInfo ()
{
// Store a reference to the div that we will add the content to
// the id is given in the global variable at the top of this script
var workingDiv = document.getElementById(infoDiv);
// We want to clear all the content from the div before we add the new
// content...
var emptyDiv = workingDiv.cloneNode(false);
// This new empty div now goes before the old one
workingDiv.parentNode.insertBefore(emptyDiv, workingDiv);
// Now get rid of the old one all together
workingDiv.parentNode.removeChild(workingDiv);
}
See results at : www.lukedingle.com/scripts/dom.php?getCode
These two functions are very simple again but they are introducing a few new functions the DOM gives us to manipulate our documents.
Let’s work through the showInfo function.
- We store a reference to the div that will hold the info. This id is stored in a global variable infoDiv at the top of the script.
- workingDiv.innerHTML would probably be the way most would achieve the next part of the code but innerHTML is not part of the DOM specification. To emulate the innerHTML function we have to work a bit differently. Into a new variable, we clone workingDiv using cloneNode. The boolean false that is passed as an argument tells JavaScript that we want to clone the element only and not anything inside of it.
- Now that we have a new empty div, we insert this into the document and introduce a couple of new concepts. workingDiv.parentNode refers to the parent of workingDiv or the next level up in the document hierarchy if you wanted to you could use workingDiv.parentNode.parentNode.parentNode.parentNode to refer through all parents to the <html> element. workingDiv.parentNode.insertBefore(emptyDiv, workingDiv) is a function that will insert a the first argument node or element directly before the second node or element passed to the function. Now we have the same div as before (because we cloned it) inserted before the old one.
- Now that we have two nodes, we need to remove the old one from the document so again we refer to the parent node of workingDiv and run the removeChild function passing workingDiv as an argument. workingDiv no longer exists in the document and we are left with just the emptyDiv that was inserted in step 3.
- Because the DOM works on document hierarchy, it’s not a simple matter of adding text inside emptyDiv we need to give emptyDiv a child. In this case we will create a text node using createTextNode out of the text in the title attribute. We then use appendChild to add the text node as a child of emptyDiv!
hideInfo is exactly the same as showInfo except for step 5 we clear the text out of the document onmouseout.☺
So you’re probably starting to see that there aren’t many limits on what can be done without a single line of code inside the HTML.
Let’s look at another method of isolating a group of elements.
I want users to know when they will be leaving my page. In order to flag this, I give them a confirm message whenever they click a link to an external page. (I wouldn’t do this on a real site unless you wanted to annoy your users ☺ ). This next method will find all the links on the page and add the confirm function to any link that has a class of "external".
Add the below code just above the last } in the initiatePage function.
// loop through and check if the class name contains externalFlag
// it will add a confirm message if it does...
nm = links.length;
for (i=0; i< nm; i++)
{
if (links[i].className.indexOf(externalFlag) != -1)
links[i].onclick = function () { return confirm(message);};
}
See results at : www.lukedingle.com/scripts/dom.php?getCode
This is as simple as it gets really. Get all the links, check their className for the external link flag and add a confirm message to it if it is found.
As you can see now, our page has a lot of JavaScript going on and we haven’t written a single line into the HTML page yet.
Hierarchy Acrobatics and Elements on the Fly
Now that you can access any element you want inside of a document, let’s have a look at some more DOM properties and methods you can use to swing your way around the HTML family tree.
Some Additional Properties
- element.firstChild: This holds a reference to the first child of the element. If element is <html> it will return a reference to <head> as it is the first child of <html>. If the element has no first child, it holds the value null. You can use firstChild an arbitrary number of times all the way down to the wee wee grandchildren.
- element.childNodes: An array of all the child nodes inside an element. If element was <html>, this would hold all the elements in the document.
- element.parentNode: We had a quick look at this earlier in the examples, this holds a reference to the parent node of the element. If element was <head> then parentNode would hold a reference to <html> as it is the next level up in the hierarchy. As with firstChild, this can be used an arbitrary amount of times up to the top level element <html>. If there is no parent, this will be null.
- element.previousSibling/element.nextSibling: This holds a reference to the element that is previous/next within the document but has the same direct parent. If element was <body> then previousSibling be a reference to <head> as they are both direct descendants of <html>.
Some Additional Methods
- document.createElement(tagName): This will create an element of type tagName. If you wanted to create a div you would use
var newDiv = document.createElement("div");
You can then set some attributes of the new div
newDiv.setAttribute("id", "newDiv");
Then you can add it to the element of your choice – in this case the document.
document.appendChild(newDiv); - element.getAttribute(attName)/element.setAttribute(attName, attVal): This will get and set attributes of an element. Although it is possible to access a lot of attributes directly, the DOM requests that you use get and set attributes in favour of the older methods.
- element.removeChild(element): Pass a reference to an element to this method to remove it from the document.
Summing Up
I certainly hope that I have conveyed how easy the DOM can make your client-side scripting life. In the example code we have gone through, there are many elements with JavaScript applied to them using events but even without JavaScript the page is completely functional. There isn’t a single line of code amongst the HTML and the JavaScript can be re-used with little or no change in any HTML file you want.
Thanks for reading and I hope to answer your DOM questions on the Web Squeeze soon!
Here is all the JavaScript code:
Or you can download from http://www.lukedingle.com/scripts/javascript.js
// This function will be added to throughout the tutorial
// This function will be run when the a page loads and will set up
// The various things using the DOM
var backgroundColour = '#EEEEEE'; // This is the colour the background will turn on mouseover
var date = new Date(); // a nice date object to work with
var infoDiv = 'border'; // This is the div we will be adding text to later
var externalFlag = 'external'; // The class name given to links to external sites
var message = 'Oh! Please don\'t leave so soon! By clicking okay, you will go to another site. Are you sure you want to?'; // Message on leaving
function initiatePage ()
{
// Let's get a reference to the #header div in our document.
var header = document.getElementById('header');
// Now, we'll attach the background colour changing function
// to the header div's onmouseover and onmouseout event handlers
if (header != null)
{
header.onmouseover = changeBackground;
header.onmouseout = changeBackground;
//let's get all of the links found inside the header div
// we will add the showInfo function to the mouseover eventHandler
// to hide the details
var headerLinks = header.getElementsByTagName('a');
// count how many there are and add the function to them
nm = headerLinks.length
for (i=0; i<nm; i++)
{
headerLinks[i].onmouseover = showInfo;
headerLinks[i].onmouseout = hideInfo;
}
}
// Get references to all of the images in the document using getElementsByTagName
// The results are returned in an array
var imgs = document.getElementsByTagName('img');
// count how many images there are and loop through adding a simple onclick function
// to alert the alt text to the user...
nm = imgs.length;
for (i=0; i<nm; i++)
{
// date.getFullYear() is using the date object declared earlier
imgs[i].onclick = function () { alert(this.getAttribute('alt') + '\n\n \t\t(c) ' + date.getFullYear());};
}
// Let's get all the links and add a confirm message if it leads to an external site
var links = document.getElementsByTagName('a');
// loop through and check if the class name contains externalFlag
// it will add a confirm message if it does...
nm = links.length;
for (i=0; i <nm; i++)
{
if (links[i].className.indexOf(externalFlag) != -1)
links[i].onclick = function () { return confirm(message);};
}
}
function changeBackground()
{
if (this.style.backgroundColor == '')
{
this.style.backgroundColor = backgroundColour;
} else {
this.style.backgroundColor = '';
}
}
function showInfo ()
{
// Store a reference to the div that we will add the content to
// the id is given in the global variable at the top of this script
var workingDiv = document.getElementById(infoDiv);
// We want to clear all the content from the div before we add the new
// content...
var emptyDiv = workingDiv.cloneNode(false);
// This new empty div now goes before the old one
workingDiv.parentNode.insertBefore(emptyDiv, workingDiv);
// Now get rid of the old one all together
workingDiv.parentNode.removeChild(workingDiv);
// Insert the new text into the new div
emptyDiv.appendChild(document.createTextNode(this.getAttribute('title')));
}
function hideInfo ()
{
// Store a reference to the div that we will add the content to
// the id is given in the global variable at the top of this script
var workingDiv = document.getElementById(infoDiv);
// We want to clear all the content from the div before we add the new
// content...
var emptyDiv = workingDiv.cloneNode(false);
// This new empty div now goes before the old one
workingDiv.parentNode.insertBefore(emptyDiv, workingDiv);
// Now get rid of the old one all together
workingDiv.parentNode.removeChild(workingDiv);
}
// Attach the initiatePage function to the onload event handler of the window object
window.onload = initiatePage;




Comments on this article
Have something to say about the article? Need more help? Have your say in the Squeeze Forums.