Learn JavaScript: Novice to Ninja

Chapter 6: The Document Object Model

The Document Object Model (DOM) allows you to access elements of a web page and enable interaction with the page by adding and removing elements, changing the order, content and attributes of elements, and even altering how they are styled.

In this chapter, we’ll cover the following topics:

  • Introduction to the DOM
  • Getting elements ― getElementByIdgetElementsByClassNamegetElementsByTagNamequerySelector and querySelectorAll
  • Navigating the DOM
  • Getting and setting an element’s attributes
  • Updating the DOM by creating dynamic markup
  • Changing the CSS of an element
  • Our project ― we’ll dynamically insert each question into the HTML

The Document Object Model

What is the DOM?

The Document Object Model, or DOM for short, represents an HTML document as a network of connected nodes that form a tree-like structure.

The DOM treats everything on a web page as a node. HTML tags, the text inside these tags, even the attributes of a tag are all nodes. The HTML tag is the root node, and every other part of the document is a child node of this.

Take the following piece of HTML as an example:

<p class='warning'>Something has gone <em>very</em> wrong!</p>

This can be represented as the tree diagram shown below.

The DOM tree

The DOM is not actually part of JavaScript because it islanguage agnostic(although JavaScript is, by far, the language most commonly used with it). This means it can be used in any programming language, not just JavaScript. We can use JavaScript to access and modify different parts of a web page using a special built-in object called document .

History of the DOM

In the early days of the web, browser vendors such as Netscape and Microsoft developed their own distinct ways of accessing and altering parts of a web page. In the beginning, they tended to focus on common page elements such as images, links and forms – this was known as Dynamic HTML (DHTML). These methods became known as DOM level 0, or legacy DOM. Some of the more common methods, such as those used for selecting images and forms, can still be used in the current DOM.

The World Wide Web Consortium (W3C) started to standardize the process, and created the DOM level 1 in 1998. This introduced a complete model for web pages that allowed every part of them to be navigated.

The DOM level 2 specification was published in 2000 and introduced the popular getElementById() method, which made it much easier to access specific elements on a web page. The DOM level 3 specification was published in 2004, and since then the W3C has abandoned using levels. The DOM specification is developed as aliving standard.

Despite the standardization process, browsers have not always implemented the DOM consistently, so it’s been difficult to program for in the past. Fortunately, since Internet Explorer 8, DOM support has been much more consistent, and modern browsers now implement the current DOM level 3. They’re also implementing more of the new DOM level 4 features with every update.

An Example Web Page

To illustrate the DOM concepts covered in this chapter, we’ll use a basic web page that contains a heading and three paragraph elements. Save the following code in a file called heroes.html :

<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Justice League</title></head><body>    <header>    <h1 id='title'>Justice League</h1>    </header>    <ul id='roster'>    <li class='hero'>Superman</li>    <li class='vigilante hero' id='bats'>Batman</li>    <li class='hero'>Wonder Woman</li>    </ul></body></html>

Below is a node tree diagram for the <ul> element with a class of roster :

The DOM tree

What’s With the Extra Text Nodes?

There appear to be some extra #text nodes in this diagram, even in places where there isn’t any text. This is because the DOM also stores any whitespace that is in the HTML document as text nodes.

Because we’re using the browser, the best way to follow along with the examples in this chapter is to use the console built into the web browser (we discussed how to use this in Chapter 1). This will allow you to enter commands that interact with the elements on the web page and see the results. The screenshot below shows the page with the console open.

Using the console

Console Shortcuts

Here are a couple of useful shortcuts that will help speed things up when you’re using the console:

  • Pressing TAB will autocomplete any methods and should show you a list of possible methods
  • Pressing the UP arrow key will select the previous command entered.

Getting Elements

The DOM provides several methods that allow us to access any element on a page. These methods will return a node object or a node list, which is an array-like object. These objects can then be assigned to a variable and be inspected or modified.

For example, we can access the body element of a web page and assign it to the variable body by entering the following code into the browser console:

const body = document.body;

Now we have a reference to the body element, we can check its type:

typeof body;<< "object";

This is a special Node object with a number of properties and methods that we can use to find information about, or modify, the body element.

For example, we can use the nodeType property to find out what type of node it is:

body.nodeType;<< 1

All nodes have a numerical code to signify what type they are. These are summmarized in the table below.

CodeType
1element
2attribute
3text
8comment
9body

There are other types not covered in the table, but these aren’t used in HTML documents. As we can see from the table, a code of 1 confirms that body is an element node.

We can also use the nodeName property to find the name of the element:

body.nodeName;<< "BODY"

Note that the element name is returned in uppercase letters.

This is a live reference to what is displayed in the browser. You can see this by hovering your cursor over the reference in the console and see it highlighted in the main viewport, as illustrated in the screenshot below:

Highlighting the reference

Legacy DOM Shortcut Methods

There are some methods from DOM Level 0 that can still be employed to access commonly used elements. These include:

  •  Document.body returns the body element of a web page, as we saw in the previous example.
  •  Document.images returns a node list of all the images contained in the document.
  •  Document.links returns a node list of all the <a> elements and <area> elements that have an href attribute.
  •  Document.anchors returns a node list of all the <a> elements that have a name attribute.
  •  Document.forms returns a node list of all the forms in the document. This will be used when we cover forms in Chapter 8.

Not Actually Arrays

Node lists are array-like objects, but they are not arrays. You can access each item using index notation. For example, document.images[0] will return the first image in the node list of all the images in the document.

They also have a length property, which can be used to iterate through every element using a for loop, like so:

for (let i=0 ; i < document.images.length ; i++) {// do something with each image using document.images[i]}

Node listsdon’thave any other array methods such as slicesplice and join .

ES6 makes it very easy to turn a node list into an array, however. You can either use the Array.from() method:

const imageArray = Array.from(document.images);

Or you can use the spread operator:

const imageArray = [...document.images];

Once the node list has been turned into an array, you can use all the array methods on it.

Getting An Element By Its ID

The getElementById() method does exactly what it says on the tin. It returns a reference to the element with a unique id attribute that is given as an argument. For example, we can get a reference to the <h1> heading element with the id of ‘title’ in the ‘heroes.html’ page by writing this in the console:

const h1 = document.getElementById('title');

Every id attribute should be unique to just one element(Make sure you follow this rule – it’s not enforced by the HTML parser, but odd things can happen in your code if you have more than one element with the same ID). This method will return a reference to the unique element with the ID provided as an argument. For this reason, it’s a very quick way of finding elements in a document. It’s also supported in all the major browsers, and is probably the most commonly used method of accessing elements on a web page.

If no element exists with the ID provided, null is returned.

Get Elements By Their Tag Name

 getElementsByTagName() will return a live node list of all the elements with the tag name that is provided as an argument. For example, we can get all the list items (HTML tag of <li> ) in the document using this code:

const listItems = document.getElementsByTagName('li');

As this is a node list, we can use the index notation to find each individual paragraph in the list:

listItems[0];<< <li class='hero'>Superman</li>listItems[1];<< <li class='vigilante hero' id='bats'>Batman</li>listItems[2];<< <li class='hero'>Wonder Woman</li>

If there are no elements in the document with the given tag name, an empty node list is returned.

Get Elements By Their Class Name

 getElementsByClassName() will return a live node list of all elements that have the class name that is supplied as an argument. For example, we can return a collection of all elements with the class of ‘hero’ using the following code:

const heroes = document.getElementsByClassName('hero');

Note that, in this case, it is exactly the same collection that was returned when we found all of the list items previously.

There are three elements on the page that have the class name of hero , which we can test by querying the length property:

heroes.length;<< 3

Note that if there are no elements with the given class, an HTML collection is still returned, but it will have a length of 0:

document.getElementsByClassName('villain').length;<< 0

 document.getElementsByClassName is supported in all the major modern browsers, but was only supported in Internet Explorer 9 and later.

Query Selectors

The document.querySelector() method allows you to use CSS notation to find thefirstelement in the document that matches that matches a CSS selector provided as an argument. If no elements match, it will return null .

The document.querySelectorAll() method also uses CSS notation but returns a node list ofallthe elements in the document that match the CSS query selector. If no elements match, it will return an empty node list.

These are both very powerful methods that can emulate all the methods discussed, as well as allowing more fine-grained control over which element nodes are returned.

Query Selectors

You do have to know a bit about CSS query selectors to be able to use this method! If you don’t know, or just need a reminder, you might want to checkthis page out at SitePoint.

For example, the following could be used instead of document.getElementById() :

document.querySelector('#bats');<< <li class="vigilante hero" id="bats">Batman</li>

And this could be used instead of document.getElementsByClassName :

document.querySelectorAll('.hero');<< NodeList [<li class="hero">, <li id="bats">, <li class="hero">]

Note that this is not alivenode list. See the section later in this chapter for more details about live node lists.

CSS query selectors are a powerful way of specifying very precise items on a page. For example, CSS pseudo-selectors can also be used to pinpoint a particular element. The following code, for example, will return only the last list item in the document:

const wonderWoman = document.querySelector('li:last-child');

The querySelector() method can be called onanyelement, rather than just document . For example, we can get a reference to the <ul> element, using the following code:

const ul = document.querySelector('ul#roster');

Now we can use the querySelector() method on this element, to find a <li> element with an id of ‘bats’:

const batman = ul.querySelector('li#bats')

All modern browsers support these methods, and Internet Explorer supported it from version 8 onwards. Version 8 of Internet Explorer only understands CSS2.1 selectors (because that is the highest level of CSS that it supports), so complex CSS3 notations such as ul ~ p:empty (which finds any empty <p> elements that are also siblings with a <ul> element) will fail to work.

jQuery

jQuery is a popular JavaScript framework that makes it very easy to find elements on a page using a CSS-style syntax. It uses document.querySelectorAll() in the background whenever it can. For example, the jQuery code $('ul#roster').find('li#bats'); is basically doing the same as our previous example:

const ul = document.querySelector('ul#roster');ul.querySelector('li#bats')

Navigating the DOM Tree

Node objects have a number of properties and methods for navigating around the document tree. Once you have a reference to an element, you can walk along the document tree to find other nodes. Let’s focus on a particular part of the document tree in our example. The relationship each node has with the Batman node is shown below.

Super Hero DOM tree

The childNodes property is a list of all the nodes that are children of the node concerned. The following example will return all the child nodes of the element with an id attribute of roster :

const heroes = document.getElementById('roster');heroes.childNodes<< NodeList [#text "", <li class="hero">, #text "", <li id="bats">, #text "", <li class="hero">, #text "", <li class="hero">, #text "

Note that the childNodes property returnsallthe nodes that are children of an element. This will include any text nodes, and since whitespace is treated as a text node, there will often be empty text nodes in this collection.

The children property only returns anyelementnodes that are children of that node, so will ignore any text nodes. Note that this is only supported in Internet Explorer from version 9 onwards:

heroes.children // this will only contain list items<< HTMLCollection [<li class="hero">, <li id="bats">, <li class="hero">, <li class="hero">] (4)heroes.children.length<< 3

The firstChild property returns the first child of a node:

heroes.firstChild<< #text " "

And the lastChild property returns the last child of a node:

heroes.lastChild<< #text " "

Be careful when using these properties ― the first or last child node can often be a text node, even if it’s just an empty string generated by some whitespace (this can be seen in both of the examples above).

For example, you might expect the first child node of the <ul> element to be a <li> element, and the last child to also be a <li> element, but they are both in fact text nodes, generated by the whitespace characters in between the <ul> and <li> tags:

The parentNode property returns the parent node of an element. The following code returns the roster node because it’s the parent of the wonderWoman node:

const wonderWoman = document.querySelector('ul#roster li:last-child');wonderWoman.parentNode<< <ul id='roster'>…</ul>

The nextSibling property returns the next adjacent node of the same parent. It will return null if the node is the last child node of that parent:

wonderWoman.nextSibling<< #text " "

The previousSibling property returns the previous adjacent node. It will return null if the node is the first child of that parent:

wonderWoman.previousSibling<< #text " "

Once again, these methods find the next and previousnode, not element, so they will often return a blank text node, as in the examples above.

Using these properties allows you to navigate around the whole of the document tree.

Finding the Value of a Node

Finding the text contained within an element is actually trickier than it sounds. For example, the variable wonderWoman has a DOM node that contains the following HTML:

<li class='hero'>Wonder Woman</li>

It clearly contains the text ‘Wonder Woman’, but this is held in a text node, which is the first child of the <li> element node:

const textNode = wonderWoman.firstChild;<< "Wonder Woman"

Now we have a reference to the text node, we can find the text contained inside it using the nodeValue method:

textNode.nodeValue;<< "Wonder Woman"

We can also find this value using the textContent property. This will return the text content of an element as a string:

wonderWoman.textContent<< "Wonder Woman"

Note that Internet Explorer version 8 does not support the textContent property, but has the innerText property, which works in a similar way.

Getting and Setting Attributes

All HTML elements have a large number of possible attributes such as ‘class’, ‘id’, src , and ‘href’. The DOM has numerous getter and setter methods that can be used to view, add, remove or modify the value of any of these attributes.

Getting An Element’s Attributes

The getAttribute() method returns the value of the attribute provided as an argument:

wonderWoman.getAttribute('class');<< "hero"

If an element does not have the given attribute, it returns null :

wonderWoman.getAttribute('src');<< null

Setting An Element’s Attributes

The setAttribute can change the value of an element’s attributes. It takes two arguments: the attribute that you wish to change, and the new value of that attribute.

For example, if we wanted to change the class of the element in the wonderWoman variable to ‘villain’, we could do so using this code:

wonderWoman.setAttribute('class', 'villain');<< undefinedwonderWoman.getAttribute('class');<< "villain"

If an element does not have an attribute, the setAttribute method can be used to add it to the element. For example, we can add an id of ‘amazon’ to the wonderWoman element:

wonderWoman.setAttribute('id','amazon');wonderWoman.getAttribute('id');<< 'amazon'

Dot Notation

The legacy DOM allows access to attributes using dot notation, like so:

wonderWoman.id;<< 'amazon'

Now if we take a look at the wonderWoman variable, we can see that the changes have been made, as this is a live reference to the element:

wonderWoman<< <li class=​"villain" id=​"amazon">​Wonder Woman​</li>​

This notation is still supported, although some attribute names such as class and for are reserved keywords in JavaScript, so we need to use className and htmlFor instead.

Classes Of An Element

The className Property

As we’ve seen, we can modify the class name of an element using the setAttribute() method. There is also a className property that allows the class of an element to be set directly. In addition, it can be used to find out the value of the class attribute:

wonderWoman.className;<< "villain"

We can change the class back to ‘hero’ with the following code:

wonderWoman.className = 'hero'<< "hero"

Be Careful Updating className

Changing the className property of an element by assignment will overwrite all other classes that have already been set on the element.

This problem can be avoided by using the classList property instead.

The classList Property

The classList property is a list of all the classes an element has. It has a number of methods that make it easier to modify the class of an element. It’s supported in all modern browsers and in Internet Explorer from version 10 onwards.

The add method can be used to add a class to an element without overwriting any classes that already exist. For example, we could add a class of ‘warrior’ to the wonderWoman element:

wonderWoman.classList.add('warrior');

Now we can check that it has been added:

wonderWoman.className;<< "hero warrior"

The remove method will remove a specific class from an element. For example, we could remove the class of ‘warrior’ with the following code:

wonderWoman.classList.remove('warrior');

The toggle method is a particularly useful method that willadda class if an element doesn’t have it already, andremovethe class if it does have it. It returns true if the class was added and false if it was removed. For example:

wonderWoman.classList.toggle('hero'); // will remove the 'hero' class<< falsewonderWoman.classList.toggle('sport'); // will add the 'hero' class back<< true

The contains method will check to see if an element has a particular class:

wonderWoman.classList.contains('hero');<< truewonderWoman.classList.contains('villain');<< false

 classList and Older Versions of IE

Unfortunately, the classList property is only available in Internet Explorer version 10 and above, so if you want to support older versions of Internet Explorer, you could create a function that will add an extra class to an element, rather than just replace the current class. The addClass function takes the element and the new class name to be added as parameters. It uses a simple if block to check if the value of the element’s className property is truthy. If it is, it will append the new class to the end of the current class; otherwise, it will simply set the new class as the element’s class:

function addClass(element,newClass){    if (element.className) {        element.className = element.className + ' ' + newClass;    } else {        element.className = newClass;    }    return element.className;}

Let’s test this out on the wonderWoman element, which already has a class of hero :

addClass(wonderWoman,'warrior');<< "hero warrior"

Creating Dynamic Markup

So far we’ve looked at how to gain access to different elements of a web page and find out information about them. We’ve also looked at how to change the attributes of elements. In this section, we’re going to learn how to create new elements and add them to the page, as well as edit elements that already exist and remove any unwanted elements.

Creating An Element

The document object has a createElement() method that takes a tag name as a parameter and returns that element. For example, we could create a new list item as a DOM fragment in memory by writing the following in the console:

const flash = document.createElement('li');

At the moment, this element is empty. To add some content, we’ll need to create a text node.

Creating a Text Node

A text node can be created using the document.createTextNode() method. It takes a parameter, which is a string containing the text that goes in the node. Let’s create the text to go in our new element:

const flashText = document.createTextNode('Flash');

Now we have an element node and a text node, but they are not linked together ― we need to append the text node to the paragraph node.

Appending Nodes

Every node object has an appendChild() method that will add another node (given as an argument) as a child node. We want our newly created text node to be a child node of the list element node. This means that it’s the flash object that calls the method, with flashText as its argument:

flash.appendChild(flashText);

Now we have a <li> element that contains the text we want. So the process to follow each time you want to create a new element with text content is this:

  1. Create the element node
  2. Create the text node
  3. Append the text node to the element node

This can be made simpler by using the textContent property that every element object has. This will add a text node to an element without the need to append it, so the code above could have been written as the following:

const flash = document.createElement('li');flash.textContent = 'Flash';

While this has cut the number of steps from three down to two, it can still become repetitive, so it’s useful to write a function to make this easier. This is what we’ll do next.

A Function To Create Elements

When we created our new list item element, all we specified was the type of tag and the text inside it. These will form the parameters of our function. The function will then perform the two steps we used to create the new element, and then return that element:

function createElement (tag,text) {    const el = document.createElement(tag);    el.textContent = text;    return el}

Let’s try it out by creating another new list item element:

const aquaman = createElement('li','Aquaman');

We can now create new elements in a single line of code rather than three. It’s time to add these new elements to our example page.

Adding Elements to the Page

We have already seen the appendChild() method. This can be called on a node to add a new child node. The new node will always be added at the end of any existing child nodes. The following example will add the flash element we created above to the end of the <ul> element, as shown below:

heroes.appendChild(flash);
Append child

The appendChild method is useful as you’ll often want to add a new element to the bottom of a list. But what if you want to place a new element in between two existing elements?

The insertBefore() method will place a new element before another element in the markup. It’s important to note that this method is called on theparent node. It takes two parameters: the first is the new node to be added, and the second is the node that you want it to go before (it’s helpful to think that the order of the parameters is the order they will appear in the markup). For example, we can place the aquaman element that we created earlier before the wonderWoman element with the following line of code:

heroes.insertBefore(aquaman,wonderWoman);

This will produce the output shown below.

Insert before

The appendChild() and insertBefore() methods can be used to move markup that already exists in the DOM as well. This is because a reference to a single DOM element can only exist once in the page, so if you use multiple inserts and appends, only the last one will have an effect. If an element is required to appear in several different places in the document, it would need to be cloned before each insertion.

This can be seen by using the appendChild() method on the wonderWoman element. Since it already exists, it just moves its position to appear at the end of the <ul> element, as shown below:

heroes.appendChild(wonderWoman);
Moving an existing node

Somewhat annoyingly, there is no insertAfter() method, so you need to ensure you have access to the correct elements to place an element exactly where you want it.

Remove Elements From a Page

An element can be removed from a page using the removeChild() method. This method is called on the parent node and has a single parameter, which is the node to be removed. It returns a reference to the removed node. For example, if we wanted to remove the aquaman element, we would use the following code:

heroes.removeChild(aquaman);<< <li>Aquaman</li>

As you can see below, it’s been removed.

Remove a child node

Because we have a reference to the element, we can easily put it back into the document if we need to:

heroes.appendChild(aquaman);

Replacing Elements on a Page

The replaceChild() method can be used to replace one node with another. It’s called on the parent node and has two parameters: the new node and the node that is to be replaced. For example, if we wanted to change the content of the <h1> tag that makes the title of the page, we could replace the text node with a new one, like so:

const h1 = document.getElementById('title');const oldText = h1.firstChild;const newText = document.createTextNode('Justice League of America');h1.replaceChild(newText,oldText);

the figure below shows that the text has now changed to ‘Justice League of America’.

Replacing an element

 innerHTML

The innerHTML element property was standardized as part of the HTML5, although it was already supported by all the major browsers. It returns all the child elements of an element as a string of HTML. If an element contains lots of other elements, all the raw HTML is returned. In the following example, we can see all the HTML that is contained inside the <ul> element with the id of roster :

heroes.innerHTML<<"    <li class=\"hero\">Superman</li>    <li class=\"vigilante hero\" id=\"bats\">Batman</li>    <li class=\"hero\">Wonder Woman</li>    "

The innerHTML property is also writable and can be used to place a chunk of HTML inside an element. This will replace all of a node’s children with the raw HTML contained in the string. This saves you having to create a new text node as it’s done automatically and inserted into the DOM. It’s also much quicker than using the standard DOM methods. For example, the heading text that we changed before could be changed in one line:

h1.innerHTML = 'Suicide Squad';

The power of the innerHTML property becomes even more apparent if you want to insert a large amount of HTML into the document. Instead of creating each element and text node individually, you can simply enter the raw HTML as a string. The relevant nodes will then be added to the DOM tree automatically. For example, we could change everything contained within the <ul> element:

heroes.innerHTML = '<li>Harley Quinn</li><li>Deadshot</li><li>Killer Croc</li><li>Enchantress</li><li>Captain Boomerang</li><li>Katana</li><li>Slipknot</li>';

This will now remove all the child elements of the <ul> element and replace them with the string of HTML that was provided, as shown in below.

The innerHTML property

Scripts Inserted Using innerHTML Won’t Run

To stop any malicious content being added to a page using innerHTML , any code contained within <script> tags is not executed.

Live Collections

The node lists returned by the document.getElementsByClassName() and document.getElementsByTagName() methods arelivecollections that will update to reflect any changes on the page. For example, if a new element with the class hero is added, or an existing one is removed, the node list updates automatically without having to make another call to the method. Therefore, its use is discouraged for performance reasons, but it can be useful.

To see an example of this, reload the page again to reset the DOM to its original state. Let’s take a look at how many elements are in the <ul> element:

const heroes = document.getElementById('roster'); const list = heroes.children;list.length << 3

Now remove the batman element:

const batman = document.getElementById('bats'); << undefinedheroes.removeChild(batman);  list.length;<< 2

You also need to be careful when referring to elements by their index in a collection, as this can change when markup is added or removed. For example, wonderWoman element could originally be accessed using this line of code:

heroes.children[2];<< undefined

Yet now it refers to undefined , as nothing is at the index of 2 in the collection; this is because the batman element was dynamically removed from the DOM, which means the index of the remaining elements will now change in the node list.

Updating CSS

Every element node has a style property. This can be used to dynamically modify the presentation of any element on a web page.

To see an example of this, reload the page again to reset the DOM. We can add a red border to the superman element with the following code:

const heroes = document.getElementById('roster');const superman = heroes.children[0];superman.style.border = "red 2px solid";<< "red 2px solid"

Camel Case Properties

Any CSS property names that are separated by dashes must be written in camelCase notation, so the dash is removed and the next letter is capitalized because dashes are not legal characters in property names.

For example, the CSS property background-color becomes backgroundColor . We can change the color of the superman background to green using this code:

superman.style.backgroundColor = 'blue';<< "blue"

Alternatively, the bracket notation that we saw in chapter 5 can also be used, meaning that CSS properties can be provided as a string and don’t need the camelCase notation:

superman.style['background color'] = 'blue';<< "blue"

You can see this change below.

Changing the style

Disappearing Act

One particularly useful CSS property often employed is the display property. This can be used to make elements disappear and reappear on the page as needed:

You can hide the superman element with the following code:

superman.style.display = 'none';<< "none"

You can see the effect below.

Hiding the elements

The element can be made to ‘reappear’ by changing the display property back to block :

superman.style.display = 'block';<< "block"

Checking Style Properties

The style property can also be used to see what CSS styles have been set on an element, but unfortunately it applies only to inline styles, and styles set using JavaScript. This means it excludes styles from external stylesheets, which is the most common way of setting styles.

There is a function called getComputedStyle() that will retrieve all the style information of an element that is given as a parameter. This is a read-only property, so is only used for finding out information about the style of an element.

For example, if you wanted all the styles applied to the superman element, you could use the following:

getComputedStyle(superman);<< CSSStyleDeclaration {0: "alt", 1: "animation-delay", 2: "animation-direction", 3: "animation-duration", 4: "animation-fill-mode", 5: "animation-iteration-count", 6: "animation-name", 7: "animation-play-state", 8: "animation-timing-function", 9: "background-attachment", …}

As you can see, it returns an object (more specifically, it is a CSSStyleDeclaration object) that contains a list of property-value pairs of all the CSS styles that have been applied to the element in question. In this example, there are over 200, although CSSStyleDeclaration objects have some built-in methods to help extract the information. For instance, if I wanted to find out about the element’s color property I could use this code in the console:

getComputedStyle(superman).getPropertyCSSValue('color').cssText;<< "rgb(0, 0, 0)"

This tells us that the color of the text is rgb(0, 0, 0) , which is black.

You can read more on the Mozilla Developer Networkabout the getComputedStyle() functionand about CSSStyleDeclaration objects.

This Will Cause An Error in Some Browsers

Some browsers, such as Chrome, do not allow access to the methods of a CSSStyleDeclaration object, such as getPropertyCSSValue() , so attempting to use them will result in an error.

Use with Caution

While it may seem useful to be able to edit the styles of elements on the fly like this, it is much better practice to dynamically change the class of an element and keep the relevant styles for each class in a separate stylesheet.

For example, if you wanted to add a red border around the superman element (to highlight it for some reason), you could do it in the way we saw earlier:

superman.style.border('red 2px solid');

A better alternative would be to add a class of ‘highlighted’:

superman.classList.add('highlighted');

And then add the following CSS in a separate stylesheet file:

.highlighted{    border: red 2px solid;}

This would give more flexibility if it was later decided to change the look of the highlighted elements. It could simply be changed at the CSS level, rather than having to dig around in the JavaScript code. There may be times, however, when you don’t have access to a stylesheet or its classes, so in this case you would have to change the CSS dynamically using JavaScript.

Quiz Ninja Project

Now we’ve learned about the Document Object Model, we can start to add some dynamic markup to display the questions in our quiz. This will mean we won’t need as many alert dialogs.

The first thing to do is add some empty <div> elements to the HTML by updating index.html to the following:

<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='description' content='A JavaScript Quiz Game'><title>Quiz Ninja</title><link rel='stylesheet' href='styles.css'></head><body><section class='dojo'>    <div class='quiz-body'>    <header>        <div id='score'>Score: <strong>0</strong></div>        <h1>Quiz Ninja!</h1>    </header>    <div id='question'></div>    <div id='result'></div>    <div id='info'></div>    </div></section><script src='main.js'></script></body>

We’ve added four <div> elements that will be used to show the questions and provide feedback about whether the user has answered a question correctly or not. We’ve also added a <div> element inside the <header> that can be used to display the score as the game is being played.

The ID attributes of these elements will act as hooks that allow us to easily gain access to that element using the document.getElementById() method. These will be namespaced inside an object called view , as they all relate to the view. Add the following code at the start of the main.js file, just after the array of questions:

// View Objectconst view = {    score: document.querySelector('#score strong'),    question: document.getElementById('question'),    result: document.getElementById('result'),    info: document.getElementById('info'),    render(target,content,attributes) {        for(const key in attributes) {            target.setAttribute(key, attributes[key]);        }        target.innerHTML = content;    }};

This uses the document.querySelector() method to access the elements we require and assign them to a variable. So, for example, the div with an id of question can be accessed in the Javascript code using view.question .

We’ve also added a helper function called render() that can be used to update the content of an element on the page. This function has three parameters: the first is the element that displays the content, the second is for the content it’s to be updated with, and the last is an object of any HTML attributes that can be added to the element.

The function loops through any attributes provided as the third argument, and uses the setAttribute() method to update them to the values provided. It then uses the innerHTML property to update the HTML with the content provided.

Now we need to update some of the functions inside the game object to use update the HTML.

We will still need to keep using dialogs for the time being, because without them, the JavaScript won’t stop running and the game would be unplayable. Don’t worry though, we won’t need them for much longer.

We’re going to update the HTML alongside showing the information. This means that the following methods need updating:

ask(){    const question = `What is ${this.question.name}'s real name?`;    view.render(view.question,question);    const response =  prompt(question);    this.check(response);},check(response){    const answer = this.question.realName;    if(response === answer){    view.render(view.result,'Correct!',{'class':'correct'});    alert('Correct!');    this.score++;    view.render(view.score,this.score);    } else {    view.render(view.result,`Wrong! The correct answer was ${answer}`,{'class':'wrong'});    alert(`Wrong! The correct answer was ${answer}`);    }},gameOver(){    view.render(view.info,`Game Over, you scored ${this.score} point${this.score !== 1 ? 's' : ''}`);}

In most cases we have placed a call to view.render() wherever there is an alert() or prompt() dialog that displays the same information in the HTML. We’ve also used the view.render() method to update the score if a player gains any points.

Unfortunately, if you have a go at playing the quiz by opening index.html in a browser, you won’t notice much difference until right at the end when all the dialogs have finished displaying. You’ll notice that the HTML has also been updating in the background, as seen in the screenshot below:

Current quiz

This is definitely starting to look better, although it would be good if we could see the results of our HTML updates all the way through the game. Don’t worry about this, though, because we’ll not be using prompts for much longer.

We have used our knowledge of the DOM to dynamically update the markup on the page. We’ve also continued to keep our code organized by keeping any properties and methods to do with the view in a separate object.

You can see a live example onCodePen.

Chapter Summary

  • The Document Object Model is a way of representing a page of HTML as a tree of nodes.
  • The document.getElementById()document.getElementsByClassName()document.getElementsByTagNames() and document.querySelector() can be used to access elements on a page.
  • The parentNode()previousSibling()nextSibling()childNodes() and children() methods can be used to navigate around the DOM tree.
  • An element’s attributes can be accessed using the getAttribute() method, and updated using the setAttribute() method.
  • The createElement() and createTextNode() methods can be used to create dynamic markup on the fly.
  • Markup can be added to the page using the appendChild() and insertBefore() methods.
  • Elements can be replaced using the replaceChild() method, and removed using the removeChild() method.
  • The innerHTML property can be used to insert raw HTML directly into the DOM.
  • The CSS properties of an element can be changed by accessing the style property.

Now we’ve learned how to navigate and dynamically update the markup of a web page, it’s time to start interacting with it. In the next chapter we’ll be covering a fundamental part of the JavaScript language: events.

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16