Dom Jolly takes a call on his not so mobile phone

Call me DOM

In this scoop of JavaScript ice-cream I’m going to cover some DOM management techniques with pure JavaScript.

When using a library, such as jQuery or Zepto, DOM traversal and manipulation is super simple but the shorthand in such libraries is both beautiful and mysterious. However, many of the methods for dealing with the DOM are actually quite succinct and straightforward. Along with sharing some pure JS techniques, I’ll help circumnavigate a few gotchas and offer some utility functions, all of which should help you get back in touch with your inner programmer-self.

HTML strings

Perhaps the most easy-to-use DOM getter and setter in JavaScript is innerHTML. Popping some HTML into a container is as simple as:

//HTML//
<div id="container"></div>
//JS//
var hello = '<p>Hello!</p>';
document.getElementById('container').innerHTML = hello;

Now let’s try getting some HTML and using it:

//HTML//
<div id="container"><p>Hello!</p></div>
<article id="article"></article>
//JS//
var hello = document.getElementById('container').innerHTML;
document.getElementById('article').innerHTML = hello;

All pretty straight forward. Let’s take a look at what data type innerHTML actually returns:

var container = document.getElementById('container').innerHTML;
console.log('typeof container', typeof container);
> typeof container string

It’s a string. This is fine for many situations, but some methods will not parse it automatically. One such example is passing our container HTML into appendChild, like so:

var container = document.getElementById('container').innerHTML;
document.getElementById('article').appendChild(container);
> Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.

The error we get back is nice and clear, but our program is broken. In order to get it working we either need to store the HTML as a DOM node, convert the string to a DOM node or use a different method to achieve the desired result.

Luckily for us there’s a lovely method called insertAdjacentHTML which handles these serialised HTML strings. It also gives us flexibility over where the HTML is inserted. Here it is in action:

var container = document.getElementById('container').innerHTML;
document.getElementById('article').insertAdjacentHTML('beforeend', container);

Notice the first parameter is a string which tells it where to place our HTML in the target container. There are four of these: ‘beforebegin’, ‘afterbegin’, ‘beforeend’ and ‘afterend’. For full details check the MDN page here and note the comprehensive browser compatibility. Good times!

A little type clarity

DOM nodes come in different flavours, some of which are very common, such as element, text and comment nodes. Let’s select a specific DOM node directly and store it in a variable for closer inspection. There’s loads of ways to do this, as explained in this excellent sitepoint article, but for now we’ll just use getElementById as we have done so far.

var container = document.getElementById('container');

Now we can reveal its type by accessing the nodeType property like so:

console.log(container.nodeType);
> 1

Notice it returns a number. If you’re working in vanilla JS and want to check what kind of type of DOM node you’re dealing with, the number isn’t so helpful.

To understand what the number represents you’d have to look it up on MDN or some such resource…or, you could use a utility function like the one I’ve created here on JSFiddle. All I’ve done is create a JSON object which stores the different types in ascending order so we can look them up using array methods. I went to the trouble of adding a description property too. All the content for this function is taken from MDN.

Let’s try the function out by passing in the document body. What we get back in the console will now be human readable string rather than a mystery number.

var docBody = document.body; console.log(nodeTypeEval(docBody).name);
> ELEMENT_NODE

This can be useful when you’re looping through a set of nodes (perhaps by using childNodes) and want to perform actions on particular types or avoid others.

Whitespace headaches

Whitespace can drive you around the bend, particularly when you’re dealing with siblings. If you only want to manage DOM Nodes that are HTML Elements then you’ll generally want to ensure you’re dealing with nodes which have a type of 1 i.e.

if (myNode.nodeType !== 1) {
//do something
}

or avoid ones of type 3 or 8 — which are comments and text respectively.

A nice utility I’ve started using is this clean function by James Edwards, which neatly loops through a set of nodes of any depth and removes the comments and whitespace. In this fiddle I have added a done callback to run some code when all the whitespace has been removed.

Collections, lists, arrays… oh my!

We’ve looked briefly at handling single nodes but we will often want to handle groups of nodes. Certain functions, such as getElementsByClassName, will return an HTMLcollection, while others will return a nodeList. This might seem a little confusing at first, but practically speaking they’re reasonably similar as explained in this StackOverflow post.

Put simply, an HTMLCollection provides the same methods as a NodeList with an additional method called namedItem. Let’s look at a collection first:

//HTML
<form class="form-main" id="form-main" action="#">
<input class="input" type="text" value="none" id="input" />
<button class="button" type="submit" id="btn1">Button</button>
<button class="button cta">CTA</button>
</form>
//JS
//Create an HTML Collection / Array like object
var buttons = document.getElementsByClassName('button');
console.log('typeof buttons', typeof buttons);
> typeof buttons object

When we log the collection of buttons it’s considered an object, but crucially it’s an array-like object, which means we can access the nodes much in the same way we would any item in an array:

console.log('first button', buttons[0]);
> first button
<button class=​"button" type=​"submit" id=​"btn1">​Button​</button>​

Using a standard for loop will yield the item(s) you may want to handle.

for (var i = 0; i < buttons.length; i++) { 
console.log(buttons[i]);
}

Node lists can also be iterated through in this way. NodeList objects are collections of nodes such as those returned by Node.childNodes and the document.querySelectorAll method.

Conclusion

Peering into the workings of JavaScript’s DOM management properties and methods can be both enlightening and empowering. There’s lots of great resources out there to help you deal with some of the issues around data and DOM node type, some of which I have covered here.

It is often easier than you think to work in pure JS and more often than not your code will perform better. And that’s why I think JavaScript Ice Cream always tastes so nice.