The document object model in JavaScript: Finding and changing document

1. Finding Elements

Navigating these links among parents, children, and siblings is often useful. But if we want to find a specific node in the document, reaching it by start­ing at document.body and following a fixed path of properties is a bad idea. Doing so bakes assumptions into our program about the precise structure of the document—a structure you might want to change later. Another complicating factor is that text nodes are created even for the whitespace between nodes. The example document’s <body> tag does not have just three children (<hi> and two <p> elements) but actually has seven: those three, plus the spaces before, after, and between them.

So if we want to get the href attribute of the link in that document, we don’t want to say something like “Get the second child of the sixth child of the document body.” It’d be better if we could say “Get the first link in the document.” And we can.

let link = document.body.getElementsByTagName(“a”)[0];

console.log(link.href);

All element nodes have a getElementsByTagName method, which collects all elements with the given tag name that are descendants (direct or indirect children) of that node and returns them as an array-like object.

To find a specific single node, you can give it an id attribute and use document.getElementById instead.

<p>My ostrich Gertrude:</p>

<p><img id=”gertrude” src=”img/ostrich.png”></p>

<script>

let ostrich = document.getElementById(“gertrude”);

console.log(ostrich.src);

</script>

A third, similar method is getElementsByClassName, which, like getElementsByTagName, searches through the contents of an element node and retrieves all elements that have the given string in their class attribute.

2. Changing the Document

Almost everything about the DOM data structure can be changed. The shape of the document tree can be modified by changing parent-child relationships. Nodes have a remove method to remove them from their cur­rent parent node. To add a child node to an element node, we can use appendChild, which puts it at the end of the list of children, or insertBefore, which inserts the node given as the first argument before the node given as the second argument.

<p>One</p>

<p>Two</p>

<p>Three</p>

<script>

let paragraphs = document.body.getElementsByTagName(“p”);

document.body.insertBefore(paragraphs[2], paragraphs[0]);

</script>

A node can exist in the document in only one place. Thus, inserting paragraph Three in front of paragraph One will first remove it from the end of the document and then insert it at the front, resulting in Three/ One/ Two. All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).

The replaceChild method is used to replace a child node with another one. It takes as arguments two nodes: a new node and the node to be replaced. The replaced node must be a child of the element the method is called on. Note that both replaceChild and insertBefore expect the new node as their first argument.

3. Creating Nodes

Say we want to write a script that replaces all images (<img> tags) in the docu­ment with the text held in their alt attributes, which specifies an alternative textual representation of the image.

This involves not only removing the images but adding a new text node to replace them. Text nodes are created with the document.createTextNode method.

<p>The <img src=”img/cat.png” alt=”Cat”> in the

<img src=”img/hat.png” alt=”Hat”>.</p>

<p><button onclick=”replaceImages()”>Replace</button></p>

<script>

function replaceImages() {

let images = document.body.getElementsByTagName(“img”);

for (let i = images.length – 1; i >= 0; i–) {

let image = images[i];

if (image.alt) {

let text = document.createTextNode(image.alt);

image.parentNode.replaceChild(text, image);

}

}

}

</script>

Given a string, createTextNode gives us a text node that we can insert into the document to make it show up on the screen.

The loop that goes over the images starts at the end of the list. This is necessary because the node list returned by getElementsByTagName (or a prop­erty like childNodes) is live. That is, it is updated as the document changes. If we started from the front, removing the first image would cause the list to lose its first element so that the second time the loop repeats, where i is 1, it would stop because the length of the collection is now also 1.

If you want a solid collection of nodes, as opposed to a live one, you can convert the collection to a real array by calling Array.from.

let arrayish = {0: “one”, 1: “two”, length: 2};

let array = Array.from(arrayish);

console.log(array.map(s => s.toUpperCase()));

// → [“ONE”, “TWO”]

To create element nodes, you can use the document.createElement method. This method takes a tag name and returns a new empty node of the given type.

The following example defines a utility elt, which creates an element node and treats the rest of its arguments as children to that node. This func­tion is then used to add an attribution to a quote.

<blockquote id=”quote”>

No book can ever be finished. While working on it we learn just enough to find it immature the moment we turn away from it.

</blockquote>

<script>

function elt(type, …children) {

let node = document.createElement(type); for (let child of children) {

if (typeof child != “string”) node.appendChild(child);

else node.appendChild(document.createTextNode(child));

}

return node;

}

document.getElementById(“quote”).appendChild( elt(“footer”, “–“,

elt(“strong”, “Karl Popper”),

“, preface to the second editon of “, elt(“em”, “The Open Society and Its Enemies”),

“, 1950”));

</script>

This is what the resulting document looks like:

No book can ever be finished. While working on it we learn just enough to find it immature the moment we turn away from it.

—Karl Popper, preface to the second editon of The Open Society and Its Enemies, 1950

4. Attributes

Some element attributes, such as href for links, can be accessed through a property of the same name on the element’s DOM object. This is the case for most commonly used standard attributes.

But HTML allows you to set any attribute you want on nodes. This can be useful because it allows you to store extra information in a document. If you make up your own attribute names, though, such attributes will not be present as properties on the element’s node. Instead, you have to use the getAttribute and setAttribute methods to work with them.

<p data-classified=”secret”>The launch code is 00000000.</p>

<p data-classified=”unclassified”>I have two feet.</p>

<script>

let paras = document.body.getElementsByTagName(“p”);

for (let para of Array.from(paras)) {

if (para.getAttribute(“data-classified”) == “secret”) { para.remove();

}

}

</script>

It is recommended to prefix the names of such made-up attributes with data- to ensure they do not conflict with any other attributes.

There is a commonly used attribute, class, which is a keyword in the JavaScript language. For historical reasons—some old JavaScript imple­mentations could not handle property names that matched keywords—the property used to access this attribute is called className. You can also access it under its real name, “class”, by using the getAttribute and setAttribute methods.

Source: Haverbeke Marijn (2018), Eloquent JavaScript: A Modern Introduction to Programming, No Starch Press; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *