facebook google plus twitter
Webucator's Free JavaScript Tutorial

Lesson: The HTML Document Object Model

Welcome to our free JavaScript tutorial. This tutorial is based on Webucator's Introduction to JavaScript Training course.

The HTML Document Object Model (DOM) is a W3C standard that defines a set of HTML objects and their methods and properties. JavaScript can be used to create and destroy these objects, to invoke their methods, and to manipulate their properties. In HTML5, the DOM has been expanded and some already widely supported previously unofficial features have been included as part of the specification.

Lesson Goals

  • Learn to work with the HTML DOM.
  • Learn to access specific nodes.
  • Learn to access nodes by class name.
  • Learn to remove a node.
  • Learn to create a new node.
  • Learn to dynamically create an HTML page.
  • Learn to modify node styles.
  • Learn to modify node classes.
  • Learn to add events to nodes.

The innerHTML Property

Most HTML elements have an innerHTML property, which can be used to access and modify the HTML within that element. The innerHTML property wasn't included in any specification until HTML5. However, as it's extremely useful and well supported, we will use it widely throughout this course.

innerHTML Illustration

Given the code:

<p>I <strong>love</strong> Webucator.</p>

the innerHTML property of the <p> tag would be: "I <strong>love</strong> Webucator."

Tip: you can use the innerHTML property to either get the element's innerHTML value (as shown above) or you can use it to set the element's innerHTML value. More on this later in the lesson.

Accessing Element Nodes

JavaScript provides several different ways to access elements on the page. Let's have a look.

getElementById()

Chances are you have already seen the document.getElementById(id) method, which returns the first element with the given id (there shouldn't be more than one on the page!) or null if none is found. The following example illustrates how getElementById() works:

Code Sample:

HTMLDOM/Demos/getElementById.html
---- C O D E   O M I T T E D ----

function show() {
	var elem = document.getElementById("beatleList");
	alert(elem.innerHTML);
}
---- C O D E   O M I T T E D ----

<body onload="show();">
<h1>Rockbands</h1>
<h2>Beatles</h2>
<ol id="beatleList">
	<li>Paul</li>
	<li>John</li>
	<li>George</li>
	<li>Ringo</li>
</ol>
<h2>Rolling Stones</h2>
<ol id="stonesList">
	<li>Mick</li>
	<li>Keith</li>
	<li>Charlie</li>
	<li>Bill</li>
</ol>
</body>
</html>

Code Explanation

When this page loads, the following alert box will pop up:Alert Box

getElementsByTagName()

The getElementsByTagName() method of an element node retrieves all descendant (children, grandchildren, etc.) elements of the specified tag name and stores them in a NodeList, which is similar, but not exactly the same as an array of nodes. The following example illustrates how getElementsByTagName() works:

Code Sample:

HTMLDOM/Demos/getElementsByTagName.html
---- C O D E   O M I T T E D ----

function getElements()
{
	var elems = document.getElementsByTagName("li");
	var msg = "";
	for (var i=0; i < elems.length; i++)
	{
		msg += elems[i].innerHTML + "\n";
	}
	alert(msg);
}
</script>
<title>getElementsByTagName()</title>
</head>

<body onload="getElements();">
<h1>Rockbands</h1>
<h2>Beatles</h2>
<ol>
	<li>Paul</li>
	<li>John</li>
	<li>George</li>
	<li>Ringo</li>
</ol>
<h2>Rolling Stones</h2>
<ol>
	<li>Mick</li>
	<li>Keith</li>
	<li>Charlie</li>
	<li>Bill</li>
</ol>
</body>
</html>

Code Explanation

When this page loads, the following alert box will pop up:Alert Box

getElementsByClassName()

The getElementsByClassName() method is well supported by browsers and is officially part of HTML5. Applicable to all elements that can have descendant elements, getElementsByClassName() is used to retrieve all the descendant (children, grandchildren, etc.) elements of a specific class. For example, the following code would return a node list containing all elements of the "warning" class:

var warnings = document.getElementsByClassName("warning");

querySelectorAll()

The document.querySelectorAll() method gives you access to elements using the CSS selector syntax. For example, the following code would return a node list containing all list items that are direct children of ol tags:

var orderedListItems = document.querySelectorAll("ol>li");

querySelector()

The document.querySelector() method is the same as document.querySelectorAll() but rather than returning a node list, it returns only the first element found. The following two lines of code would both return the first list item found in an ol tag:

var firstOrderedListItem = document.querySelectorAll("ol>li")[0];
var firstOrderedListItem = document.querySelector("ol>li");

The this Object

The this keyword provides access to the current object. It references the object in which it appears.

The following example shows a page (HTMLDOM/Demos/Hierarchy.html) on which a mouse click on any element will display a message giving information about that element's position in the object hierarchy.

Open HTMLDOM/Demos/Hierarchy.html in your browser and play with it, but you don't need to explore the code. It's a complex program and we haven't covered everything in it yet.

Object Hierarchy

When you click on the middle list item, for example, you get the following message: Middle List Message

This shows that the list item's parent is an unordered list (UL), its first child is a text node ("List Item 2"), its last child is the same as it only has one child, its next and previous siblings are both text nodes, and it has only one child node (the text node). Again, this message was generated in Firefox, but would be the same in other modern browsers.

Accessing Elements

Duration: 25 to 40 minutes.

In this exercise, you will practice using the methods for accessing elements.

  1. Open HTMLDOM/Solutions/AccessingElements.html in your browser. It will look like this: Accessing Elements
  2. Click on the links and notice how the calendar changes. For example, click on Weekdays This Month and the calendar will appear as follows: Weekdays This Month
  3. Now open HTMLDOM/Exercises/AccessingElements.html in your editor.
  4. Notice that we've included some functions for you:
    1. addClass() - adds a class to an element.
    2. getElementsByClassName() - returns a collection of elements by class name.
    3. removeClass() - removes a class from an element.
    4. hasClassName - returns true or false depending on whether the element has the given class name.
    5. highlight() - adds the "highlight" class to the passed-in node or nodes.
    6. unhighlight() - removes the "highlight" class from the passed-in node or nodes.
    7. unhighlightDays() - helper function removing the "highlight" class from all the td elements to reset the calendar.
    8. weekEndDays() - gets all the weekend days using the getElementsByClassName() helper function from the ClassFiles/lib.js library and passes it to the highlight() function after calling unhighlightDays() to reset the calendar.
  5. Complete the offDays() function to highlight the days that are not in the current month (that have the "off" class).
  6. Complete the allDays() function to highlight all the days of the month.
  7. Complete the firstDayOfMonth() function to highlight the first day of the current month (the one that contains a value of "1"). Note that every table element contains a rows array, so you can access the first row in a table with table.rows[0].
  8. Complete the weekDays() function to highlight all the week days. Use querySelectorAll if the browser supports it.
  9. Challenge: Complete the weekDaysThisMonth() function to highlight all the weekdays of the month. Hint: You can make use of the weekDays() function.

Solution:

HTMLDOM/Solutions/AccessingElements.html
---- C O D E   O M I T T E D ----
function offDays() {
		//first get the calendar by its ID
		var calendar = document.getElementById("calendar");		
		//from the calendar, get all the weekend days (the td tags with a css class of "off")
		var offDays = getElementsByClassName(calendar,"off");
		//first "unhighlight" all the days
		unhighlightDays();
		//now, highlight the "off" days
		highlight(offDays);
		return offDays;
	}
	
	function allDays() {
		
		//first get the calendar by its ID
		var calendar = document.getElementById("calendar");
		//from the caledar, get all the days (the td tags)
		var days = calendar.getElementsByTagName("td");
		//now, highlight all the "days"
		highlight(days);
		return days;
	}
	
	function firstDayOfMonth() {
		
		//first get the calendar by its ID
		var calendar = document.getElementById("calendar");
		//the "calendar" is a table, so it has rows. The first row in the calendar array is at index position 0 (the header)
		var headerRow = calendar.rows[0];
		//the 2nd row in the calendar array is at index position 1 (the first data row)
		var firstRow = calendar.rows[1];
		//get all the td tags
		var tds = firstRow.getElementsByTagName("td");
		//unhighlight all the days
		unhighlightDays();
		//loop all the td tags in the calendar
		for (var i=0; i<tds.length; i++) {
			//as you loop the td tags, if the html in "this" td tag is 1 (the 1st day of the month), then highlight this td tag
			if ( tds[i].innerHTML==1 ) {
				highlight(tds[i]);
				return tds[i];
			}
		}
	}
	
	function weekDays() {
		var weekDays;
		if ( document.querySelectorAll ) {
			
			//get all the days that are not of class "weekend" - in other words, get the weekdays
			weekDays = document.querySelectorAll("td:not(.weekend)");
		} else {
			var calendar = document.getElementById("calendar");
			var days = calendar.getElementsByTagName("td");
			var weekDays = [];
			for (var i=0; i<days.length; i++) {
				if ( !hasClassName(days[i],"weekend") ) {
					weekDays.push(days[i]);
				}
			}
		}
		//unhighlight all days first
		unhighlightDays();
		//now just highlight all the "weekDays"
		highlight(weekDays);
		return weekDays;
	}
	
	function weekDaysThisMonth() {
		
		//create empty array to receive days as we find them
		var weekDaysThisMonth = [];
		
		// start by getting all weekdays, regardless of month
		var days=weekDays();
		var i;
		//loop through all the week days, if it does NOT have a css class of "off", then add the day to the weekDaysThisMonth array.  
		//Note that the push() method simply adds an element to an array
		for (i=0; i<days.length; ++i) {
			if ( !hasClassName(days[i],"off") ) {
				weekDaysThisMonth.push(days[i]);
			}
		}
		//now unhighlight all the days
		unhighlightDays();
		//and finally, highlight all the "weekDaysThisMonth"
		highlight(weekDaysThisMonth);
		return weekDaysThisMonth;
	}
---- C O D E   O M I T T E D ----

Code Explanation

Accessing Attribute Nodes

getAttribute()

The getAttribute() method of an element node returns the attribute value as a string or null if the attribute doesn't exist. The syntax is shown below:

myNode.getAttribute("AttName");

Most HTML attributes are also directly available as properties of the element node; for example, for an img tag element, the width, height, and source are available as the width, height, and src properties.

attributes[]

The attributes[] property references the collection of a node's attributes; in practice, though, it's better to access attributes with the getAttribute() method.

hasAttribute()

The hasAttribute(attName) method returns a Boolean (true/false) value indicating whether or not the element to which the method is applied includes the given attribute. For example, the code

var newsletterlink = document.getElementById("newsletterlink");
if (newsletterlink.hasAttribute("target")) {
	alert("newsletter link opens in new window");
}

would pop up a JavaScript alert if the link with id newsletterlink had a target attribute, such as:

<a href="nl.pdf" id="newsletterlink" target="_blank">View Newsletter</a>

Note that method hasAttribute() does not check the value of the attribute, only if the attribute exists for the given element.

setAttribute()

Method setAttribute(attName, attValue) allows us to add the given attribute to an element (if the attribute does not exist) or to change it (if the attribute does exist), and assign the given value. For instance, if we had this HTML:

<a href="nl.pdf" id="newsletterlink" target="_blank">View Newsletter</a>

then the following code:

var newsletterlink = document.getElementById("newsletterlink");
newsletterlink.setAttribute("target", "_self");

would change the value of the target attribute to _self.

removeAttribute()

Method removeAttribute(attName) removes the attribute from an element; attempting to remove an attribute that does not exist does not produce an error. The following code

var newsletterlink = document.getElementById("newsletterlink");
newsletterlink.removeAttribute("target");

would remove the target attribute from the link, if the link had that attribute.

Removing Nodes from the DOM

Element nodes have a removeChild() method, which takes a single parameter: the child node to be removed. There is no W3C method for a node to remove itself, but the following code will do the trick:

elem.parentNode.removeChild(elem);
elem = null;

removeChild(elem) only removes the element from its parent. Setting elem to null destroys the element.

Sometimes it's useful to remove all of a node's children in one fell swoop. The code below will handle this:

while (parent.hasChildNodes()) {
	removeElement(parent.childNodes[0]);
}

Creating New Nodes

The document node has separate methods for creating element nodes and creating text nodes: createElement() and createTextNode(). These methods each create a node in memory that then has to be placed somewhere in the object hierarchy. A new node can be inserted as a child to an existing node with that node's appendChild() and insertBefore() methods.

You can also use the appendChild() and insertBefore() methods to move an existing node - the node will be removed from its current location and placed at the new location (since the same node cannot exist twice in the same document).

These methods and some others are described in the table below.

Methods for Inserting Nodes
Method Description
appendChild() Takes a single parameter: the node to insert, and inserts that node after the last child node.
insertBefore() Takes two parameters: the node to insert and the child node that it should precede. The new child node is inserted before the referenced child node.
replaceChild() Takes two parameters: the new node and the node to be replaced. It replaces the old node with the new node and returns the old node.
setAttribute() Takes two parameters: the attribute name and value. If the attribute already exists, it replaces it with the new value. If it doesn't exist, it creates it.

The sample below illustrates how these methods work.

Code Sample:

HTMLDOM/Demos/InsertingNodes.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<script>
window.onload = function() {
	document.getElementById('append').addEventListener('click', appendNewListItem);
	document.getElementById('prepend').addEventListener('click', prependNewListItem);
	document.getElementById('change-num').addEventListener('click', changeStartNum);
	document.getElementById('replace').addEventListener('click', replaceOlWithUl);
};

function appendNewListItem() {
	var li = document.createElement("li");
	var liText = document.createTextNode("New List Item");
	              li.appendChild(liText);
	document.getElementById("first-list").appendChild(li);
}

function prependNewListItem() {
	var li = document.createElement("li");
	var liText = document.createTextNode("New List Item");
	var firstList = document.getElementById("first-list");
	li.appendChild(liText);
	firstList.insertBefore(li,firstList.firstChild);
}

function changeStartNum() {
	var firstList = document.getElementById("first-list");
	var start = firstList.getAttribute("start");
	if (start) {
		start++;
	} else {
		start=2;
	}
	firstList.setAttribute("start",start);
}

function replaceOlWithUl() {
	var list = document.getElementById("first-list");
	var list2 = document.getElementById("second-list");
	list.parentNode.replaceChild(list2,list);
}
</script>
<title>Inserting Nodes</title>
</head>

<body>
<form id="menu">
	<button id="append" type="button">Append List Item</button>
	<button id="prepend" type="button">Prepend List Item</button>
	<button id="change-num" type="button">Change Start Number</button>
	<button id="replace" type="button">Replace ordered list with bulleted list</button>
</form>
<ol id="first-list">
	<li>List Item</li>
	<li>List Item</li>
	<li>List Item</li>
	<li>List Item</li>
</ol>
<hr>
<ul id="second-list">
	<li>List Item</li>
	<li>List Item</li>
	<li>List Item</li>
	<li>List Item</li>
</ul>
</body>
</html>

Code Explanation

The page is shown below in a browser. Click on any of the menu items to see the methods in action. The unordered list will move when it replaces the ordered list, so it will then appear above the horizontal rule line.Inserting Nodes

As a coding practice, it is better to create a structure before inserting it into the page than to put the parent element in the page and then append to it. This saves the browser from unnecessary redrawing of the page as each child is added.

A Note on the setAttribute() Method

You can use the setAttribute() method to change the value of all attributes by name as you would expect with one exception. In Internet Explorer 8 and earlier, the class attribute must be referrred to as "className" in the setAttribute() method. This means that you shouldn't use setAttribute() for setting the class. Instead, change the className property.

Syntax

node.className = "new-class-name";

Or you can use the addClass() function in our ClassFiles/lib.js file.

Identifying the Target of an Event

Often you will want to take action on the target of an event. For example, in a drag-and-drop application, you click down on an element and drag it around the screen. To write that code, you need to be able to identify which element receives the click.

In most modern browsers, this is done with the target property of the event itself. The following demo illustrates:

Code Sample:

HTMLDOM/Demos/target.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../Styles/propagation.css">
<script>
	window.onload = function() {
		document.getElementById('outer').addEventListener('click', function(e) {
			findTarget(e);
		});
		document.getElementById('inner').addEventListener('click', function(e) {
			findTarget(e);
		});
		document.getElementById('heading').addEventListener('click', function(e) {
			findTarget(e);
		});
	}
	
	function findTarget(e) {
		e.target.innerHTML += " | click | ";
		e.stopPropagation();
	}
	
</script>
<title>Target</title>
</head>
<body>
	<h1 id="heading">Target</h1>
	<div id="outer">outer
		<div id="inner">inner</div>
	</div>
</body>
</html>

Code Explanation

In your browser, click on any of the elements: outer, inner, or the h1 element and the word "click" will get added to the element.

We can also make use of the currentTarget property: this property will refer to the element to which the event handler was attached, as opposed to the target property (which identifies the element from which the event fired.)

Manipulating the DOM

Duration: 25 to 35 minutes.

In this exercise, you will add JavaScript code to a page that displays information about several Beatles records - users will be able to toggle the display of more details about each record and add records to a "favorites" list.

  1. Open HTMLDOM/Exercises/ManipulatingDOM.html in a code editor and in a browser.
  2. Note that the CSS for the page is done for you: there are style rules to display the records (floated into two-across blocks) and other layout details for the page. In partcular, note the rules for span.more and span.details; these elements are set to display (or not) depending on whether their parent element (the divs of class record) have a class of active.
  3. Write the body of function addToFavorites which adds the title of the record to the "favorites" element when its "Add To Favorites" link is clicked.
  4. Add code to toggle the details content for each record: toggle the class active on the clicked .record element, which will (through the already-written CSS rules) change the display of the "More..." link and the detail content.
  5. Test your work.

Solution:

HTMLDOM/Solutions/ManipulatingDOM.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<style>
	section#records {
		float:left;
		width:70%;
	}
	aside#favorites {
		float:right;
		width:25%;
	}
	.record {
		width:40%;
		float:left;
		padding:2%;
		background:#efefef;
		margin:0 2% 20px 0;
	}
	.record:nth-child(2n){
		clear:left
	}
	.record span.more {
		font-style: italic;
	}
	.record span.details {
		display: none;
	}
	.record.active span.details {
		display: inline;
	}
	.record.active span.more {
		display: none;
	}
	.footer {
		clear:both;
		font-style: italic;
		padding-top: 30px;
	}
