6. The Document Object Model (DOM)
Earlier, we learned about basic HTML tags and how they can be used to structure a page. Let's now study how those elements come together to represent a complex page.
6.1 A Web Page as a Tree
Consider the following HTML page:
<div>
<div id="home">
<a href="/">Home</a>
</div>
<div id="about">
<a href="/about">About</a>
</div>
<div id="projects">
<a href="/projects">Projects</a>
</div>
</div>
<div>
<h1> Welcome to my Website!</h1>
<p>
This is my website. My name is Dingleberry Dingus. I know, it's a horrible name.
I was bullied a lot as a child. I made this website to talk about how I went through life with that name.
</p>
</div>
Let's talk about how some elements enclose other elements. Consider the "home" element with attribute id="home": <div id="home">
. It is enclosed by a single <div>
element - call this its parent - and it encloses a single <a>
element - call this element its child. Notice how any given element can have any number of children but only a single parent.
We can view the above page, and indeed any such HTML page as a single tree, where each node is an HTML element, each node's children are the HTML elements enclosed by that node, and each node's parent is the HTML element that contains it.
Where is the root of this tree? It's actually a hidden node with special tag <html>
. Most pages will actually make this explicit with the tags <html>
and <body>
, where the former is the root of the entire page (including various other hidden nodes), and the latter is the root of the nodes actually being displayed:
<html>
<body>
<div>
<!---->
</div>
<div>
<!---->
</div>
</body>
</html>
Note that if your HTML source code does not contain these tags, they will be added automatically by the browser.
The entire tree is then called the Document Object Model (DOM), where the document is the name of the tree containing objects specifying the page. You can read more about it in the official documentation.
6.2 Accessing the DOM using JavaScript
Trees are super useful because they are easy to understand and manipulate programmatically. Indeed, we can access our web page's DOM tree in JavaScript using the special global variable document
, which is provided by every browser and is technically an instance of the Document
object:
> document;
document: { ... }
However, the document
variable by itself does not refer to any node in the tree, but rather contains some functions for manipulating the entire tree. We can access individual nodes of the tree using some of these functions.
Notation: We will be using the following terms throughout:
document
: Refers to the global variable with functions to manipulate the entire DOM tree.element
: An HTML element and an instance ofElement
. The nodes of the DOM tree will be of this type.attribute
: An attribute of an HTML element. Technically attributes are also nodes in the DOM tree, but we will rarely if ever use them as such.
6.2.1 The Root of the DOM - <body>
We can access the root element of our page with document.body
. This returns the special <body>
tag that acts as the root node of the HTML elements that are actually displayed. As mentioned above, if your HTML source code does not contain this tag, it will be added automatically by the browser.
6.2.2 Element IDs
You may have noticed in the example code from earlier that some HTML elements had an id
attribute. This attribute is intended to be the unique identifier of the HTML element, and it allows us to access that particular HTML element in our JavaScript code using document.getElementById
.
Consider the following web page:
<div>
<div id="home">
<a href="/">Home</a>
</div>
<div id="about">
<a href="/about">About</a>
</div>
<div id="projects">
<a href="/projects">Projects</a>
</div>
</div>
We can access the element with ID "home" in our JavaScript code:
> document.getElementById("home");
<div id="home">...</div>
Note that you can set multiple elements to have the same ID in your HTML code, and the browser won't complain (it's still a bad idea!). However, when you call document.getElementById
, it will only return the first element with that ID.
6.2.3 Element Tags
Sometimes we would like to access all elements of a particular type. We can do this with document.getElementsByTagName
, which returns an HTMLCollection (you can treat this as a read-only array) of all elements of the specified tag name.
For instance, we can access all anchor tags in the following web page:
<div>
<div id="home">
<a href="/">Home</a>
</div>
<div id="about">
<a href="/about">About</a>
</div>
<div id="projects">
<a href="/projects">Projects</a>
</div>
</div>
With the code:
> var links = document.getElementsByTagName("a");
[a, a, a]
> links[0];
<a href="/">Home</a>
Additionally, Element.getElementsByTagName
is also a property of any Element object - the difference is that this function will only look in the children (subtree) of the given object:
> var home = document.getElementById("home");
> var homeLinks = home.getElementsByTagName("a");
[a]
> homeLinks[0];
<a href="/">Home</a>
6.2.4 Element Classes
There is another type of identifier called class that we can use to group HTML elements together. Like IDs, classes are specified as attributes of HTML tags in our web page's source code. Returning to our example web page, let's say we want to group together all the <div>
elements with anchor tags in them. We can add a class attribute "button" to their tags:
<div>
<div id="home" class="button">
<a href="/" class="button-link">Home</a>
</div>
<div id="about" class="button">
<a href="/about" class="button-link">About</a>
</div>
<div id="projects" class="button">
<a href="/projects" class="button-link">Projects</a>
</div>
</div>
In fact, we can associate any element with multiple classes by separating each class with a space:
<div class="button centered jumbo">
This div is associated with classes "button", "centered", "jumbo".
</div>
To access all elements of a particular class, use document.getElementsByClassName
, which returns an array-like of all elements with the given class names. If multiple class names (separated by a space) are given, only elements that have ALL the class names are returned.
> var buttons = document.getElementsByClassName("button");
[div, div, div]
> buttons[0];
<div id="home" class="button">...</div>
Element.getElementsByClassName
is also a property of any Element object - this function will only look in the children (subtree) of the given object:
> var home = document.getElementById("home");
> var homeLinks = home.getElementsByClassName("button-link");
[a]
> homeLinks[0];
<a href="/">Home</a>
6.2.5 Query Selectors
document.querySelectorAll
combines the functionality of getElementById
, getElementsByTagName
, and getElementsByClassName
- we can pass in a single string containing the ID that we want, the tag(s) that we want, and the class(es) that we want. This string is given in CSS Selector Syntax, and the function returns all elements that satisfy these selectors.
6.2.6 Element Children and Parent
We can access and iterate over an element's direct children in the DOM tree using Element.childNodes
, which returns a list of all the children of an element in the DOM tree. Additionally, we can access an element's parent node using Element.parentNode
. Note that both of these properties are read-only properties and not functions.
6.3 Manipulating the DOM using JavaScript
So far we've seen how to access nodes in the DOM tree. We can also modify, delete, and even add new nodes to the DOM tree!
6.3.1 Modifying DOM Nodes
The Element.innerHTML
Property
Every HTML Element has an innerHTML
property that specifies the content enclosed by that element as a string. We can get or set this property as we please, and even add in arbitrary HTML in the string.
Consider the following page:
<div id="button">
Stanford sux!!!
</div>
We can completely replace or add to the content of the div with the following code:
> var btn = document.getElementById("button");
> btn.innerHTML;
"Stanford sux!!!"
> btn.innerHTML = btn.innerHTML + "<p>Go bears!!!</p>";
The result will look like this:
<div id="button">
Stanford sux!!!
<p>Go bears!!!</p>
</div>
This is one - albeit very jank - method of deleting and adding nodes to the DOM! For the most part you should only use this method to edit text content; use other methods to properly delete and add nodes to the DOM.
Setting and Getting Element Attributes
We can also get and set the attributes of an HTML element using the following functions:
These functions may be useful for e.g. setting the URL of an anchor tag. You can also use them to get and set the class and ID of an element, but we also have easier-to-use properties for that since it is so common:
6.3.2 Adding DOM Nodes
Adding a node to the DOM takes 2 steps:
- Create the HTML Element to add.
- Add the HTML Element to the DOM tree in the right place.
We create elements using the document.createElement
function; this function takes in a string tag name and creates the corresponding element.
To add an element to the DOM, use [Element.appendChild
] to add an HTML element as a child of a specific node in the DOM tree. As an example, consider the following web page:
<div id="button">
Stanford sux!!!
</div>
Here is a better way of adding a paragraph element to the button above:
var newParagraph = document.createElement("p");
newParagraph.innerHTML = "Go bears!!!";
var btn = document.getElementById("button");
btn.appendChild(newParagraph);
The end result looks like this:
<div id="button">
Stanford sux!!!
<p>Go bears!!!</p>
</div>
6.3.3 Deleting DOM Nodes
Deleting nodes is done by the parent of the node being deleted, using the Element.removeChild
function, which takes a reference to the element to remove and returns the element again after it has been deleted from the DOM tree.
Let's go back to our example page:
<div id="button">
Stanford sux!!!
</div>
The following code will remove the single div above:
> var btn = document.getElementById("button");
> document.body.removeChild(btn);
If the specified element does not exist in the DOM or is not a child of the parent node, this function will throw an error.