Web Programming Step by Step, 2nd Edition

Lecture 19: The DOM Tree

Reading: 9.2 - 9.4, 10.1

Except where otherwise noted, the contents of this document are Copyright 2012 Marty Stepp, Jessica Miller, and Victoria Kirst. All rights reserved. Any redistribution, reproduction, transmission, or storage of part or all of the contents in any form is prohibited without the author's expressed written permission.

Valid HTML5 Valid CSS

Complex DOM manipulation problems

How would we do each of the following in JavaScript code? Each involves modifying each one of a group of elements ...

The DOM tree

DOM tree

Prototype's DOM element methods

absolutize addClassName classNames cleanWhitespace clonePosition
cumulativeOffset cumulativeScrollOffset empty extend firstDescendant
getDimensions getHeight getOffsetParent getStyle getWidth
hasClassName hide identify insert inspect
makeClipping makePositioned match positionedOffset readAttribute
recursivelyCollect relativize remove removeClassName replace
scrollTo select setOpacity setStyle show
toggle toggleClassName undoClipping undoPositioned update
viewportOffset visible wrap writeAttribute

Prototype's DOM tree traversal methods

method(s) description
ancestors, up elements above this one
childElements, descendants, down elements below this one (not text nodes)
siblings, next, nextSiblings,
previous, previousSiblings, adjacent
elements with same parent
as this one (not text nodes)
DOM element
// alter siblings of "main" that do not contain "Sun"
var sibs = $("main").siblings();
for (var i = 0; i < sibs.length; i++) {
	if (sibs[i].innerHTML.indexOf("Sun") < 0) {
		sibs[i].innerHTML += " Sunshine";
	}
}

Selecting groups of DOM objects

name description
getElementsByTagName returns array of descendents with the given tag, such as "div"
getElementsByName returns array of descendents with the given name attribute (mostly useful for accessing form controls)
querySelector * returns the first element that would be matched by the given CSS selector string
querySelectorAll * returns an array of all elements that would be matched by the given CSS selector string

Getting all elements of a certain type

highlight all paragraphs in the document:

var allParas = document.querySelectorAll("p");
for (var i = 0; i < allParas.length; i++) {
	allParas[i].style.backgroundColor = "yellow";
}
<body>
	<p>This is the first paragraph</p>
	<p>This is the second paragraph</p>
	<p>You get the idea...</p>
</body>

Complex selectors

highlight all paragraphs inside of the section with ID "address":

// var addrParas = $("address").getElementsByTagName("p");
var addrParas = document.querySelectorAll("#address p");
for (var i = 0; i < addrParas.length; i++) {
	addrParas[i].style.backgroundColor = "yellow";
}
<p>This won't be returned!</p>
<div id="address">
	<p>1234 Street</p>
	<p>Atlanta, GA</p>
</div>

Prototype's methods for selecting elements

Prototype adds methods to document (and all DOM elements) for selecting groups of elements:

getElementsByClassName array of elements that use given class attribute
select array of descendants that match given CSS selector, such as "div#sidebar ul.news > li" (identical to querySelectorAll on the element)
$$ equivalent to document.querySelectorAll
var gameButtons = $("game").select("button.control");
for (var i = 0; i < gameButtons.length; i++) {
	gameButtons[i].style.color = "yellow";
}

The $$ function

var arrayName = $$("CSS selector");
// hide all "announcement" paragraphs in the "news" section
var paragraphs = $$("div#news p.announcement");
for (var i = 0; i < paragraphs.length; i++) {
	paragraphs[i].hide();
}

Common $$ issues

Creating new nodes

name description
$(document.createElement("tag")) creates and returns a new empty DOM node representing an element of that type
// create a new <h2> node
var newHeading = $(document.createElement("h2"));
newHeading.innerHTML = "This is a heading";
newHeading.style.color = "green";

Modifying the DOM tree

Every DOM element object has these methods:

name description
appendChild(node) places given node at end of this node's child list
insertBefore(newold) places the given new node in this node's child list just before old child
removeChild(node) removes given node from this node's child list
replaceChild(newold) replaces given child with new node
var p = $(document.createElement("p"));
p.innerHTML = "A paragraph!";
$("main").appendChild(p);

Removing a node from the page

function slideClick() {
	var bullets = document.getElementsByTagName("li");
	for (var i = 0; i < bullets.length; i++) {
		if (bullets[i].innerHTML.indexOf("children") >= 0) {
			bullets[i].remove();
		}
	}
}

DOM versus innerHTML hacking

Why not just code the previous example this way?

function slideClick() {
	$("thisslide").innerHTML += "<p>A paragraph!</p>";
}
  • Imagine that the new node is more complex:
    • ugly: bad style on many levels (e.g. JS code embedded within HTML)
    • error-prone: must carefully distinguish " and '
    • can only add at beginning or end, not in middle of child list
function slideClick() {
	$("main").innerHTML += "<p style='color: red; " +
			"margin-left: 50px;' " +
			"onclick='myOnClick();'>" +
			"A paragraph!</p>";
}

Problems with reading/changing styles

<button id="clickme">Click Me</button>
window.onload = function() {
	$("clickme").onclick = biggerFont;
};
function biggerFont() {
	var size = parseInt($("clickme").style.fontSize);
	size += 4;
	$("clickMe").style.fontSize = size + "pt";
}

Accessing styles in Prototype

function biggerFont() {
	// turn text yellow and make it bigger
	var size = parseInt($("clickme").getStyle("font-size"));
	$("clickme").style.fontSize = (size + 4) + "pt";
}

Common bug: incorrect usage of existing styles

$("main").style.top = $("main").getStyle("top") + 100 + "px";            // bad!

Getting/setting CSS classes

function highlightField() {
	// turn text yellow and make it bigger
	if (!$("text").className) {
		$("text").className = "highlight";
	} else if ($("text").className.indexOf("invalid") < 0) {
		$("text").className += " highlight";
	}
}

Getting/setting CSS classes in Prototype

function highlightField() {
	// turn text yellow and make it bigger
	if (!$("text").hasClassName("invalid")) {
		$("text").addClassName("highlight");
	}
}

Types of DOM nodes

<p>
	This is a paragraph of text with a 
	<a href="/path/page.html">link in it</a>.
</p>
DOM Tree

Traversing the DOM tree manually

every node's DOM object has the following properties:

name(s) description
firstChild, lastChild start/end of this node's list of children
childNodes array of all this node's children
nextSibling, previousSibling neighboring nodes with the same parent
parentNode the element that contains this node

DOM tree traversal example

<p id="foo">This is a paragraph of text with a 
	<a href="/path/to/another/page.html">link</a>.</p>
navigate tree

Element vs. text nodes

<div>
	<p>
		This is a paragraph of text with a 
		<a href="page.html">link</a>.
	</p>
</div>