</style>
<script>
	window.onload = function() {

		function addToFavorites(node) {
			var recordListItem = document.createElement('p');
			var recordListItemText = document.createTextNode(document.querySelector('#' + node.id + ' h3 span.title').innerHTML);
			recordListItem.appendChild(recordListItemText);
			document.getElementById('favorites').appendChild(recordListItem);
		}

		var detailElements = document.getElementsByClassName('record');
		for (var i=0; i < detailElements.length; i++) {
			detailElements[i].addEventListener('click', function(e) {
				if (e.target.className == 'addtofavorites') {
					addToFavorites(e.target.parentNode.parentNode);
					e.target.parentNode.removeChild(e.target);
				} else {
					if (e.currentTarget.className == 'record') {
						e.currentTarget.className = 'record active';
					} else {
						e.currentTarget.className = 'record';
					}
					var myToggle = document.querySelector('#' + e.currentTarget.id + ' h3 span.toggle');
					var myToggleContents = myToggle.innerHTML;
					if (myToggleContents == '+') {
						myToggle.innerHTML = '-';
					} else {
						myToggle.innerHTML = '+';
					}
				}
			});
		}
		
	};
</script>
<title>Manipulating the DOM</title>
</head>

<body>
<section id="records">
	<h1>Selected Beatles Records</h1>
	<div class="record" id="harddaysnight">
		<h3><span class="title">A Hard Day's Night</span> <span class="toggle">+</span></h3>
		<p>A Hard Day's Night is the third studio album by the English rock band the Beatles, released on 10 July 1964, with side one containing songs from the soundtrack to their film A Hard Day's Night.<span class="more"> More...</span><span class="details"> The American version of the album was released two weeks earlier, on 26 June 1964 by United Artists Records, with a different track listing. In contrast to their first two albums, all 13 tracks on A Hard Day's Night were written by John Lennon and Paul McCartney showcasing the development of their songwriting talents. The album includes the title track, with its distinct opening chord,[4] and the previously released "Can't Buy Me Love", both transatlantic number-one singles for the band.</span></p>
		<p><a href="#" class="addtofavorites">Add To Favorites</a></p>
	</div>
	<div class="record" id="rubbersoul">
		<h3><span class="title">Rubber Soul</span> <span class="toggle">+</span></h3>
		<p>Rubber Soul is an album by the Beatles, their sixth UK album, and the tenth released in America.<span class="more"> More...</span><span class="details"> Released on 3 December 1965, it met with a highly favourable critical response and topped record charts in the United Kingdom for several weeks, as well as in the United States, where it was issued with a different selection of tracks.</span></p>
		<p><a href="#" class="addtofavorites">Add To Favorites</a></p>
	</div>
	<div class="record" id="abbeyroad">
		<h3><span class="title">Abbey Road</span> <span class="toggle">+</span></h3>
		<p>Abbey Road is the eleventh studio album by English rock band the Beatles, released on 26 September 1969 by Apple Records.<span class="more"> More...</span><span class="details"> The recording sessions for the album were the last in which all four Beatles participated. Although Let It Be was the final album that the Beatles completed before the band's dissolution in April 1970, most of the album had been recorded before the Abbey Road sessions began.[1] A double A-side single from the album, "Something"/"Come Together", released in October, topped the Billboard chart in the US.</span></p>
		<p><a href="#" class="addtofavorites">Add To Favorites</a></p>
	</div>
	<div class="record" id="letitbe">
		<h3><span class="title">Let It Be</span> <span class="toggle">+</span></h3>
		<p>Let It Be is the twelfth and final studio album by the English rock band the Beatles.<span class="more"> More...</span><span class="details"> It was released on 8 May 1970, almost a month after the group's break-up. Like most of the band's previous releases, it was a number one album in many countries, including both the US and the UK, and was released in tandem with the motion picture of the same name.</span></p>
		<p><a href="#" class="addtofavorites">Add To Favorites</a></p>
	</div>
</section>
<aside id="favorites">
	<h2>My Favorites</h2>
</aside>
<p class="footer">Content taken from <a href="https://en.wikipedia.org/wiki/A_Hard_Day%27s_Night_(album)">Wikipedia</a></p>
</body>
</html>

Code Explanation

When the "Add To Favorites" link is clicked for any record, we call the addToFavorites function, passing to it the .record node element from which the click came, and remove the "Add To Favorites" link (so that the user can't add the record twice). Function addToFavorites creates a new paragraph node, adds text (the title of the record) to it, and adds the node to the sidebar "Favorites" area.

When the user clicks any record, we toggle the active class (using e.currentTarget.className) and toggle between the + and - characters (using myToggle.innerHTML.)