Chapter 13
The Document Object Model

When you open a web page in your browser, it will retrieve the page’s HTML text, and parse it much like our parser from Chapter 11 parses programs, building up a model of its structure. It will then use this model to draw the page onto the screen.

One of the toys that a JavaScript program has available in its sandbox is this representation of the document. We can read from it, but also change it. It acts as a live data structure—when it is modified, the page on the screen is updated to reflect the changes.

Document structure

An HTML document can be visualized as a nested set of boxes. Tags like <body> and </body> enclose other tags, which in turn contain other tags (or text). Here’s the example document one more time:

<!doctype html>
<html>
  <head>
    <title>My home page</title>
  </head>
  <body>
    <h1>My home page</h1>
    <p>Hello, I am Marijn and this is my home page.</p>
    <p>I also wrote a book! Read it
      <a href="http://eloquentjavascript.net">here</a>.</p>
  </body>
</html>

This page has the following structure:

HTML document as nested boxes

The data structure the browser uses to represent the document follows this shape. For each box, there is an object, which we can interact with to find out things like what HTML tag it represents, and which boxes and text it contains. This representation is called the Document Object Model, DOM for short.

The global variable document gives us access to these objects. Its documentElement property refers to the object representing the <html> tag. It also provides properties head and body, holding the objects for those elements.

Trees

Think back to the syntax trees from Chapter 11 for a moment. Their structure is strikingly similar to the structure of a browser’s document. Each node may refer to other nodes, children, which may have their own children. This shape is typical of nested structures where elements can contain sub-elements that are similar to themselves.

We call a data structure a tree when it has a branching structure, contains no cycles (a node may not contain itself, directly or indirectly), and has a single, well-defined “root”.

Trees come up a lot in computer science. Apart from representing recursive structures like HTML documents or programs, they are also often used to maintain sorted sets of data, because elements can typically be found or inserted more efficiently in a sorted tree than in a sorted flat array.

A typical tree has different kinds of nodes. The syntax tree had variables, values, and application nodes. Application nodes always had children, variables and values were leaves, nodes without children.

The same goes for the DOM. Nodes for regular elements (the representation of a tag in the document) determine the structure of the document. These can have child nodes. An example of such a node is document.body. Some of these children can be leaf nodes, such as pieces of text or comments (which are written between <!-- and --> in HTML).

Each DOM node object has a nodeType property, which contains a number code that identifies the type of node. Regular nodes have the value 1 (which is also defined as the constant property document.ELEMENT_NODE). Text nodes, representing a section of text in the document, get type 3 (document.TEXT_NODE). Comments get type 8 (document.COMMENT_NODE).

So another way to visualize our document tree is:

HTML document as a tree

The leaves are text nodes, and the arrows indicate parent-child relationships between nodes.

The standard

Using cryptic number codes to represent node types is not a very JavaScript-like thing to do. Further on in this chapter, we’ll see that other parts of the DOM interface also feel cumbersome and alien. The reason for this is that the DOM wasn’t designed for just JavaScript, but rather tries to define a language-neutral interface that can be used in other systems as well, not just for HTML, but also for XML, which is a generic data format with an HTML-like syntax.

This is unfortunate. Standards are often useful. But in this case, the advantage (cross-language consistency) isn’t all that compelling. And the downside, having an interface that’s not well integrated with the language, is costly.

As an example of such poor integration, consider the childNodes property that element nodes in the DOM have. This property holds an array-like object, with a length property and properties labeled by numbers (0, 1) to access the child nodes. But it is an instance of the NodeList type, not a real array, so it does not have methods like slice and forEach.

Then there are issues that are simply the result of poor design. For example, there is no way to create a new node and immediately add children or attributes to it. Instead, you have to first create it, then add the children one by one, and set the attributes one by one. Code that interacts heavily with the DOM tends to get very long, repetitive, and ugly.

But none of these are fatal, since JavaScript allows us to create our own abstractions. It is easy to write some helper functions that allow you to express the operations you are performing in a clearer and shorter way. In fact, many libraries intended for browser programming come with such tools.

Moving through the tree

DOM nodes contain a wealth of links to other, nearby nodes. The following diagram tries to illustrate these.

Links between DOM nodes

