Course: JavaScript

Progress (0%)

HTML DOM - Elements

Chapter 47 43 mins

Learning outcomes:

  1. What is the Element interface
  2. The children property
  3. The firstElementChild and lastElementChild properties
  4. The nextElementSibling and previousElementSibling properties
  5. The innerHTML property
  6. Inserting nodes using insertAdjacentElement(), insertAdjacentText() insertAdjacentHTML()
  7. Bulk-inserting nodes using append(), prepend() before() and after
  8. The matches() method

What is the Element interface?

In the previous chapter, we went over the Node interface in very fine detail. There we learnt that Node is an abstract interface in that it can't be instantiated directly, but rather has to be extended by some other interface.

Element is an example of such an interface that builds upon the Node interface. As we learnt in the previous-to-previous chapter HTML DOM — Basics, Element is used to specifically represent an element node in the DOM tree.

It contains properties and methods that specifically apply only to elements in the DOM. Things such as children elements (not any arbitrary node), next element sibling, attributes, the inner HTML content, all apply to the Element interface.

Here's a list of some of the extremely common properties of Element, some of which we'll explore in this chapter and the rest in the next chapter:

PropertyPurpose
childrenA collection of all the children element nodes of the calling element.
firstElementChildThe first element child node of the calling element.
lastElementChildThe last element child node of the calling element.
nextElementSiblingThe following sibling node of the calling element which is an element node itself.
previousElementSiblingThe preceding sibling node of the calling element which is an element node itself.
innerHTMLThe HTML content inside the calling element.
tagNameThe name of the element's tag.

Note that all these properties are accessor properties.

To learn more about accessor properties, please refer to the chapter JavaScript Objects — Property Attributes.

Now, let's talk about the methods of Element:

MethodPurpose
insertAdjacentElement()Insert a given element node at a given position adjacent to the calling element.
insertAdjacentText()Insert some text at a given position adjacent to the calling element.
insertAdjacentHTML()Insert some HTML content at a given position adjacent to the calling element.
before()Insert a whole set of (text and element) nodes as the previous sibling of the calling element.
after()Insert a whole set of (text and element) nodes as the next sibling of the calling element.
prepend()Insert a whole set of (text and element) nodes as the first child of the calling element.
append()Insert a whole set of (text and element) nodes as the last child of the calling element.
matches()Determine whether the calling element matches a given CSS selector.

Time to go over each of these properties and methods one-by-one...

The children property

Do you recall the Node interface's childNodes property from the previous chapter HTML DOM — The Node Interface?

Well, the children property of the Element interface is a little bit similar.

In particular, it returns back an HTMLCollection instance containing only those child nodes of the calling element (i.e. the element node on which the property is accessed) that are elements themselves.

If the calling element has no element child nodes, or just about no child nodes at all, the return value of children is still an HTMLCollection, albeit an empty one.

Let's consider an example.

In the code below, we obtain the children of the #main element, and then log its length comparing it with the length of childNodes:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

console.log('childNodes', mainElement.childNodes.length);
console.log('children', mainElement.children.length);
childNodes 5 children 1

Let's inspect the nodeName of the first, and the only, element of children here:

mainElement.children[0].nodeName
'P'

As expected, the single item in children is the element node inside #main, which is simply the <p> element.

The firstElementChild and lastElementChild properties

The firstElementChild and lastElementChild, once again, are similar to the firstChild and lastChild properties of the Node interface, but with a slight difference.

That difference is already apparent in the naming of these Element properties.

That is, firstElementChild and lastElementChild return the first and last child node of the calling element node that is an element itself, respectively.

If there is no element child node in the calling element, or just about no children at all, both these properites evaluate down to null (similar to how firstChild and lastChild evaluate down to null as well).

Let's consider a very quick example.

In the code below, we retrieve the nodeName of the firstElementChild of #main:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

console.log(mainElement.firstElementChild.nodeName);
P

Let's inspect a couple more details regarding the firstElementChild of mainElement:

mainElement.firstElementChild === mainElement.children[0]
true
mainElement.firstChild.nodeName
'#text'

First of all, firstElementChild is equivalent to accessing children[0]. Secondly, as we access firstChild in the case above, it yields back a text node in contrast to firstElementChild which, as we've just seen, yields back an element node.

Both firstElementChild and lastElementChild can be defined solely using the firstChild and the lastChild properties of Node, and using its node type constants. We get you to code this notion in the upcoming exercise on the next page.

The previousElementSibling and nextElementSibling properties

Not surprising to know, the previousElementSibling and the nextElementSibling properties are the element equivalents of the previousSibling and nextSibling properties.

That is, previousElementSibling returns the previous sibling of the calling element node that is an element, or null if there is no preceding sibling that's an element or just about no preceding sibling at all.

On the other hand, nextElementSibling returns the next sibling of the calling element node that is an element, or null if there is no following sibling that's an element or just about no following sibling at all.

Here's an example.

Consider the following markup:

<div id="main">
   <p>A paragraph</p>
   <div>A div</div>
   <!--A comment-->
</div>

In the code below, we define a function logNodeName() to log the nodeName of a given element node, if the node is not null. Using this function, we log the nodeName of the previousElementSibling and the nextElementSibling of the <p> element above:

function logNodeName(elementNode) {
   console.log(elementNode ? elementNode.nodeName : elementNode);
}

var mainElement = document.getElementById('main');

logNodeName(mainElement.firstElementChild);
logNodeName(mainElement.firstElementChild.previousElementSibling);
logNodeName(mainElement.firstElementChild.nextElementSibling);
P null DIV

Simple?

As with firstElementChild and lastElementChild, previousElementSibling and nextElementSibling can be implemented entirely using the Node interface's properties previousSibling and nextSibling, respectively.

The innerHTML property

The innerHTML property allows us to get, as well as set, the HTML content inside a given element node.

It's defined in a separate web standard entitled DOM Parsing and Serialization.

When innerHTML is accessed in a 'get' context on a given element node, all the children nodes of the element are serialized based on the HTML serialization algorithm as specified in Section 13.3 — Serializing HTML fragments of the WHATWG HTML standard.

