Monday-Tuesday: DOM Manipulation & Events Copy

Introduction to DOM

One of the most unique and useful abilities of JavaScript is its ability to manipulate the web pages. It can manipulate both the HTML and CSS of any web page through the DOM. But what is the DOM, and how do we go about changing it? Let’s jump right in…

Learning Objectives

  • DOM in relation to a web page
  • The difference between a "node" and an "element"
  • How to target nodes with "selectors"
  • The basic methods for finding/adding/removing and altering DOM nodes
  • The difference between a "nodelist" and an "array of nodes"

Study

Watch An Introduction to Browser Rendering.

Duration: 8 minutes

Read: What, exactly, is the DOM?

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="wrapper">
  <div class="main"></div>
  <div class="secondary"></div>
</div>

In the above example, the <div class="main"></div> is a "child" of <div id="wrapper"></div> and a sibling to <div class="secondary"></div>. Think of it like a family tree. <div id="wrapper"></div> is a parent, with its children on the next level, each on their own "branch".

How to Target Nodes with Selectors

If we want to target a specific node that we want to work with then we use the selectors. These selectors can be a combination of CSS-style selectors and relationship properties.

We will start with the CSS-style selectors. If we want to target the <div class="main"></div> node then we could use the following selectors:

  • div.main
  • .main
  • #wrapper > .main
  • div#wrapper > div.main

In addition we can use relational selectors (i.e. firstElementChild or lastElementChild etc.) with special properties owned by the nodes.

const wrapper = document.querySelector('#wrapper');
// select the #wrapper div (don't worry about the syntax, we'll get there)

console.dir(wrapper.firstElementChild);                      
// select the first child of #wrapper => .main

const secondary = document.querySelector('.secondary');   
// select the .secondary div

console.dir(secondary.previousElementSibling);                  
// selects the prior sibling => .main

Basically in the examples above we identifying a certain node based on its relationship to the other nodes around it.

Introduction to DOM Methods

As stated before when the HTML code is parsed by a web browser it is converted to the DOM. Those nodes are objects that have many methods and properties attached to them. Therefore we use those methods and properties to manipulate our web page with JavaScript.

We will start with the query selectors and those selectors help us to target nodes.

Target an Element with Query Selectors

  • element.querySelector(selector) returns reference to the first match of selector
  • element.querySelectorAll(selectors) returns a "nodelist" containing references to all of the matches of the selectors

*There are several other, more specific queries, that offer potential (marginal) performance benefits, but we won’t be going over them now.

Important note: When using querySelectorAll, the return value is not an array. Although it might look an array it somewhat acts like an array, but it’s really a "nodelist". The big difference is that several array methods are missing from nodelists. One solution is to convert the nodelist into an array. You can do this with Array.from() or the spread operator.

Create Elements

  • 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');

Keep in mind that this function does NOT put your new element into the DOM – it simply creates it in memory. Thus before you place the element to the DOM you can add styles, classes, ids, text etc.

Then you can add that element to the DOM with the following methods.

Append Elements

  • parentNode.appendChild(childNode) appends childNode as the last child of parentNode
  • parentNode.insertBefore(newNode, referenceNode) inserts newNode into parentNode before referenceNode

Remove Elements

  • parentNode.removeChild(child) removes child from parentNode on the DOM and returns reference to child

Altering Elements

We can also alter the element’s own properties. In other words when we have a reference to an element we can use that reference and do many alterations to the element itself, such as add or remove an attribute, change classes, adding styles and much more.

Let’s see an example. First we will create a div element.

const div = document.createElement('div');                     
// create a new div referenced in the variable 'div'

In the following sections we will use that div to do several alterations to it.

Inline style

Now we will add the color purple and the background color gray in to our previously created div.

div.style.color = 'purple';                                      
// adds the indicated style rule

div.style.cssText = 'color: purple; background: gray';          
// adds several style rules

div.setAttribute('style', 'color: purple; background: gray');    
// adds several style rules

For further information on inline styles see the DOM Enlightenment’s section on CSS Style rules.

Please be aware 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.

For example:

div.style.background-color // doesn't work - attempts to subtract color from div.style.background
div.style.backgroundColor // accesses the divs background-color style
div.style['background-color'] // also works
div.style.cssText = "background-color: white" // ok in a string

