DOM Manipulation And Events
Table of Contents
DOM - Document Object Model⌗
The DOM (or Document Object Model) is a tree-like representation of the contents of a webpage - a tree of “nodes” with different relationships depending on how they’re arranged in the HTML document.
<div id="container">
<div class="display"></div>
<div class="controls"></div>
</div>
In the above example, the <div class="display"></div>
is a “child” of <div id="container"></div>
and a sibling to <div class="control"></div>
. Think of it like a family tree. <div id="container"></div>
is a parent, with its children on the next level, each on their own “branch”.
Targeting Nodes with Selectors⌗
You can use a combination of CSS-style selectors and relationship properties to target the nodes you want. CSS-style selectors from the above example:
- div.display
- .display
- #container > .display
- div#container > div.display
You can also use relational selectors (i.e. firstElementChild or lastElementChild etc.) with special properties owned by the nodes.
const container = document.querySelector('#container');
// selects the #container div
// '#' for id like css
console.dir(container.firstElementChild);
// selects the first child of #container => .display
const controls = document.querySelector('.controls');
// selects the .controls div
// '.' for class like css
console.dir(controls.previousElementSibling);
// selects the prior sibling => .display
DOM Methods⌗
In the DOM, all HTML elements are defined as objects, these objects have properties and methods attached to them, which are the primary tools we are going to use to manipulate our webpage with JavaScript.
Query Selectors⌗
These will help you target nodes.
-
element.querySelector(selector)
returns a reference to the first match of the selector -
element.querySelectorAll(selectors)
returns a “nodelist” containing references to all of the matches of the selectors
Note: There are several other, more specific queries, that offer potential (marginal) performance benefits
Note: When using querySelectorAll
, the return value is not an array. It looks like one, and
it somewhat acts like one, but it’s really a “nodelist”. The big distinction is that several array
methods are missing from nodelists. One solution, if problems arise, is to convert the nodelist into
an array using Array.from() or the spread operator.
Element Creation⌗
document.createElement(tagName, [options])
creates a new element of tag type tagName.[options]
in this case means you can add some optional parameters to the function.
const div = document.createElement('div');
This function does NOT put your new element into the DOM - it simply creates it in memory. This is so that you can manipulate the element (by adding styles, classes, ids, text, etc.) before placing it on the page. You can place the element into the DOM with one of the following methods.
Append Elements⌗
-
parentNode.appendChild(childNode)
appends childNode as the last child of the parentNode -
parentNode.inserBefore(newNode, referenceNode)
inserts newNode into parentNode before referenceNode
Remove Elements⌗
parentNode.removeChild(child)
removes child from parentNode on the DOM and returns a reference to child
Altering Elements⌗
When you have a reference to an element, you can use it to alter the element’s own properties. This allows you to do many useful alterations, like adding/removing and altering attributes, changing classes, adding inline style information and more.
const div = document.createElement('div');
// creates a new div referenced in the variable 'div'
Adding inline style⌗
div.style.color = 'blue';
// adds the indicated style rule
div.style.cssText = 'color: blue; background: white;';
// adds several style rules
div.setAttribute('style', 'color: blue; background: white;');
// adds several style rules
For more inline styles, checkout DOM Enlightenment’s section on CSS Style rules
Note that if you’re accessing a kebab-cased CSS rule from JS, you’ll either need to use camelCase or you’ll need to use bracket notation instead of dot notation
div.style.background-color // doesn't work - attempts to subtract color from div.style.background
div.style.backgroundColor // accesses the div's background-color style
div.style['background-color'] // also works
div.style.cssText = "background-color: white;" // ok in a string
Editing attributes⌗
div.setAttribute('id', 'theDiv');
// if id exists, update it to 'theDiv', else create an id
// with value "theDiv"
div.getAttribute('id');
// returns value of specified attribute, in this case
// "theDiv"
div.removeAttribute('id');
// removes specified attribute
To see more info on available attributes checkout MDN’s section on HTML Attributes
Working with Classes⌗
div.classList.add('new');
// adds class "new" to your new div
div.classList.remove('new');
// removes "new" class from div
div.classList.toggle('active');
// if div doesn't have class "active" then add it, or if
// it does, then remove it
It is often standard (and cleaner) to toggle a CSS style rather than adding and removing inline CSS.
Adding Text Content⌗
div.textContent = 'Hello World!'
// creates a text node containing "Hello World!" and
// inserts it in div
Adding HTML Content⌗
div.innerHTML = '<span>Hello World!</span>';
// renders the HTML inside div
Note that textContent is preferable for adding text, and innerHTML should be used sparingly as it can create security risks if misused. Checkout this video if you want to see an example of how.
To Review what we’ve Covered⌗
<!-- your HTML file: -->
<body>
<h1>
THE TITLE OF YOUR WEBPAGE
</h1>
<div id="container"></div>
</body>
// your JavaScript file
const container = document.querySelector('#container');
const content = document.createElement('div');
content.classList.add('content');
content.textContent = 'This is the glorious text-content!';
container.appendChild(content);
In the JavaScript file, first we get a reference to the container
div that already exists in
our HTML. Then we create a new div and store it in the variable content
. We add a class and
some text to the content
div and finally append that div to container
. After the
JavaScript code is run, our DOM tree will look like this:
<!-- The DOM -->
<body>
<h1>
THE TITLE OF YOUR WEBPAGE
</h1>
<div id="container">
<div class="content">
This is the glorious text-content!
</div>
</div>
</body>
Your JavaScript, for the most part, is run whenever the JS file is run, or when the script
tag is encountered in the HTML. If you are including your JavaScript at the top of your file,
many of these DOM manipulation methods will not work because the JS code is being run before
the nodes are created in the DOM, the simplest way to fix this is to include your
JavaScript at the bottom of your HTML or to link it in the <head>
and include the
defer
keyword to load the file after the HTML is parsed.
<head>
<script src="js-file.js" defer></script>
</head>
Read the second bullet point in this MDN article for more information, which also includes a link to additional script loading strategies.
Events⌗
Events will help us manipulate the DOM dynamically, or on demand! Events are how you make that magic happen your pages. Events are actions that occur on your webpage such as mouse-clicks or key-presses, and using JavaScript we can make our webpage listen and react to these events.
We’re going to create 3 buttons that all alert “Hello World” when clicked.
Method 1⌗
<button onclick="alert('Hello World')">Click Me</button>
This solution is less than ideal because we’re cluttering our HTML with JavaScript. Also, we can only have 1 “onclick” even per element.
Method 2⌗
<!-- the HTML file -->
<button id="btn">Click Me</button>
const btn = document.querySelector('#btn');
btn.onclick = () => alert("Hello World");
This is a little better. We’ve moved the JS out of the HTML and into a JS file, but we still have the problem that a DOM element can only have 1 “onclick” property.
Method 3⌗
<!-- the HTML file -->
<button id="btn">Click Me</button>
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
alert("Hello World");
});
Now, we maintain separation of concerns, and we also allow multiple event listeners if the need arises. Method 3 is much more flexible and powerful, though it is a bit more complex to set up.
Note that all 3 of these methods can be used with named functions
<!-- the HTML file -->
<!-- METHOD 1 -->
<button onclick="alertFunction()">CLICK ME BABY</button>
function alertFunctino() {
alert("YAY! YOU DID IT!");
}
// METHOD 2
btn.onclick = alertFunction;
// METHOD 3
btn.addEventListener('click', alertFunction);
With all three methods we can access more information about the event by passing a parameter to the function that we are calling.
btn.addEventListener('click', function (e) {
console.log(e);
});
**Note that function (e)
is a callback from addEventListener. Further explanation of callbacks
can be found HERE.
The e
in that function is an object that references the event itself. Within that
object you have access to many useful properties and functions such as which mouse button
or key was pressed, or information about the event’s target - the DOM node that was clicked.
Try this:
btn.addEventListener('click', function (e) {
console.log(e.target);
});
and now this:
btn.addEventListener('click', function (e) {
e.target.syle.background = 'blue';
});
Attaching Listeners to Groups of Nodes⌗
This might seem like a lot of code if you’re attaching lots of similar event listeners to many elements. There are a few ways to go about doing that more efficiently. In order to add a listener to each we simply need to iterate through the whole nodelist like so:
<div id="container">
<button id="1">Click Me</button>
<button id="2">Click Me</button>
<button id="3">Click Me</button>
</div>
// buttons is a node list. It looks and acts much like an array.
const buttons = document.querySelectorAll('button');
// we use the .forEach method to iterate through each button
buttons.forEach((button) => {
// and for each one we add a 'click' listener
button.addEventListener('click', () => {
alert(button.id);
});
});
This is just the tip of the iceberg when it comes to DOM manipulation and event handling. There are many more events available, some useful ones are:
- click
- dblclick
- keydown
- keyup
Resources⌗
- Eloquent JS - DOM
- Eloquent JS - Handling Events
- DOM Enlightenment
- Plain JavaScript is a reference of JavaScript code snippets and explanations involving the DOM, as well as other aspects of JS. If you’ve already learned jQuery, it will help you figure out how to do things without it.
- This W3Schools article offers simple and easy-to-understand lessons on the DOM.
- JS DOM Crash Course is an extensive and well explained 4 part video series on the DOM by Traversy Media.
- Understanding The Dom is an aptly named article-based tutorial series by DigitalOcean.