The DOM
sourceOpening any html page within the browser and opening up the devtools window, we can see the DOM by typing, document in the console and hitting return.
We can use this same call to document within our scripts to access elements upon the page. To achieve this there are a couple of steps, the first is to reach in to select the element that we wish to act upon, this is known as querying the DOM. The second step is to modify the content, for now we will look at the first step, retrieving an element.
DOM 01
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 01</title>
</head>
<body>
<h1 id="page-title">The DOM</h1>
<div>
<p>Hello World</p>
<p>Jabber wocky doin' jabbering</p>
<p class="error">this is an error message</p>
</div>
<div class="error">This div is an error too.</div>
<script src="dom_01.js"></script>
</body>
</html>
Step one: Querying the DOM
document.querySelector()
We will use document.querySelector() to store the targeted element in a variable. The query selector takes a css selector as a string, it will scan through the document until it finds the first selector that matches the request and return that.
DOM 01 JSlet data = document.querySelector("p"); console.log(data);
This gets us the first p tag in the document, but how would we get others if we require a later one? We can target also using the class, to achieve this we call the css class selector with a dot before the name.
let data = document.querySelector(".error"); console.log(data);
Now, if we would like to select the second div, which also has the class .error, how should we proceed. We can not use the .error class as that would select the first p tag that also has it and we can not use the div as it is not the first div either, we will have to combine them, we can do this by using "div.error".
let data = document.querySelector("div.error"); console.log(data);
When you are not sure what a selector for an element should be, you can always go to the browser and select the element on the page and right click it, and select "inspect", then in the devtools right click the now selected element and click upon copy css selector; This will copy the elements unique selector to the clipboard.
let data = document.querySelector("body > h1"); console.log(data);
document.querySelectorAll()
If we would like to select more than one elment, we use another method called querySelectorAll, again we give the css selector for the elements that we require. This will return us a NodeList of those selected elements, though similar to an array it is not quite the same thing, we have only a select few methods not all those that are available upon an array.
let paras = document.querySelectorAll("p"); console.log(paras);
We do have the [] selector for addressing individual list members, we also have the forEach method
// Select all paragraph tags.
console.log(paras[2]); // <p class="error">
paras.forEach(para => {
console.log(para); // <p>
// <p>
// <p class="error">
});
// Select all error class.
let errors = document.querySelectorAll(".error");
errors.forEach(error => {
console.log(error); // <p class="error">
// <div class="error">
});
document.getElementById()
We can also use id"s to individually select page elements, for this an id attribute must first be set upon the desired element, id"s must be unique and as such you can only use them once in any particular scope.
We do not need to use the "#" before the id as we would if searching for the id as a css selector, it is already implicit due to our use of this specific function.
let title = document.getElementById("page-title");
console.log(title); // <h1 id="page-title">
document.getElementsByClassName()
We can also select directly by class name:
errors = document.getElementsByClassName("error");
console.log(errors); // HTMLCollection {
// 0: p.error, 1: div.error, length: 2 }
This time we receive an HTMLCollection, this is similar but not identical to the NodeList that we have previously had returned to us. We can still target individual members of the collection with the square brackets notation [n], but we can not use the forEach method call. The differences between the NodeList and the HTMLCollection can be seen by opening up the tree in the console and selecting the prototype of each type their within.
document.getElementsByTagName()
let paras = document.getElementsByTagName("p"); console.log(paras); console.log(paras[0]);
Any of the elements that can be selected with any of the three methods just demonstrated, can also be selected using the querySelector that we saw first. The NodeList returned by the querySelector methods having the advantage of being interable with the forEach method.
Step two: Altering an Element
innerText
To alter the content of an element after selecting it we use a property called innerText, that will give us the text contained within an element. Setting this property to another value will change what is displayed by that element on the rendered page.
const para = document.querySelector("p"); console.log(para.innerText); // Hello World para.innerText = "Ferrets are fun!";
Combining this with our prior knowledge we can list out the content of a NodeList into the console, or, modifying each element like so:
const paras = document.querySelectorAll("p"); paras.forEach(para => { console.log(para.innerText); para.innerText += " the new bit."; });
innerHTML
We have seen now to change the inner text of an element, but what if we would like to change an elements HTML markup? For this we have the innerHTML property.
let content = document.querySelector(".content");
console.log(content.innerHTML);
content.innerHTML = "This is a new h1
"
For the next example, imagining that we have polled a database and received a list of people back from the query. What we are going to do is to output an HTML element for each of the people in the array.
const people = ["mario", "bob", "suzy"]
people.forEach(person => {
content.innerHTML += `${person}
`;
});
getAttribute
Using a new html page, we will this time seek to effect the href attribute within an anchor tag element. For this we will use the getAttribute method.
DOM 02
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 01</title>
</head>
<body>
<h1 style="color: orange;">The DOM</h1>
<a href="http://google.com">link to somewhere.</a>
<p class="error">This is and error.</p>
<script src="dom_02.js"></script>
</body>
</html>
Using the following script we can read the href attributes value. We can also set attributes using the method setAttribute.
let link = document.querySelector("a"); console.log(link.getAttribute("href")); // http://googly.com link.setAttribute("href", "wibble.com"); link.innerText = "Go to wibble";
We can also retrieve the names of any classes that have been applied by retrieving the class attribute.
let msg = document.querySelector("p"); console.log(msg.getAttribute("class")); msg.setAttribute("class", "success");
We can also set new atributes upon elements that exist in the html, it does not matter that the attribute does not yet exist upon the tag, javascript will create it.
msg.setAttribute("style", "color: green;");
There is one major draw back to setting attributes in this way. When we add to an attribute that already exists, such as the orange colour in our h1 heading, then that pre existing style is completely overwritten.
let title = document.querySelector("h1"); title.setAttribute("style", "margin: 50px");
The style property of an html element can be addressed in another way, the style attribute is a property upon the element and as such can be addressed directly instead.
console.log(title.style); title.style.color = orange;
When the attribute that we wish to address contains a hyphen, such as font-size, we use camel case to call them, fontSize.
title.style.fontSize = "2rem";
Attributes can be deleted by setting them to an empty string.
title.style.margin = "";
To demonstrate how we can add and remove classes to HTML tags we will use a new DOM file, and this time also a separate css stylesheet.
DOM 03
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 03</title>
<link rel="stylesheet" href="dom_03.css">
</head>
<body>
<h1>The DOM 03</h1>
<p class="error">
rþin ualan ineunidil sea trgebn hmabgasr hoouðinsók dyr
þhanniefee etrenin soit þoebsoro tlim elmoodh yþidnð fa
eðe euttiw ȝlloho thess ranw
</p>
<script src="dom_03.js"></script>
</body>
</html>
CSS
.error { padding: 10px; color: crimson; border: 1px dotted crimson; } .success { padding: 10px; color: limegreen; border: 1px dotted limegreen; }
classList.add classList.remove
In this new html document we have a paragraph element with no classes on it, we again access this element document.querySelector the returned content can be examined for its classes using the classList property.
const content = document.querySelector("p");
console.log(content.classList); // DOMTokenList [ ]
We can add classes to the classList of the element by calling the add method upon it.
content.classList.add("error"); console.log(content.classList); // DOMTokenList [ "error" ]
We can also remove classes using the remove method.
content.classList.remove("error"); console.log(content.classList); // DOMTokenList [ ]
... And finally adding our success class:
content.classList.add("success");
A little test
This next document has a many paragraph tag elements inside of it, all with a string of text, the challenge is to find all those paragraphs that contain the word "error", and then set the error class upon them, and all that contain the word "success" and set the success class upon those.
DOM 04
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOM 04</title>
<link rel="stylesheet" href="dom_03.css">
</head>
<body>
<h1 class="title">The DOM 04</h1>
<p>this <span style="display: none";>error</span> and that</p>
<p>the success was final</p>
<p>not a chair but a table</p>
<p>then againg there could be success</p>
<p>error he stated!</p>
<p>what knot not that ought but nought forgot</p>
<p>the tide change but that was an error</p>
<p>once the dust settles, success will be</p>
<script src="dom_04.js"></script>
</body>
</html>
Here is a script that we might write to solve this problem.
let paras = document.querySelectorAll("p"); paras.forEach(para => { if (para.innerText.indexOf("error") >= 0) { para.classList.add("error"); } if (para.innerText.indexOf("success") >= 0) { para.classList.add("success"); } });
Our solution here is not ideal, it is not clear that the 0 value of the index when a text begins with the search term that is being sought, needs to be included in the condition.
textContent
The innnerText returns all visible text from the element, but there is another method that will return the text, called textContent. The difference between the tow is that innerText will not return text that is hidden where as textContent will. I have added a span across one of the errors in the above markup and as such the first paragraph is no longer receiving the error style, changing our property to textContent will fix this.
textContent.includes()
We can also use the includes() method on either of the properties, rather than looking and testing the position of the index of our search term.
let paras = document.querySelectorAll("p"); paras.forEach(para => { if (para.textContent.includes("error")) { para.classList.add("error"); } if (para.textContent.includes("success")) { para.classList.add("success"); } });
toggle
Next we will see how to toggle a class, that is to say that if the class is not present upon the element concerned, then the class is added, if it is found then it is removed.
const title = document.querySelector(".title"); title.classList.toggle("test");