Attributes

Now let’s see how we can edit an attribute of an element.

div.setAttribute('id', 'myDiv');                              
// if id exists update it to 'myDiv' else create an id
// with value "myDiv"

div.getAttribute('id');                                        
// returns value of specified attribute, in this case
// "myDiv"

div.removeAttribute('id');                                     
// removes specified attribute

For further information on available attributes see MDNs section on HTML Attributes.

Classes

Now we will see some examples of methods we use with the class that our element might has.

div.classList.add('content');                                      
// adds class "content" to our new div

div.classList.remove('content');                                   
// remove "content" 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 more clean) to toggle a CSS style rather than adding and removing inline CSS.

Text content

Now we will see how we add text content to our element.

div.textContent = 'My first content!'                               
// creates a text node containing "Hello World!" and
// inserts it in div

HTML content

Now we will see how we add HTML content to our element.

div.innerHTML = '<span>My first content!</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. For further information about this topic you can check this article.

Now let’s see an example as a review of what we have covered so far. So in the example we will create a DOM element and we will append it to a webpage.

<!-- HTML FILE -->
<body>
  <h1>
    HERE IS THE TITLE OF YOUR WEBPAGE
  </h1>
  <div id="wrapper"></div>
</body>
// JAVASCRIPT FILE
const wrapper = document.querySelector('#wrapper');

const container = document.createElement('div');
container.classList.add('content');
container.textContent = 'This is our text-content!';

wrapper.appendChild(container);

First we get a reference to the wrapper div that already exists in our HTML. Then we create a new div and store it in the variable container. We add a class and some text to the container div and finally append that div to wrapper.

So, after the JavaScript code is run, our DOM tree will look like this:

<!-- DOM -->
<body>
  <h1>
    HERE IS THE TITLE OF YOUR WEBPAGE
  </h1>
  <div id="wrapper">
  	<div class="content">
      This is our text-content!
    </div>
  </div>
</body>

Something to keep in mind is that the Javascript doesn’t alter our HTML. The HTML file will look the same but the JavaScript changes what the browser renders.

Important note: JavaScript, for the most part, is run whenever the JS file is run, or when the script tag is encountered in the HTML. Therefore, If you include your JavaScript at the top of your file, many of these DOM manipulation methods will not work. This is due to the fact that the JS code is being run before the nodes are created in the DOM. So, the simplest way to fix this is to include your JavaScript at the bottom of your HTML file. In that way the Javascript runs after the DOM nodes are parsed and created.

Additional Resources

In this section you can find a lot of helpful links to other content. This is a supplemental material for you if you want to dive deeper into some concepts.


Introduction to Events

In the previous topic we learned how we can manipulate the DOM. So in this topic we will learn how we can make that happen dynamically. The way we do it is using events. Events are actions that occur on our web page. For example a mouse-click or a keypress are events and we can use JavaScript to make our web page to listen and react to those events.

There are three primary ways to handle the events:

  1. Attach functions attributes directly on the HTML elements
  2. Set the on_event_ property on the DOM object in our JavaScript
  3. Attach event listeners to the nodes in our JavaScript

The third way with the event listeners is definitely the preferred method. However we will regularly see the others in use, so we’re going to cover all three.

Learning Objectives

  • How "events" and "listeners" work.
  • The three ways to use events in our code
  • How "bubbling" works.

Basic Example

Now we are going to see a basic example using all three methods. So we are going to create 3 buttons that all alert "BUTTON" when clicked.

You can try them all out using your own HTML file, or using something like CodePen.

Method 1 (Not recommended)

<button onclick="alert('BUTTON')">Click Me</button>

This way is less than ideal because we are cluttering our HTML with JavaScript. In addition, we can only have 1 "onclick" event per element.

Method 2 (Not recommended)

<!-- HTML FILE -->
<button id="btn">Click Me</button>
// JAVASCRIPT FILE
const btn = document.querySelector('#btn');
btn.onclick = () => alert("BUTTON");

*If you need further assistance with the arrow functions check this link: Arrow Functions Basics.

This way is a little better since we have moved the JS out of the HTML and into a JS file. However we still have the problem that a DOM element can only have 1 "onclick" property.

Method 3 (Recommended)

<!-- HTML FILE -->
<button id="btn">Click Me Too</button>
// JAVASCRIPT FILE
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
  alert("BUTTON");
});

This way is much better since we have our HTML and JavaScript separated and we also allow multiple event listeners if the need arises. Although it is a bit more complex to set up, it is more flexible.

We can also use these methods with named functions if we want to.

<!-- HTML FILE -->
<!-- METHOD 1 -->
<button id="btn" onclick="alertFunction()">CLICK ME AGAIN</button>
// JAVASCRIPT FILE
const btn = document.querySelector('#btn');
function alertFunction() {
  alert("BUTTON");
}

// METHOD 2
btn.onclick = alertFunction;

// METHOD 3
btn.addEventListener('click', alertFunction);

It is a really good idea to name the functions. The code is cleaner and it is really handy if you are going to use it in multiple places.

If we want we can access more information about the events. We can do that by passing a parameter to the function we are calling. Let’s see how we can do it.

// JAVASCRIPT FILE
btn.addEventListener('click', function (e) {
  console.log(e);
});

*Note that function (e) is a callback from addEventListener. If you need further assistance with callbacks you can check this link Callbacks.

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 target refers to the DOM node that was clicked.

So we can access the button by accessing the target property as:

// JAVASCRIPT FILE
btn.addEventListener('click', function (e) {
  console.log(e.target);
});

and then we can change the background color of the button as:

// JAVASCRIPT FILE
btn.addEventListener('click', function (e) {
  e.target.style.background = 'purple';
});

Attaching listeners to groups of nodes

In case we want to attach similar event listeners to many elements there is a way to make our code more efficient rather than writing the same code over and over again.

In previous topics we learned that we can get a nodelist of all the items matching a specific selector. We can do that with the querySelectorAll('selector'). So in order to add a listener to all of them all we have to do is to iterate through the entire list.

<!-- HTML FILE -->
<div id="wrapper">
    <button id="1">Click Me</button>
    <button id="2">Click Me</button>
    <button id="3">Click Me</button>
</div>
// JAVASCRIPT FILE

// buttons is a node list. It looks and acts much like an array.
const btns = document.querySelectorAll('button');

// we use the .forEach method to iterate through each button
btns.forEach((btn) => {

  // and for each one we add a 'click' listener
  btn.addEventListener('click', () => {
    alert(btn.id);
  });
});

What we have learned so far in regards to DOM manipulation and event handling is just the tip of the iceberg. However you have learned enough so as to complete some exercises.

In our previous examples we have used the click event but there are a lot more available to you!

Below is a list of some useful events:

  • click
  • dblclick
  • keypress
  • keydown
  • keyup

In this page HTML DOM Events you will find a more complete list of events as well as an explanation of each one.

Avoiding Confusion

As you’ve seen, you can define an event handler in 3 different ways.

  1. Using the addEventListener method (Recommended)
document.querySelector(".box").addEventListener( "click", function( event ){ ... });
document.querySelector("form").addEventListener( "submit", function( event ){ ... });
document.body.addEventListener( "keyup", function( event ){ ... });
  1. Using the on<EVENTNAME> property syntax (Not recommended)
document.querySelector(".box").onclick = function( event ){ ... };
document.querySelector("form").onsubmit = function( event ){ ... };
document.body.addEventListener.onkeyup = function( event ){ ... };
  1. Using an attribute with the on<EVENTNAME> directly in an HTML element (Not recommended):
<div class="box" onclick="clickHandler()">Click me</div>
<form onsubmit="submit()">
    <!-- CONTENT -->
</form>
<body onkeyup="handleKey()">

Events in the first syntax are used like this (nouns): ‘click’, ‘submit’, ‘change’, ‘scroll’, etc.

While using the 2nd syntax, are prefixed with the ‘on’ keyword: ‘onclick’, ‘onsubmit’, ‘onchange’, ‘onscroll’, etc.

The second syntax corresponds to element properties and should not be confused with the event names used in the first case.

Additional Resources

In this section you can find a lot of helpful links to other content. This is a supplemental material for you if you want to dive deeper into some concepts.

Leave a Reply