Every node has a parentNode property, pointing to the node it is part of. The diagram only shows one of each link type. Every element node (node type 1) has a childNodes property that points to an array-like object holding its children. Those represent the fundamental structure of the tree.

In addition, there are a number of convenience links. The firstChild and lastChild properties point to the first and last child element, or have the value null for nodes without children. Similarly, previousSibling and nextSibling point to adjacent nodes, nodes with the same parent that appear immediately before or after the node itself. For a first child, previousSibling will be null, and for a last child, nextSibling is null.

When dealing with a data structure like this, whose structure repeats itself as we go deeper, recursive functions are often useful. The one below scans a document for text nodes containing a given string, and returns true when it has found one.

function talksAbout(node, string) {
  if (node.nodeType == document.ELEMENT_NODE) {
    for (var i = 0; i < node.childNodes.length; i++) {
      if (talksAbout(node.childNodes[i], string))
        return true;
    }
    return false;
  } else if (node.nodeType == document.TEXT_NODE) {
    return node.nodeValue.indexOf(string) > -1;
  }
}

console.log(talksAbout(document.body, "book"));
// → true

The nodeValue property of a text node refers to the string of text that it represents.

Finding elements

Navigating these links to parents, children, and siblings is often useful, as in the function above, which blindly runs through the whole document. But if we want to find a specific node in the document, reaching it by starting at document.body and following the various kinds of links is a bad idea. It ties assumptions about the precise structure of the document into our program, and we might want to change that structure later. Another complicating factor is that text nodes are created even for the whitespace (newlines and spaces) between nodes. The example document’s body tag does not have just three children (<h1> and two <p>’s), but actually has 7 (those three, plus the space 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.

var 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 the given 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>
  var 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.

Changing the document

Almost everything about the DOM data structure can be changed. Element nodes have a number of methods that can be used to change their content. The removeChild method removes the given child node from the document. To add a child, we can use appendChild, which puts it at the end of the list of children, or insertBefore, which inserts the node given as first argument before the node given as second argument.

<p>One</p>
<p>Two</p>
<p>Three</p>

<script>
  var paragraphs = document.body.getElementsByTagName("p");
  document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>

A node can only exist in the document in one place. That is why 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 that must be replaced, which 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.

Creating nodes

In the next example, we want to write a script that replaces all images (<img> tags) in the document with the text held in their alt attribute (which is intended for specifying an alternative textual representation of the image).

This involves not only removing the images, but adding a new text node to replace them. For this, we use 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() {
    var images = document.body.getElementsByTagName("img");
    for (var i = images.length - 1; i >= 0; i--) {
      var image = images[i];
      if (image.alt) {
        var text = document.createTextNode(image.alt);
        image.parentNode.replaceChild(text, image);
      }
    }
  }
</script>

Given a string, createTextNode gives us a type 3 DOM node, which 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 of nodes. This is necessary because a node list returned by a method like getElementsByTagName (or a property like childNodes) is live—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 one, it would stop, because the length of the collection is now also one.

If you want a solid collection of nodes, that doesn’t unexpectedly change, you can convert them to a real array by calling the array slice method on it.

var arrayish = {0: "one", 1: "two", length: 2};
var real = Array.prototype.slice.call(arrayish, 0);
real.forEach(function(elt) { console.log(elt); });
// → one
//   two

To create regular element nodes (type 1), 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 function is then used to add a simple 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) {
    var node = document.createElement(type);
    for (var i = 1; i < arguments.length; i++) {
      var child = arguments[i];
      if (typeof child == "string")
        child = document.createTextNode(child);
      node.appendChild(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>

Attributes

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

But HTML allows you to set any attribute you want on nodes. This can be useful, as it allows you to store extra information for your scripts in a document. If you make up your own attribute names, though, such attributes will not be present as a property on the element’s node. Instead, you’ll 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>
  var paras = document.body.getElementsByTagName("p");
  Array.prototype.forEach.call(paras, function(para) {
    if (para.getAttribute("data-classified") == "secret")
      para.parentNode.removeChild(para);
  });
</script>

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

As a simple example, we’ll write a “syntax highlighter” that looks for <pre> tags (“pre-formatted”, used for code and similar plain text) with a data-language attribute, and crudely tries to highlight the keywords for that language.

function highlightCode(node, keywords) {
  var text = node.textContent;
  node.textContent = ""; // Clear the node

  var match, pos = 0;
  while (match = keywords.exec(text)) {
    var before = text.slice(pos, match.index);
    node.appendChild(document.createTextNode(before));
    var strong = document.createElement("strong");
    strong.appendChild(document.createTextNode(match[0]));
    node.appendChild(strong);
    pos = keywords.lastIndex;
  }
  var after = text.slice(pos);
  node.appendChild(document.createTextNode(after));
}

The function highlightCode takes a <pre> node and a regular expression (with the “global” option turned on) that matches the keywords of the programming language that the element contains.

The textContent property is used to get all the text in the node, and is then set to an empty string, which has the effect of emptying the node. We loop over all matches of the keyword expression, appending the text between them as regular text nodes, and the text matched as text nodes wrapped in <strong> (bold) elements.

We can automatically highlight all programs on the page by looping over the <pre> elements that have a data-language attribute, and calling highlightCode on them with the correct regular expression for the language.

var languages = {
  javascript: /\b(function|return|var)\b/g /* … etc */
};

function highlightAllCode() {
  var pres = document.body.getElementsByTagName("pre");
  for (var i = 0; i < pres.length; i++) {
    var pre = pres[i];
    var lang = pre.getAttribute("data-language");
    if (languages.hasOwnProperty(lang))
      highlightCode(pre, languages[lang]);
  }
}

For example:

<p>Here it is, the identity function:</p>
<pre data-language="javascript">
function id(x) { return x; }
</pre>

<script>highlightAllCode();</script>

There is one commonly used attribute, class, which is a reserved word in the JavaScript language. For historical reasons—some old JavaScript implementation could not handle property names that matched keywords or reserved words—the property used to access this attribute is called className. You can, of course, also access it with the getAttribute and setAttribute methods.

Layout

You will have noticed that different types of elements behave differently. Some, such as paragraphs (<p>) or headings (<h1>), are placed below each other, taking up the whole width of the document. These are called block elements. Others, such as links (<a>) or the <strong> element used in the example above, are treated as part of the surrounding text. Such elements are called inline elements.

Browsers contain a component that, given a document, computes a layout, which gives each element a size and position based on its type and content. This layout is then used to actually draw the document.

The size and position of an element can be accessed from JavaScript. The offsetWidth and offsetHeight properties give us the space the element takes up, in pixels (the basic unit of measurement in the browser, typically corresponding to the smallest dot that your screen can display). Similarly, clientWidth and clientHeight give us the size of the inside of the element, ignoring border width.

<p style="border: 3px solid red">
  I'm boxed in
</p>

<script>
  var para = document.body.getElementsByTagName("p")[0];
  console.log("clientHeight:", para.clientHeight);
  console.log("offsetHeight:", para.offsetHeight);
</script>

The most effective way to find the precise position of an element on the screen is the getBoundingClientRect method. It returns an object with top, bottom, left, and right properties indicating the pixel positions of the sides of the element, relative to the top left of the screen. If you want them relative to the whole document, you must add the current scroll position, found under the global pageXOffset and pageYOffset variables.

Laying out a document can be quite a lot of work. In the interest of speed, browser engines do not immediately re-layout a document every time it is changed, but rather wait as long as they can. When a JavaScript program finishes running, they will have to show the changes to the document on the screen, which involves computing the layout. When a program asks for the position or size of something (by reading properties like offsetHeight or calling getBoundingClientRect), providing correct information also requires computing a layout.

A program that repeatedly alternates between reading DOM layout information and changing the DOM will force a lot of layouts to happen, and consequently run really slowly. This page demonstrates the difference. It contains two different programs that build up a line of “X” characters 2000 pixels wide, and measures the time they take.

<p><span id="one"></span></p>
<p><span id="two"></span></p>

<script>
  function time(name, action) {
    var start = Date.now(); // Current time in milliseconds
    action();
    console.log(name, "took", Date.now() - start, "ms");
  }

  time("naive", function() {
    var target = document.getElementById("one");
    while (target.offsetWidth < 2000)
      target.appendChild(document.createTextNode("X"));
  });
  // → naive took 32 ms

  time("clever", function() {
    var target = document.getElementById("two");
    target.appendChild(document.createTextNode("XXXXX"));
    var total = Math.ceil(2000 / (target.offsetWidth / 5));
    for (var i = 5; i < total; i++)
      target.appendChild(document.createTextNode("X"));
  });
  // → clever took 1 ms
</script>

Styling

We have seen a number of different elements displaying different behavior. Some are displayed as blocks, others inline. Some add styling, like <strong> making its content bold, and <a> making it blue and underlining it.

Some of this behavior is strongly tied to the element type. For example the way an <img> tag shows an image or an <a> tag causes a link to be followed when it is clicked. Other behavior, such as boldness or color, are simply default styles that the browser assigns to the element, and can be changed by us. For example by using the style property.

<p><a href=".">Normal link</a></p>
<p><a href="." style="color: green">Green link</a></p>

A style attribute may contain one or more declarations, which are a property (such as color) followed by a colon and a value (such as green). When there are more than one declaration, they must be separated by semicolons. For example “color: red; border: none”.

There are a lot aspects that can be influenced with styling. For example, whether an element is displayed as a block or inline is controlled by the display property.

This text is displayed <strong>inline</strong>,
<strong style="display: block">as a block</strong>, and
<strong style="display: none">not at all</strong>.

The block tag will end up on its own line, since block elements are not displayed inline with the text around them. The last tag is not displayed at all—display: none prevents an element from showing up on the screen. This is a way to hide elements, and it is often preferable to removing them from the document entirely—it makes it easy to reveal them again at a later time.

The style of an element can be manipulated directly by a JavaScript program through the node’s style property. This property holds an object that has properties for all possible style properties. The values of these properties are strings, which we can write to in order to change a particular aspect of the element’s style.

<p id="para" style="color: purple">
  Pretty text
</p>

<script>
  var para = document.getElementById("para");
  console.log(para.style.color);
  para.style.color = "magenta";
</script>

Some style property names contain dashes, for example font-family. Because such property names are awkward to work with in JavaScript (you’d have to say style["font-family"]), the property names in the style object for such properties have their dashes removed, and the letter that follows them capitalized (style.fontFamily).

Cascading styles

The styling system for HTML is called CSS, for Cascading Style Sheets. A style sheet is a set of rules for how to style elements in the document. It can be given inside a <style> tag.

<style>
  strong {
    font-style: italic;
    color: grey;
  }
</style>
<p>Now <strong>strong text</strong> is italic and grey.</p>

The cascading in the name refers to the fact that multiple such rules are combined to produce the actual style for an element. In the example above, the default styling for <strong> tags, which gives them font-weight: bold, is overlaid by the rule in the <style> tag, which adds font-style and color.

When multiple rules define a value for the same property (for example if the rule in the <style> tag included font-weight: normal, disagreeing with the default font-weight rule), the most recently read rule gets a higher precedence and wins out (so in this case, the text would not be bold). Styles in a style attribute applied directly to the node have the highest precedence, and always win.

It is possible to target things other than tag names in CSS rules. A rule for .abc applies to all elements with "abc" in their class attribute. A rule for #xyz applies to the element with an id attribute of "xyz".

.subtle {
  color: grey;
  font-size: 80%;
}
#header {
  background: blue;
  color: white;
}
/* p elements, with classes a and b, and id main */
p.a.b#main {
  margin-bottom: 20px;
}

The precedence rule favoring the most recently defined rule only holds when the rules have the same specificity. Specificity depends on the amount of aspects of the tag that the rule matches. Simply said, p.a is more specific than just p or just .a, and thus would take precedence to them.

The notation p > a {…} applies the given styles to all <a> tags that are direct children of <p> tags. Similarly, p a {…} applies to all <a> tags inside <p> tags, whether they are direct or indirect children.

Query selectors

We won’t be using style sheets all that much in this book. Understanding them is crucial to programming in the browser, but properly explaining all properties they support, and the interaction between those, would take three or four books itself.

The main reason I introduced selector syntax—the notation used in style sheets to determine which elements a set of styles apply to—is that we can use this same mini-language as a more effective way to find DOM elements.

The querySelectorAll method (defined both on the document object and on element nodes) takes a selector string, and returns an array-like object containing all the elements that it matches.

<p>And if you go chasing
  <span class="animal">rabbits</span></p>
<p>And you know you're going to fall</p>
<p>Tell 'em a <span class="character">hookah smoking
  <span class="animal">caterpillar</span></span></p>
<p>Has given you the call</p>

<script>
  function count(selector) {
    return document.querySelectorAll(selector).length;
  }
  console.log(count("p"));           // All <p> elements
  // → 4
  console.log(count(".animal"));     // Class animal
  // → 2
  console.log(count("p .animal"));   // Animal inside of <p>
  // → 2
  console.log(count("p > .animal")); // Direct child of <p>
  // → 1
</script>

The querySelector method (without the All part) works in a similar way. This one is useful if you want a specific, single element. It will return only the first matching element, or null if no elements match.

Positioning and animating

The style property called position influences layout in a powerful way.

By default it has a value of static, meaning the element sits in its normal place in the document. When it is set to relative, the element still takes up space in the document, but the top and left style properties can be used to move it relative to its normal place. When position is set to absolute the element is removed from the normal document flow (it no longer takes up space, but will overlap with other elements), and its top and left properties can be used to absolutely position it (relative to the top left of the document, or the nearest enclosing element whose position property isn’t static).

We can use this to create an animation. The document below displays a spinning picture of a cat.

<p style="text-align: center">
  <img src="img/cat.png" style="position: relative">
</p>
<script>
  var cat = document.querySelector("img");
  var angle = 0, lastTime = null;
  function animate(time) {
    if (lastTime != null)
      angle += (time - lastTime) * 0.001;
    lastTime = time;
    cat.style.top = (Math.sin(angle) * 20) + "px";
    cat.style.left = (Math.cos(angle) * 200) + "px";
    requestAnimationFrame(animate);
  }
  requestAnimationFrame(animate);
</script>

First, the picture is centered on the page, and given a position of relative. We’ll repeatedly update that picture’s top and left styles in order to move it.

The script uses requestAnimationFrame to schedule a function animate to run whenever the browser is ready to repaint the screen. The animate function itself again calls requestAnimationFrame to schedule the next update. When the browser window (or tab) is active, this will cause updates to happen at a rate of about 60 per second, which tends to produce a good-looking animation.

If we just updated the DOM in a loop, the page would freeze and nothing would show up on the screen. Browsers do not update their display (nor do they allow any interaction with the page) while a JavaScript program is running. This is why we need requestAnimationFrame—it lets the browser know that we are done for now, and it can go ahead and do the things that browsers do, such as updating the screen and responding to user actions.

Our animation function is passed the current time as argument, which it compares to the time it saw before (the lastTime variable) to ensure the motion of the cat per millisecond is stable, resulting in a smooth animation. If it would just move a fixed amount each step, the motion would look stuttery when, for example, some other heavy task running on the same computer prevented the function from running for a fraction of a second.

Moving in circles is done using the trigonometry functions Math.cos and Math.sin. If you are not familiar with those, I’ll try to briefly introduce them, since we will occasionally need them in this book.

Math.cos and Math.sin are useful for finding points that lie on a circle around point (0,0), with each point being exactly one unit away from (0,0). They interpret their argument as the position on this circle, with zero denoting the point on the far right of the circle, going clockwise until 2π (about 6.28) has taken us around the whole circle. Math.cos tells you the x coordinate of the point that corresponds to the given angle, Math.sin yields the y coordinate. Positions (angles) higher than 2π or below 0 are valid—the rotation repeats, so that a+2π refers to the same angle as a.

Using cosine and sine to compute coordinates

The example keeps a counter (angle) for the current angle of the animation, and increments this counter in proportion to the elapsed time every time our animation function is called. It can then use this angle to compute the current position of the image element. The top style is computed with Math.sin, and multipled by 20, which is the vertical radius of our circle. The left style is based on Math.cos, and multipled by 200, so that the circle is much wider than it is high, resulting in an elliptic motion.

Note that styles usually need units. In this case, we have to append "px" to the number to tell the browser we are counting in pixels (as opposed to centimeters, “ems”, or other units). This is easily forgotten. Using numbers without unit will result in your style being ignored (unless the number is 0, which means the same regardless of its unit).

Summary

JavaScript programs may inspect and interfere with the current document that a browser is displaying through a data structure called the DOM. This data structure represents the browser’s model of the document, and a JavaScript program can modify it to change the visible document.

The DOM is shaped like a tree, in which elements are arranged hierarchically according to the structure of the document. The objects representing elements have properties like parentNode and childNodes, which can be used to navigate through this tree.

The way a document is displayed can be influenced by styling, both by attaching styles to nodes directly, and by defining rules that match certain nodes. There are many different properties, such as color or display, that can be set. JavaScript can manipulate an element’s style directly through its style property.

Exercises

Build a table

We built plain-text tables in Chapter 6. HTML makes laying out tables quite a bit easier. An HTML table is built with the following tag structure:

<table>
  <tr>
    <th>name</th>
    <th>height</th>
    <th>country</th>
  </tr>
  <tr>
    <td>Kilimanjaro</td>
    <td>5895</td>
    <td>Tanzania</td>
  </tr>
</table>

For each row, the <table> tag contains a <tr> tag. Inside of these, we can put cell elements, either heading cells (<th>) or regular cells (<td>).

The same source data that was used in Chapter 6 is again available in the MOUNTAINS variable in the sandbox, and also downloadable from the list of data sets on the website(!tex (eloquentjavascript.net/code)!).

Write a function buildTable that, given an array of objects (which all have the same set of properties), builds up a DOM structure representing a table, with the property names wrapped in <th> elements on the top row, followed by one row per object, containing the property values in <td> elements.

The Object.keys function, which returns an array containing the property names that an object has, will probably be helpful again.

Once you have the basics working, right-align cells containing numbers by setting their style.textAlign property to "right".

<style>
  /* Defines a cleaner look for tables */
  table  { border-collapse: collapse; }
  td, th { border: 1px solid black; padding: 3px 8px; }
  th     { text-align: left; }
</style>

<script>
  function buildTable(data) {
    // Your code here.
  }

  document.body.appendChild(buildTable(MOUNTAINS));
</script>

Use document.createElement to create new element nodes, document.createTextNode to create text nodes, and the appendChild method to put nodes into other nodes.

The key names should be looped over once to fill in the top row, and then once again for each object in the array to construct the data rows.

Don’t forget to return the enclosing <table> element at the end of the function.

Elements by tag name

The getElementsByTagName method returns all child elements with a given tag name. Implement your own version of it, as a regular non-method function, which takes a node and a string (the tag name) as arguments, and returns a (real) array containing all descendant element nodes with the given tag name.

Note that the tagName property, for normal documents, will return the tag name in all upper case. Use the toLowerCase or toUpperCase string method to compensate for this.

<h1>Heading with a <span>span</span> element.</h1>
<p>A paragraph with <span>one</span>, <span>two</span>
  spans.</p>

<script>
  function byTagName(node, tagName) {
    // Your code here.
  }

  console.log(byTagName(document.body, "h1").length);
  // → 1
  console.log(byTagName(document.body, "span").length);
  // → 3
  var para = document.querySelector("p");
  console.log(byTagName(para, "span").length);
  // → 2
</script>

This is most easily expressed with a recursive function, similar to the talksAbout function defined earlier in this chapter.

You could call byTagname itself recursively, concatenating the resulting arrays to produce the output. For a more efficient approach, define an inner function which calls itself recursively, and which has access to an array variable defined in the outer function to which it can add the matching elements it finds. Do not forget to call the inner function once from the outer function.

The recursive function must check the node type. In this case, we are only interested in node type 1 (document.ELEMENT_NODE). For such nodes, we must loop over their children, and for each child, see if it matches the query, but also do a recursive call on it to inspect its own children.

The cat’s hat

Extend the spinning cat animation we defined earlier to have both the cat and his hat (<img src="img/hat.png">) spin around, at opposite sides of the circle.

Or make the hat circle around the cat. Or alter the animation in some other interesting way.

To make positioning multiple objects easier, it is probably a good idea to switch to absolute positioning. This means that top and left are counted relative to the top left of the document, so you probably want to avoid using negative coordinates. This can simply be done by adding a fixed number of pixels to the values.

<img src="img/cat.png" id="cat" style="position: absolute">
<img src="img/hat.png" id="hat" style="position: absolute">

<script>
  var cat = document.querySelector("#cat");
  var hat = document.querySelector("#hat");
  // Your code here.
</script>