Modifying the document

DOM modifications is the key to create “live” pages.

Here we’ll see how to create new elements “on the fly” and modify the existing page content.

First we’ll see a simple example and then explain the methods.

Example: show a message

For a start, let’s see how to add a message on the page that looks nicer than alert.

Here’s how it will look:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="alert">
  <strong>Hi there!</strong> You've read an important message.
</div>

That was an HTML example. Now let’s create the same div with JavaScript (assuming that the styles are still in the HTML or an external CSS).

Creating an element

To create DOM nodes, there are two methods:

document.createElement(tag)

Creates a new element with the given tag:

let div = document.createElement('div');
document.createTextNode(text)

Creates a new text node with the given text:

let textNode = document.createTextNode('Here I am');

Creating the message

In our case we want to make a div with given classes and the message in it:

let div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

After that, we have a ready DOM element. Right now it’s in the variable, but can not be seen, because not inserted into the page yet.

Insertion methods

To make the div show up, we need to insert it somewhere into document. For instance, in document.body.

There’s a special method for that: document.body.appendChild(div).

Here’s the full code:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert alert-success";
  div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

  document.body.appendChild(div);
</script>

Here’s a brief list of methods to insert a node into a parent element (parentElem for short):

parentElem.appendChild(node)

Appends node as the last child of parentElem.

The following example adds a new <li> to the end of <ol>:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  let newLi = document.createElement('li');
  newLi.innerHTML = 'Hello, world!';

  list.appendChild(newLi);
</script>
parentElem.insertBefore(node, nextSibling)

Inserts node before nextSibling into parentElem.

The following code inserts a new list item before the second <li>:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>
<script>
  let newLi = document.createElement('li');
  newLi.innerHTML = 'Hello, world!';

  list.insertBefore(newLi, list.children[1]);
</script>

To insert as the first element, we can do like this:

list.insertBefore(newLi, list.firstChild);
parentElem.replaceChild(node, oldChild)

Replaces oldChild with node among children of parentElem.

All these methods return the inserted node. In other words, parentElem.appendChild(node) returns node. But usually the returned value is not used, we just run the method.

These methods are “old school”: they exist from the ancient times and we can meet them in many old scripts. Unfortunately, there are some tasks that are hard to solve with them.

For instance, how to insert html if we have it as a string? Or, given a node, how to insert another node before it? Of course, all that is doable, but not in an elegant way.

So there exist two other sets of insertion methods to handle all cases easily.

prepend/append/before/after

This set of methods provides more flexible insertions:

  • node.append(...nodes or strings) – append nodes or strings at the end of node,
  • node.prepend(...nodes or strings) – insert nodes or strings into the beginning of node,
  • node.before(...nodes or strings) –- insert nodes or strings before the node,
  • node.after(...nodes or strings) –- insert nodes or strings after the node,
  • node.replaceWith(...nodes or strings) –- replaces node with the given nodes or strings.

Here’s an example of using these methods to add more items to a list and the text before/after it:

<ol id="ol">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  ol.before('before');
  ol.after('after');

  let prepend = document.createElement('li');
  prepend.innerHTML = 'prepend';
  ol.prepend(prepend);

  let append = document.createElement('li');
  append.innerHTML = 'append';
  ol.append(append);
</script>

Here’s a small picture what methods do:

So the final list will be:

before
<ol id="ol">
  <li>prepend</li>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>append</li>
</ol>
after

These methods can insert multiple list of nodes and text pieces in a single call.

For instance, here a string and an element are inserted:

<div id="div"></div>
<script>
  div.before('<p>Hello</p>', document.createElement('hr'));
</script>

All text is inserted as text.

So the final HTML is:

&lt;p&gt;Hello&lt;/p&gt;
<hr>
<div id="div"></div>

In other words, strings are inserted in a safe way, like elem.textContent does it.

So, these methods can only be used to insert DOM nodes or text pieces.

But what if we want to insert HTML “as html”, with all tags and stuff working, like elem.innerHTML?

insertAdjacentHTML/Text/Element

There’s another, pretty versatile method: elem.insertAdjacentHTML(where, html).

The first parameter is a string, specifying where to insert, must be one of the following:

  • "beforebegin" – insert html before elem,
  • "afterbegin" – insert html into elem, at the beginning,
  • "beforeend" – insert html into elem, at the end,
  • "afterend" – insert html after elem.

The second parameter is an HTML string, inserted “as is”.

For instance:

<div id="div"></div>
<script>
  div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
  div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>

…Would lead to:

<p>Hello</p>
<div id="div"></div>
<p>Bye</p>

That’s how we can append an arbitrary HTML to our page.

Here’s the picture of insertion variants:

We can easily notice similarities between this and the previous picture. The insertion points are actually the same, but this method inserts HTML.

The method has two brothers:

  • elem.insertAdjacentText(where, text) – the same syntax, but a string of text in inserted “as text” instead of HTML,
  • elem.insertAdjacentElement(where, elem) – the same syntax, but inserts an element.

They exist mainly to make the syntax “uniform”. In practice, most of the time only insertAdjacentHTML is used, because for elements and text we have methods append/prepend/before/after – they are shorter to write and can insert nodes/text pieces.

So here’s an alternative variant of showing a message:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  document.body.insertAdjacentHTML("afterbegin", `<div class="alert alert-success">
    <strong>Hi there!</strong> You've read an important message.
  </div>`);
</script>

Cloning nodes: cloneNode

How to insert one more similar message?

We could do a function and put the code there. But the alternative way would be to clone the existing div and modify the text inside it (if needed).

Sometimes when we have a big element, that may be faster and simpler.

  • The call elem.cloneNode(true) creates a “deep” clone of the element – with all attributes and subelements. If we call elem.cloneNode(false), then the clone is made without child elements.

An example of copying the message:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="alert" id="div">
  <strong>Hi there!</strong> You've read an important message.
</div>

<script>
  let div2 = div.cloneNode(true); // clone the message
  div2.querySelector('strong').innerHTML = 'Bye there!'; // change the clone

  div.after(div2); // show the clone after the existing div
</script>

Removal methods

To remove nodes, there are following methods:

parentElem.removeChild(node)
Removes elem from parentElem (assuming it’s a child).
node.remove()
Removes the node from its place.

We can easily see that the second method is much shorter. The first one exists for historical reasons.

Please note:

If we want to move an element to another place – there’s no need to remove it from the old one.

All insertion methods automatically remove the node from the old place.

For instance, let’s swap elements:

<div id="first">First</div>
<div id="second">Second</div>
<script>
  // no need to call remove
  second.after(first); // take #second and after it - insert #first
</script>

Let’s make our message disappear after a second:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert alert-success";
  div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

  document.body.append(div);
  setTimeout(() => div.remove(), 1000);
  // or setTimeout(() => document.body.removeChild(div), 1000);
</script>

A word about “document.write”

There’s one more, very ancient method of adding something to a web-page: document.write.

The syntax:

<p>Somewhere in the page...</p>
<script>
  document.write('<b>Hello from JS</b>');
</script>
<p>The end</p>

The call to document.write(html) writes the html into page “right here and now”. The html string can be dynamically generated, so it’s kind of flexible. We can use JavaScript to create a full-fledged webpage and write it.

The method comes from times when there were no DOM, no standards… Really old times. It still lives, because there are scripts using it.

In modern scripts we can rarely see it, because of the important limitation.

The call to document.write only works while the page is loading.

If we call it afterwards, the existing document content is erased.

For instance:

<p>After one second the contents of this page will be replaced...</p>
<script>
  // document.write after 1 second
  // that's after the page loaded, so it erases the existing content
  setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>

So it’s kind of unusable at “after loaded” stage, unlike other DOM methods we covered above.

That was the downside.

Technically, when document.write is called while the browser is still reading HTML, it appends something to it, and the browser consumes it just as it were initially there.

That gives us the upside – it works blazingly fast, because there’s no DOM modification. It writes directly into the page text, while the DOM is not yet built, and the browser puts it into DOM at generation-time.

So if we need to add a lot of text into HTML dynamically, and we’re at page loading phase, and the speed matters, it may help. But in practice these requirements rarely come together. And usually we can see this method in scripts just because they are old.

Summary

Methods to create new nodes:

  • document.createElement(tag) – creates an element with the given tag,
  • document.createTextNode(value) – creates a text node (rarely used),
  • elem.cloneNode(deep) – clones the element, if deep==true then with all descendants.

Insertion and removal of nodes:

  • From the parent:

    • parent.appendChild(node)
    • parent.insertBefore(node, nextSibling)
    • parent.removeChild(node)
    • parent.replaceChild(newElem, node)

    All these methods return node.

  • Given a list of nodes and strings:

    • node.append(...nodes or strings) – insert into node, at the end,
    • node.prepend(...nodes or strings) – insert into node, at the beginning,
    • node.before(...nodes or strings) –- insert right before node,
    • node.after(...nodes or strings) –- insert right after node,
    • node.replaceWith(...nodes or strings) –- replace node.
    • node.remove() –- remove the node.

    Text strings are inserted “as text”.

  • Given a piece of HTML: elem.insertAdjacentHTML(where, html), inserts depending on where:

    • "beforebegin" – insert html right before elem,
    • "afterbegin" – insert html into elem, at the beginning,
    • "beforeend" – insert html into elem, at the end,
    • "afterend" – insert html right after elem.

    Also there are similar methods elem.insertAdjacentText and elem.insertAdjacentElement, they insert text strings and elements, but they are rarely used.

  • To append HTML to the page before it has finished loading:

    • document.write(html)

    After the page is loaded such call erases the document. Mostly seen in old scripts.

Tasks

importance: 5

We have an empty DOM element elem and a string text.

Which of these 3 commands do exactly the same?

  1. elem.append(document.createTextNode(text))
  2. elem.innerHTML = text
  3. elem.textContent = text

Answer: 1 and 3.

Both commands result in adding the text “as text” into the elem.

Here’s an example:

<div id="elem1"></div>
<div id="elem2"></div>
<div id="elem3"></div>
<script>
  let text = '<b>text</b>';

  elem1.append(document.createTextNode(text));
  elem2.textContent = text;
  elem3.innerHTML = text;
</script>
importance: 5

Create a function clear(elem) that removes everything from the element.

<ol id="elem">
  <li>Hello</li>
  <li>World</li>
</ol>

<script>
  function clear(elem) { /* your code */ }

  clear(elem); // clears the list
</script>

First, let’s see how not to do it:

function clear(elem) {
  for (let i=0; i < elem.childNodes.length; i++) {
      elem.childNodes[i].remove();
  }
}

That won’t work, because the call to remove() shifts the collection elem.childNodes, so elements start from the index 0 every time. But i increases, and some elements will be skipped.

The for..of loop also does the same.

The right variant could be:

function clear(elem) {
  while (elem.firstChild) {
    elem.firstChild.remove();
  }
}

And also there’s a simpler way to do the same:

function clear(elem) {
  elem.innerHTML = '';
}
importance: 1

Run the example. Why table.remove() does not delete the text "aaa"?

<table id="table">
  aaa
  <tr>
    <td>Test</td>
  </tr>
</table>

<script>
  alert(table); // the table, as it should be

  table.remove();
  // why there's still aaa in the document?
</script>

The HTML in the task is incorrect. That’s the reason of the odd thing.

The browser has to fix it automatically. But there may be no text inside the <table>: according to the spec only table-specific tags are allowed. So the browser adds "aaa" before the <table>.

Now it’s obvious that when we remove the table, it remains.

The question can be easily answered by exploring DOM using the browser tools. They show "aaa" before the <table>.

The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct.

importance: 4

Write an interface to create a list from user input.

For every list item:

  1. Ask a user about its content using prompt.
  2. Create the <li> with it and add it to <ul>.
  3. Continue until the user cancels the input (by pressing Esc or CANCEL in prompt).

All elements should be created dynamically.

If a user types HTML-tags, they should be treated like a text.

Demo in new window

Please note the usage of textContent to assign the <li> content.

Open the solution in the sandbox.

importance: 5

Write a function createTree that creates a nested ul/li list from the nested object.

For instance:

let data = {
  "Fish": {
    "trout": {},
    "salmon": {}
  },

  "Tree": {
    "Huge": {
      "sequoia": {},
      "oak": {}
    },
    "Flowering": {
      "redbud": {},
      "magnolia": {}
    }
  }
};

The syntax:

let container = document.getElementById('container');
createTree(container, data); // creates the tree in the container

The result (tree) should look like this:

Choose one of two ways of solving this task:

  1. Create the HTML for the tree and then assign to container.innerHTML.
  2. Create tree nodes and append with DOM methods.

Would be great if you could do both.

P.S. The tree should not have “extra” elements like empty <ul></ul> for the leaves.

Open the sandbox for the task.

The easiest way to walk the object is to use recursion.

  1. The solution with innerHTML.
  2. The solution with DOM.

Open the solution in the sandbox.

importance: 5

There’s a tree organized as nested ul/li.

Write the code that adds to each <li> the number of its descendants. Skip leaves (nodes without children).

The result:

Open the sandbox for the task.

To append text to each <li> we can alter the text node data.

Open the solution in the sandbox.

importance: 4

Write a function createCalendar(elem, year, month).

The call should create a calendar for the given year/month and put it inside elem.

The calendar should be a table, where a week is <tr>, and a day is <td>. The table top should be <th> with weekday names: the first day should be Monday, and so on till Sunday.

For instance, createCalendar(cal, 2012, 9) should generate in element cal the following calendar:

P.S. For this task it’s enough to generate the calendar, should not yet be clickable.

Open the sandbox for the task.

We’ll create the table as a string: "<table>...</table>", and then assign it to innerHTML.

The algorithm:

  1. Create the table header with <th> and weekday names.
  2. Create the date object d = new Date(year, month-1). That’s the first day of month (taking into account that months in JavaScript start from 0, not 1).
  3. First few cells till the first day of the month d.getDay() may be empty. Let’s fill them in with <td></td>.
  4. Increase the day in d: d.setDate(d.getDate()+1). If d.getMonth() is not yet the next month, then add the new cell <td> to the calendar. If that’s a Sunday, then add a newline “</tr><tr>”.
  5. If the month has finished, but the table row is not yet full, add empty <td> into it, to make it square.

Open the solution in the sandbox.

importance: 4

Create a colored clock like here:

Open the sandbox for the task.

First, let’s make HTML/CSS.

Each component of the time would look great in its own <span>:

<div id="clock">
  <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>

Also we’ll need CSS to color them.

The update function will refresh the clock, to be called by setInterval every second:

function update() {
  let clock = document.getElementById('clock');
  let date = new Date(); // (*)
  let hours = date.getHours();
  if (hours < 10) hours = '0' + hours;
  clock.children[0].innerHTML = hours;

  let minutes = date.getMinutes();
  if (minutes < 10) minutes = '0' + minutes;
  clock.children[1].innerHTML = minutes;

  let seconds = date.getSeconds();
  if (seconds < 10) seconds = '0' + seconds;
  clock.children[2].innerHTML = seconds;
}

In the line (*) we every time check the current date. The calls to setInterval are not reliable: they may happen with delays.

The clock-managing functions:

let timerId;

function clockStart() { // run the clock
  timerId = setInterval(update, 1000);
  update(); // (*)
}

function clockStop() {
  clearInterval(timerId);
  timerId = null;
}

Please note that the call to update() is not only scheduled in clockStart(), but immediately run in the line (*). Otherwise the visitor would have to wait till the first execution of setInterval. And the clock would be empty till then.

Open the solution in the sandbox.

importance: 5

Write the code to insert <li>2</li><li>3</li> between two <li> here:

<ul id="ul">
  <li id="one">1</li>
  <li id="two">4</li>
</ul>

When we need to insert a piece of HTML somewhere, insertAdjacentHTML is the best fit.

The solution:

one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');
importance: 5

There’s a table:

Name Surname Age
John Smith 10
Pete Brown 15
Ann Lee 5
... ... ...

There may be more rows in it.

Write the code to sort it by the "name" column.

Open the sandbox for the task.

The solution is short, yet may look a bit tricky, so here I provide it with extensive comments:

let sortedRows = Array.from(table.rows)
  .slice(1)
  .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);

table.tBodies[0].append(...sortedRows);
  1. Get all <tr>, like table.querySelectorAll('tr'), then make an array from them, cause we need array methods.

  2. The first TR (table.rows[0]) is actually a table header, so we take the rest by .slice(1).

  3. Then sort them comparing by the content of the first <td> (the name field).

  4. Now insert nodes in the right order by .append(...sortedRows).

    Tables always have an implicit element, so we need to take it and insert into it: a simple table.append(...) would fail.

    Please note: we don’t have to remove them, just “re-insert”, they leave the old place automatically.

Open the solution in the sandbox.

Tutorial map

Comments

read this before commenting…
  • You're welcome to post additions, questions to the articles and answers to them.
  • To insert a few words of code, use the <code> tag, for several lines – use <pre>, for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)
  • If you can't understand something in the article – please elaborate.