The DOM

The Document Object Model (or DOM) is a browser’s way of representing the content that is rendered on web page. Its main use is providing a way that the web page can be accessed and altered through the programs we write. It represents the structure of the web page using the following many data types, including the following:

  • window
  • document
  • element
  • nodeList
  • text

window

The window object is the root of the web page. It is the first thing that gets loaded when you visit a page and will contain the document object. Other than the document object, the window object is also responsible for keeping track of what is displayed on the screen (i.e. the height/width of the window, or how far the window has been scrolled in the x or y direction). This is also the base context in which all of your functions are loaded and defined. If you were to console.log(this) in a function you should see Window {stop: function, open: function, alert: function, confirm: function, prompt: function…}

document

The document object represents the parent node for all of the other nodes. Any node can be accessed through the document node (with the exception of iframes since those are technically separate window objects). If you console.log(document) you should see something like this:

1
2
3
4
5
6
#document
  <!DOCTYPE html>
  <html>
  <head>...</head>
  <body>...</body>
  </html>

You can see that this is the parent for all other nodes, with a simple recursive function:

HTML
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>...</head>
<body>
  <ul>
    <li>This is a list item
    </li>
  </ul>
</body>
</html>
Javascript
1
2
3
4
5
6
7
8
9
10
11
let li = document.getElementsByTagName("LI")[0]
function findParentNode(element) {
  if (element.parentNode) {
    return findParentNode( element.parentNode )
  }
  else {
    return element
  }
}

findParentNode(li)

this will output the document node as shown above.

element

element nodes are the thing that most of us think of when we think of HTML nodes. Each element node will represent a single HTML element in the DOM. These are what get returned with the getElementById() function or other functions that select only a single element. These have a variety of useful properties like:

  • attributes
  • classList
  • className
  • clientHeight
  • clientWidth
  • id
  • innerHTML
  • outerHTML
  • tagName

Along with a large number of methods.

nodeList

The nodeList object is similar to an array of different node objects (e.g. element nodes.) This is what gets returned when you use the getElementsByTagName() or querySelectorAll() functions. These have some, but not all of the iterator functions for the normal javascript array objects.

text

The text node object is just a string that represents the text inside an element node.

Traversing the DOM

Knowing the way the DOM is structured only useful if we can also traverse it. The most generalized method to do this with (in vanilla JS) is querySelectorAll(). This function takes in a CSS selector and returns all elements (in a nodeList) that match that selector. This gives us the full power of CSS selectors in selecting element nodes.

CSS selectors

There are only three base CSS selectors:

  • element
  • class
  • id They are used in the following way:
1
2
3
4
5
6
7
8
9
// element selector
document.querySelectorAll('p') // is equivalent to
document.getElementsByTagName('p')

// class selector
document.querySelectorAll('.my-class')

// id selector
document.querySelectorAll('#my-id')

For element selectors, you just use the tag name (e.g. p, li, body, div), for class selectors you append the class with a ., and for id selectors you append the id with a #. You can also chain these together to further narrow down your selector. For example, p.my-class will only select p elements with the class my-class.

Combinators

Our first step towards being more specific with our CSS selectors is the introduction of combinators. Combinators are the way you show a relationship between two base CSS selectors.

There are only four different combinators and they are used in the following ways:

1
2
// descendant combinator
document.querySelectorAll('div li')

This selects all li elements that are within a div element, regardless of how deeply nested they are.

1
2
// child combinator
document.querySelectorAll('div > ul')

This selects all ul elements that have a div element as its direct parent.

1
2
// adjacent sibling combinator
document.querySelectorAll('li.my-class + li')

This will select all li elements that come immediately after the li elements with the class my-class.

1
2
// general sibling combinator
document.querySelectorAll('li#my-id ~ li')

This will select all li elements that come after the li element with the id my-id.

NOTE: For the adjacent sibling combinator and the general sibling combinator, these will only select elements that exist below the first specified element in the relationship (i.e. they follow the first element in the DOM)

Psuedo-Classes

The next way we have of being more specific is with psuedo-classes. These allow us to define what state the the elements are in. All psuedo-classes are indicated by a colon, followed by the name of the psuedo-class. Some useful psuedo-classes include:

  • :empty
  • :first-child
  • :first-of-type
  • :focus
  • :hover
  • :nth-child(n)
  • :only-of-type

A full list of psuedo-classes can be found here: MDN psuedo-classes

Be careful using the psuedo-classes that involve temporary element states like button:hover or input:focus. If you want to do something when these happen, it is better to use an event listener like:

1
2
document.querySelectorAll('button').addEventListener('onmouseover', callback)
document.querySelectorAll('input').addEventListener('onfocus', callback)

Psuedo-Elements

Psuedo-elements allow us to style specific parts of an element. You likely won’t be creating any psuedo-elements with Javascript, but accessing them is still important. Psuedo-elements are indicated by a double colon, followed by the name of the psuedo-element. The most common ones include:

  • ::after
  • ::before

The full list can be found here: MDN psuedo-elements.

Just like with psuedo-classes, you should avoid using the psuedo-elements that involve temporary states like p::selection.

Attribute Selectors

The last method we have for specifying our CSS selectors is the attribute selector. These allow us to specify that we only want elements with a certain attribute. The syntax for the attribute selector is [attribute] where attribute is the name of the attribute you want to specify. For example:

1
document.querySelectorAll('a[target]')

This will select all a elements with a target attribute (e.g. <a target="_blank">link</a>.) We also have the ability to specify what the attributes value should be in a variety of different ways:

1
document.querySelectorAll('a[target="_blank"]')

This will select all a elements with the target attribute equal to _blank.

1
document.querySelectorAll('[title~="flower"]')

This will select all elements with a title attribute that contains the word ‘flower’.

1
document.querySelectorAll('[class|="top"]')

This will select all elements with a class that starts with the word ‘top’.

1
document.querySelectorAll('[class^="top"]')

This will select all elements with a class that starts with the characters ‘top’.

1
document.querySelectorAll('[class$="test"]')

This will select all elements with a class that ends with the characters ‘test’.

1
document.querySelectorAll('[class*="te"]')

This will select all elements with a class that contains the characters ‘te’.

With the use of these selectors and the element node properties like parentNode, it is possible to select any element from any other element. In practice, this is referred to as traversing the DOM.