In simpler words, the serialization algorithm is simply a means of going from an object representation of every child node, of the calling element node, to its corresponding string representation. This requires reading tag names, node values, attributes, manually concatenating angle brackets (< and >), equals sign (=), quotes ("), and much more.

It's clearly not an easy task, but at the same time, not as difficult as the one that happens when we set innerHTML.

While setting innerHTML, we provide the property a string containing some HTML markup to be added right inside the element. This string has to be processed and then a set of corresponding nodes to be added right inside the element.

Formally the process of going from a string representation to actual DOM nodes is referred to as parsing. The HTML parsing algorithm used can be read in Section 13.4 — Parsing HTML fragments of the same HTML standard discussed above.

Parsing is a way more challenging task than serialization. It has to take into account the fact that a given HTML markup might be invalidly written, work out the nesting of elements correctly, and a lot more.

This is the reason why it's recommended to use the least amount of innerHTML, as possible, to set HTML content. Many web browsers do optimize innerHTML in many many ways, but that doesn't mean that we could carelessly use it in any way we like. Not at all!

It sure comes with a performance penalty if called again and again and again. We'll talk about the performance of the various ways to carry out DOM mutation later in this unit, in the chapter HTML DOM — Performance.

Anyways, coming back to innerHTML, let's consider some examples of its usage.

In the code below, we retrieve the innerHTML of #main and log it, encapsulated between two single quotes (') so that we can clearly see the value stored in innerHTML:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

console.log("'" + mainElement.innerHTML + "'");
' <p>A paragraph</p> <!--A comment--> '

As can be seen, innerHTML includes all the whitespace that appeared initially in the HTML markup, thanks to the text nodes created when processing the HTML markup for the construction of the DOM tree.

Moving on, in the code below, we redefine the content of #main by setting it to a given string containing some HTML markup:

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

mainElement.innerHTML = `<h1>A new heading</h1>
<p>A new paragraph</p>`;

Live Example

The reason why we used a template literal here is just so that we can freely express the new HTML markup without having to manually type \ns, or look out for ' or " symbols (which would otherwise break the string).

You can learn more about template strings in the chapter JavaScript Strings — Basics.

Setting innerHTML on an element node effectively changes its children.

This is demonstrated in the following example.

<div id="main">
   <p>A paragraph</p>
   <!--A comment-->
</div>
var mainElement = document.getElementById('main');

console.log('Child nodes before:', mainElement.children.length);

mainElement.innerHTML = `<h1>A new heading</h1>
<p>A new paragraph</p>`;

console.log('Child nodes after:', mainElement.children.length);
1 2

One thing worth mentioning here is that in line 8 above, we re-accessed children on mainElement to get an updated view of its underlying HTML content. There is technically, however, no need to do so. Recall that children returns back a live DOM collection which gets updated automatically as we mutate the DOM.

Hence, the code above could be rewritten in the following way, saving the initial return value of children in a variable and then accessing just that variable later on to get an updated view of the content of mainElement:

var mainElement = document.getElementById('main');
var mainElementChildren = mainElement.children; console.log('Child nodes before:', mainElementChildren.length); mainElement.innerHTML = `<h1>A new heading</h1> <p>A new paragraph</p>`; console.log('Child nodes after:', mainElementChildren.length);
1 2

Notice the logs made here — they are identical to the logs in the previous code. And that's simply due to the live nature of the HTMLCollection instance returned by the children property.

Inserting nodes

We've seen two ways to add a given node right inside another node in the last chapter HTML DOM — The Node Interface. Those are the appendChild() and insertBefore() methods, of the Node interface.

The Element interface goes on and adds three more methods to insert nodes inside an element.

They are:

  1. insertAdjacentElement()
  2. insertAdjacentText()
  3. insertAdjacentHTML()

Let's explore each one of these. However, one thing to note before we begin the discussion is to understand the working of the first argument to all of these methods.

The first argument specifies the exact position where the insertion of a given node is desired. It's given as a string whose value can be either of the following:

  • 'beforebegin'
  • 'afterbegin'
  • 'beforeend'
  • 'afterend'

The way these strings work is as follows. The starting tag of an element is called 'begin' while its ending tag is called 'end'.

The position before the starting tag is called 'beforebegin' while the position after the starting tag as the first child of the element is called 'afterbegin'.

The position before the ending tag as the last child of element is called 'beforeend', while the position after the ending tag is called 'afterend'.

This first argument is treated case-insensitively by all three of the aforementioned insertion methods, i.e. 'beforebegin', 'beforeBegin' and 'BEFOREBEGIN' are essentially the same thing.

Simple?

insertAdjacentElement()

The insertAdjacentElement() method adds a given element node adjacent to the calling element.

Its syntax is as follows:

element.insertAdjacentElement(position, elementNode)

position is one of the 4 string values mentioned above to specify the exact location where the new element node must be added, whereas elementNode is that element node.

If elementNode is not an element node, an error is thrown. Similarly, if position is not one of the aforementioned strings, an error is thrown in that case as well.

Let's consider a comprehensive example...

We'll use the following markup, along with the inline CSS shown, to test insertAdjacentElement():

<div id="main" style="border: 1px solid red;">
   <h1>A heading</h1>
</div>

The CSS will help us visualize when something is inside #main versus when it's outside #main.

Anyways, let's now get to the JavaScript part.

We'll define a function to create an element node, holding a given text value inside it, and then return it back so that we can easily insert it using the insertion method under consideration right now:

function getElementNode(textContent) {
   var paraElement = document.createElement('div');
   paraElement.textContent = textContent;
   return paraElement;
}

Using this function, we'll now create and, consequently, insert 4 different <p> elements adjacent to the #main element, one for each of the values of the position argument. In this way, we'll immediately be able to see which position exactly does each value target:

function getElementNode(textContent) {
   var paraElement = document.createElement('p');
   paraElement.textContent = textContent;
   return paraElement;
}

var mainElement = document.getElementById('main');

mainElement.insertAdjacentElement('beforebegin', getElementNode('beforebegin position'));
mainElement.insertAdjacentElement('afterbegin', getElementNode('afterbegin position'));
mainElement.insertAdjacentElement('beforeend', getElementNode('beforeend position'));
mainElement.insertAdjacentElement('afterend', getElementNode('afterend position'));

beforebegin

afterbegin

A heading

beforeend

afterend

Live Example

Easy?

insertAdjacentText()

The insertAdjacentText() method operates similar to the insertAdjacentElement() method with one obvious difference.

That is, it's meant to insert a text node adjacent to the element on which it's called.

One thing to note however in regards to this is that the textual content isn't provided as a text node, but rather just as a string to the method; the method automatically creates a text node internally.

Here's its syntax:

element.insertAdjacentText(position, text)

position is one of the 4 string values mentioned above to specify the exact location where the new text node must be added, whereas text is the text content of that node.

Let's take a look over a simple example...

We'll use the same setup as before, just this time changing the <h1> element to some literal text. This is done in order to see how text nodes actually get rendered visually on a document as they are inserted adjacent to given text nodes.

Here's the markup and the related CSS:

<div id="main" style="border: 1px solid red;">Some text</div>

Now, as before, we'll make four separate invocations to insertAdjacentText(), one for each of the values of the position argument:

var mainElement = document.getElementById('main');

mainElement.insertAdjacentText('beforebegin', ' --beforebegin-- ');
mainElement.insertAdjacentText('afterbegin', ' --afterbegin-- ');
mainElement.insertAdjacentText('beforeend', ' --beforeend-- ');
mainElement.insertAdjacentText('afterend', ' --afterend-- ');
--beforebegin--
--afterbegin-- Some text --beforeend--
--afterend--

Live Example

insertAdjacentHTML()

As with insertAdjacentElement() and insertAdjacentText(), the insertAdjacentHTML() method of the Element interface allows us to insert a given piece of HTML content adjacent to the calling element node.

First, let's have a look over its syntax:

element.insertAdjacentHTML(position, html)

position is one of the 4 string values mentioned above to specify the exact location where the new HTML content must be added, whereas html is a string containing that very HTML markup.

What's so special about insertAdjacentHTML() is that, like innerHTML, it parses the given string for nodes. However, it has a much better performance than innerHTML when we need to add to the content of a given element.

We'll see how and why is insertAdjacentHTML() better than innerHTML later in this section.

For now, let's consider a quick example:

<div id="main" style="border: 1px solid red;">
   <h1>A heading</h1>
</div>
var mainElement = document.getElementById('main');

mainElement.insertAdjacentHTML('beforebegin', '<p>beforebegin</p>');
mainElement.insertAdjacentHTML('afterbegin', '<p>afterbegin</p>');
mainElement.insertAdjacentHTML('beforeend', '<p>beforeend</p>');
mainElement.insertAdjacentHTML('afterend', '<p>afterend</p>');

What's happening here is quite self-explanatory — we are inserting a <p> element in each of the four valid position arguments to insertAdjacentHTML(). What's important to note is that the <p> element is provided in the form of a string, not as an Element node.

Live Example

Pretty basic.

Let's now try adding a set of multiple elements at once using insertAdjacentHTML().

In the code below, we add an <h3> and a <p> element before the #main element (using the 'beforebegin' position):

<div id="main" style="border: 1px solid red;">
   <h1>A heading</h1>
</div>
var mainElement = document.getElementById('main');

mainElement.insertAdjacentHTML('beforebegin',
   '<h3>A new heading</h3><p>A new paragraph</p>');

Live Example

The given html string argument is parsed for nodes and then all these nodes are added, exactly in the order they appear in the string, right in the desired position (i.e. 'beforebegin' in the case above).

Simple, still.

Moving on, often times, it's the case that we want to add HTML content inside an element while making sure that the existing content inside it stays there as well. If we use innerHTML to accomplish this task, it simply means one thing.

That is, we ought to get the existing innerHTML value and concatenate it with a string containing the new HTML content, and then set innerHTML back to the resulting string.

Now here's the problem with this approach. As we get innerHTML (for the concatenation with our new HTML string), serialization is performed of the element's children nodes. Then after this, when we set innerHTML back to the resulting string, parsing is done including that markup which was already inside the calling element node.

In other words, there are two useless steps involved in the innerHTML-based approach:

  1. Serialization of the calling element's children nodes.
  2. Reparsing of this serialized markup.

The insertAdjacentHTML() method effectively prevents both of these wasteful steps while adding content to an element node. It takes a string containing HTML markup, parses it into a set of DOM nodes, and then adds those nodes directly in the given location.

Thus, no serialization of the existing child nodes of the calling element is done, and consequently, no reparsing of this serialized HTML content is done in the parsing stage.

insertAdjacentHTML() is only concerned with the new content.

What's even more amazing about insertAdjacentHTML() is that it has an amazing browser support, even in some of the ancient browsers.

But then what's the point of innerHTML?

Well, innerHTML is very useful if we want to retrieve the HTML content inside a given element node in the form of a string — obtained via a process called serialization.

insertAdjacentHTML() obviously doesn't allows us to retrieve the HTML content of an element.

In addition to this, innerHTML can also be really handy if we want to replace all of the content inside a given element node with some new content (removing all the previous content).

As we know, insertAdjacentHTML() only allows us to add to the existing content around or inside an element — it doesn't allow us to empty an element node.

In both these cases, the innerHTML property does prove to be a helpful feature of the Element interface.

Bulk-inserting nodes

Where insertAdjacentElement() and insertAdjacentText() allows us to add an element node and a text node to a given position in the calling element, respectively, there are 4 additional methods on the Element interface that perform the same action but on a multitude of nodes, at once.

They are prepend(), append(), before() and after().

Each method targets one of the four positions as used in the position argument of insertAdjacentElement() and insertAdjacentText():

  • prepend() works similar to the position 'afterbegin'. That is, it adds the given set of nodes inside the calling element, in the position of its first child.
  • append() works similar to the position 'beforeend'. That is, it adds the given set of nodes inside the calling element, in the position of its last child.
  • before() works similar to the position 'beforebegin'. That is, it adds the given set of nodes in the position of the previous sibling of the calling element.
  • after() works similar to the position 'afterend'. That is, it adds the given set of nodes in the position of the next sibling of the calling element.

Not really that difficult to get the hang of, were these?

The syntax of all four methods is the same, as shown below with respect to prepend():

element.prepend(node1[, node2[, ...[, nodeN]]])

The methods are variadic, i.e. we can call them with an arbitrary number of arguments. This is their speciality — we can add as many elements and text nodes in the calling element just by using one single call to the method.

However, it must be the case that each argument is either a string (in which case it's used to create a text node) or an element node. Any other value would lead to an error.

In the code below, we add three separate pieces of content within the #main element, before its first child, using the prepend() method:

<div id="main" style="border: 1px solid red;">
   <h1>A heading</h1>
</div>
var h2Element = document.createElement('h2');
h2Element.textContent = 'A smaller heading';

var italicElement = document.createElement('i');
italicElement.textContent = 'italic text';

var mainElement = document.getElementById('main');
mainElement.prepend(h2Element, 'Simple ', italicElement);

The pieces are an element node, followed by a string (which would be used to produce a text node), finally followed by another element node, respectively.

When prepend() completes executing, the document looks something like the following:

A smaller heading

Simple italic text

A heading

Live Example

Notice how the new content appears exactly in the same order in which it was provided to the prepend() method.

We could think of prepend() (and the remaning three methods) as creating a box and dumping all the provided content (string and/or element nodes) inside that box. In the end, the method inserts the box right in its respective location adjacent to the calling element, thus giving an impression of bulk-inserting nodes into the given location, all at once.

One extremely crucial thing to remember regarding all the four methods prepend(), append(), before() and after(), is that they all internally use the same routine that's used by the insertBefore() method of the Node interface to insert the given set of nodes into the desired location.

What this means is that if the provided element nodes already exist in the document, they would be moved from their original locations and dumped into the new location adjacent to the calling element node.

Let's see an illustration of this idea.

In the example below, everything is the same as above, just this time we have included a call to append() after the call to prepend() to see what change does append() lead to:

<div id="main" style="border: 1px solid red;">
   <h1>A heading</h1>
</div>
var h2Element = document.createElement('h2');
h2Element.textContent = 'A smaller heading';

var italicElement = document.createElement('i');
italicElement.textContent = 'italic text';

var mainElement = document.getElementById('main');
mainElement.prepend(h2Element, 'Simple ', italicElement);
mainElement.append(h2Element, 'Simple ', italicElement);

Before we show the output produced by this code, take a minute or two to guess it.

What output would be produced by the code above?

Assuming that you've given it a try, let's now see the output together:

Simple

A heading

A smaller heading

Simple italic text

Live Example

And now, let's try to make sense out of this output.

  • When prepend() completes, the state of the DOM tree at that stage is identical to the output shown in the previous example above.
  • Then, append() is called.
  • The first argument to append() is an element node that already exists in the document (because of the previous prepend() insertion), likewise, it's moved from its current location into this new set of nodes to be appended inside #main.
  • The second argument to append() is a string. Internally, append() creates a new text node with its nodeValue set to this string, and then adds this text node into the set of nodes to be appended inside #main (which already contains an <h2> element from the previous step).
  • The third argument to append() is another element node, which once again exists in the document, likewise, it's also moved from its current location into the set of nodes to be appended inside #main (which already contains an <h2> element and a text node from the previous steps).
  • In the end, append() completes with an <h2> element node, followed by a text node, followed by an <i> element inserted at the end of #main.
  • The text node added by the call to prepend() remains in its original position, which explains the text 'Simple' shown above the <h1> element.

Simple?

The matches() method

Suppose we have an element node with us and want to determine whether it matches a particular CSS selector or not.

For this, we simply have the provision of the matches() method of the Element interface.

You can learn more about CSS selectors in the chapters CSS Selectors and CSS Combinators, from our very own CSS Course.

Its syntax is pretty straightforward:

element.matches(selector)

Just one selector argument to specify the CSS selector, in the form of a string, to match the element node against.

Let's consider an example.

Given the following markup, below we select the #main element and its first child element, and then test both of them against a couple of CSS selectors:

<div id="main">
   <h1>A heading</h1>
</div>
var mainElement = document.getElementById('main')
undefined
var h1Element = mainElement.firstElementChild
undefined
mainElement.matches('#main')
true
mainElement.matches('body #main')
true
mainElement.matches('body > #main')
true
mainElement.matches('html > #main')
false
h1Element.matches('h1')
true
h1Element.matches('body h1')
true
h1Element.matches('#main > h1')
true
h1Element.matches('#main + h1')
false

Let's understand the two false return values here:

  • mainElement.matches('html > #main') returns false as the #main element is not a child of the <html> element; instead it's the child of the <body> element.
  • h1Element.matches('#main + h1') returns false since <h1> is not an adjacent sibling of ip #main; in fact, it's not a sibling at all.

matches() allows us to test almost all kinds of CSS selectors on a given element.

What is webkitMatchesSelector()?

matches() was inspired by a vendor-specific method called webkitMatchesSelector(). Some time ago, it was an experimental technology, having the exact same signature as match() now. Being an experimental feature, it wasn't supported across different platforms.

Even though today it's part of the DOM specification as an alias to matches(), in the Element interface, purely for legacy purposes, it's recommended that we always use matches() to favor code readability.

Who likes to see 'webkit' in his/her code anyway?

Moving on

There are numerous properties and even a couple of methods on the Element interface that we haven't explored in this chapter yet.

That's because we have spared separate chapters to talk about those properties and methods owing to their similarity to one another in terms of the nature of the underlying operation and also owing to their significance in programs.

Those chapters are HTML DOM — Attributes and HTML DOM — Selecting Elements.

So let's get going in this learning journey, exploring the DOM.

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage