Skip to content

Instantly share code, notes, and snippets.

@leahgarrett
Last active May 22, 2019 02:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leahgarrett/26960ecdf95cdad89b9c2eb53332e1f8 to your computer and use it in GitHub Desktop.
Save leahgarrett/26960ecdf95cdad89b9c2eb53332e1f8 to your computer and use it in GitHub Desktop.
DOM & DOM Node Manipulation

DOM & DOM Node Manipulation

Learning outcomes

  • Web API
  • DOM
  • DOM & DOM Node Manipulation
  • Event Listeners

What is Web API?

An API is some kind of interface which has a set of functions that allow programmers to access specific features or data of an application, operating system or other services.

Web API can mean an API used on the web.
eg: Microsoft's ASP .Net Web API

We will use it in the scope of the browser:
MDN Article on browser Web APIs

In particular we will be looking at Web API for manipulating the DOM.


What is the DOM?

DOM stands for Document Object Model.

The Document Object Model is what is known as a programming interface for HTML. It basically means that the HTML page can be represented as objects to allow our programming language (in this case JavaScript) to interact with the page.


Is the HTML you write is the DOM?

The HTML you write is not really the DOM but it is parsed by the browser and turned into the DOM.

Is the code we see in Chrome’s dev tools the DOM?

Kinda. When your looking at the code in dev tools that looks like HTML it is actually just a visual representation of the DOM.

Hmmm but it looks exactly like our HTML and you just said that the HTML we write is not the DOM so what gives?

The HTML we write is used to build the DOM initially but it can change during parsing (like when you forget to put a closing tag and the browser does it for you).

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <p>
        My paragraph.
</body>
</html>

Another way that the DOM if often manipulated is through JavaScript.

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <p id="my-paragraph"></p>

    <script>
        let p = document.querySelector("#my-paragraph");
        p.innerHTML = "Some sample text";
    </script>
</body>
</html>

Similar example represented in the CSS Tricks Article - What is the DOM?


Manipulating The DOM

We will use the below HTML code to load up a web page that we will be using to practice our DOM manipulation.

<!DOCTYPE html>
<html>
<head>
    <style>
        .blue {
            color: blue;
        }
    </style>
</head>
<body>
    <h1>Welcome</h1>
    <p>This page is used for practicing DOM manipulation</p>
    <div>
        <p>We will be learning</p>
        <ul>
            <li class="odd">Adding To The DOM</li>
            <li>Querying The DOM</li>
            <li class="odd">Changing The DOM</li>
            <li>Event Listeners</li>
        </ul>
    </div>
    <form id="page-form">
        <input type="text" />
        <input type="submit" value="Click Me" />
    </form>
    <script>
        // javascript code goes here
    </script>
</body>
</html>

Adding To The DOM

We can create new DOM objects in JavaScript.

let newDiv = document.createElement("div");

Once we create our new DOM node we can manipulate it the same as if we retrieve a node directly from the DOM.

newDiv.innerHTML = "Awesome div text";

From there we can append our new node to the DOM.

document.body.appendChild(newDiv);

You may also have heard of document.write() to add html elements to the screen but this should be used with caution because if you call document.write() after an HTML page has been fully loaded with will replace all of the HTML on the page.

document.write("<h1>Hello There</h1>");

Querying The DOM

Another very useful concept is being able to manipulate DOM nodes that are loaded in the browser. We can access these nodes by querying the DOM.

If we want to get the first element that matches a given CSS selector we can use querySelector().

let firstP = document.querySelector("p");
console.log(firstP);

let form = document.querySelector("#page-form");
console.log(form);

If we would like to access all nodes that match a given CSS selector we can use querySelectorAll().

let allPs = document.querySelectorAll("p");
console.log(allPs);

let odds = document.querySelectorAll(".odd");
console.log(odds);

Did you notice anything odd in the console when we logged out our results from querying?

What data type was logged? A NodeList?

Yep when we use querySelectorAll() it is return to us as a NodeList, so we don’t have access to all the usual features of an actual Array. However in ES6 we can easily convert this NodeList to an Array.

let allPsArray = Array.from(allPs);
console.log(allPsArray);

If we already have a node and need a child from it we can query the node instead of the whole document.

let div = document.querySelector("div");
let divPs = div.querySelectorAll("p");
console.log(divPs);

The querySelector() and querySelectorAll() methods are relatively new. Before them we had getElementById(), getElementsByClassName and getElementsByTagName().

let form2 = document.getElementById("page-form");
console.log(form);

let odds2 = document.getElementsByClassName("odd");
console.log(odds2);

let allPs2 = document.getElementsByTagName("p");
console.log(allPs2);

You will still see these methods used throughout developers JavaScript code. So why do developers use the methods instead of the newer query selector ones? One big difference is between querySelectorAll() and getElementsByClassName() / getElementsByTagName(). The querySelectorAll() method is not live meaning as we add nodes that match the selector the query will not automatically update to include these new nodes.

let li1 = document.querySelectorAll("li");
let li2 = document.getElementsByTagName("li");

console.log(li1);
console.log(li2);

let newLi = document.createElement("li");
newLi.textContent = "new li";
document.querySelector("ul").appendChild(newLi);

console.log(li1);
console.log(li2);

As we can see li2 now has 5 elements in it where li1 still has the original 4.

The querySelectorAll() method is actually less performant as well since it immediately gathers a static list of the nodes properties.


Removing From The DOM

If we would like to remove a node from the DOM we cannot do so directly but we can remove children from a parent element.

let li3 = document.querySelector("li");
let ul = document.querySelector("ul");

ul.removeChild(li3);

Modifying Classes & Attributes

Adding and removing classes from a node is very easy.

let title = document.querySelector("h1");

title.classList.add("blue");
title.classList.remove("blue");
title.classList.toggle("blue");

Modifying attributes is also easy. We can just access and change them like any other property on an object.

let formButton = document.querySelector("input[type=submit]");

console.log(formButton.value);

formButton.value = "Don't click me!");

//We can assign multiple attributes at once using Object.assign()

Object.assign(formButton, {
    id: "form-button",
    value: "ok you can click"
});

Adding & Retrieving CSS Styles

CSS styles can be applied to a node just like any other property, the big gotcha is instead of dashes we camel case the names of the style properties.

title.style.paddingLeft = "100px";

If we want to retrieve the value of a certain style property using .style will only give us a value if the style has been applied directly to the node. However it will not give us any computed style such as from a class or styling inherited from its parent.

title.classList.add("blue");
console.log(title.style.color);

We can retrieve this value using window.getComputedStyle().

console.log(window.getComputedStyle(title).getPropertyValue("color"))
;

Element Properties

The last thing we will discuss about our node elements are a couple of properties that we always have access to. Those are innerHTML and textContent (Note: there is also a property called innerText but it should be avoided as it is a non standard property that was introduced in Internet Explorer and moved over to WebKit browser to avoid compatibility issues).

They are both writable properties meaning we can modify their content directly.

let ul = document.querySelector("ul");
console.log(ul); //ul element
console.log(ul.innerHTML); //html inside the ul element

Because this property is writable we can add HTML directly to it.

ul.innerHTML += `<li>New li element</li>`;

Or we can remove all of the innterHTML by setting it to null

ul.innerHTML = null;

Event Listeners

Event listeners are a Web API that allows us to listen for events on DOM nodes. There are many different kinds of events that we can listen for.

Some of the categories of events that can occur in a browser

  • mouse events
  • keyboard events
  • drag and drop events
  • view events
  • form events

Events reference:
https://developer.mozilla.org/en-US/docs/Web/Events

We will take a look at some of the more common events and how we can add a listener to this event or remove them.

Adding An Event Listener

To add an event listener simply call addEventListener() on the DOM node which you would like it added to.

let myButton = document.querySelector("input[type=submit]");

myButton.addEventListener("click", function (event) {
    console.log(event);
});

You will notice that we try to run the listener but at the same time the browser has some default behaviour for an input of type submit inside of form.

What is the default behaviour?

To disable the browsers default behaviour inside of the listener we can call event.preventDefault(). This works because our custom listeners will run before any default browser listener.

//Refresh the web page to remove the first listener

let myButton = document.querySelector("input[type=submit]");

myButton.addEventListener("click", function (event) {
    event.preventDefault();
    console.log(event);
});

Now when we click the button our event listener runs and prevents the default behaviour of the browser allowing us to log to the console the event and not having the form actually submit. And there we go we have created our first event listener!

Another good thing to now is that we can access the DOM node the event listener was fired on by using the target property on our event.

//Refresh the web page to remove the first listener

let myButton = document.querySelector("input[type=submit]");

myButton.addEventListener("click", function (event) {
    event.preventDefault();
    console.log(event.target);
});

And because this brings back a DOM node we have all the same functionality as we had before to access and manipulate it’s properties.

//Refresh the web page to remove the first listener

let myButton = document.querySelector("input[type=submit]");

myButton.addEventListener("click", function (event) {
    event.preventDefault();
    event.target.value += "!";
});

We can also use this to access the the DOM node unless we are using a fat arrow function in the listener (fat arrow functions have a different scope for the this keyword).


Event Bubbling

Event bubbling is where multiple events are invoked at once on different DOM elements. Here is an example

let div = document.querySelector("div");
let p = div.querySelector("p");

div.addEventListener("click", function (event) {
    alert("Div clicked");
});

p.addEventListener("click", function (event) {
    alert("P clicked");
});

//Looking at our DOM how many alerts will fire?
//Which order would they be fired in if there is more than one?

When you do click on the paragraph inside of the div we first see “P clicked" but immediately after that we get “Div clicked". This is called event bubbling. The event will always start from the inner most child and bubble up to its parents. We can stop this bubbling effect by using stopPropagation().

let div = document.querySelector("div");
let p = div.querySelector("p");

div.addEventListener("click", function (event) {
    alert("Div clicked");
});

p.addEventListener("click", function (event) {
    event.stopPropagation();
    alert("P clicked");
});

//Looking at our DOM how many alerts will fire?
//Which order would they be fired in if there is more than one?

Removing Event Listeners

Event listeners can be removed by referencing the DOM node, event type and the callback function to be removed.

We can mimic our once config option behaviour by doing using this concept.

let div = document.querySelector("div");
let p = div.querySelector("p");

div.addEventListener("click", function divClick (event) {
    alert("Div clicked");
    div.removeEventListener("click", divClick);
});

p.addEventListener("click", function (event) {
    alert("P clicked");
});

We can also remove event listeners from one event by clicking on another.

let li1 = document.querySelector("li:nth-child(1)");
let li2 = document.querySelector("li:nth-child(2)");

function liClick (event) {
    alert("li was clicked");
};

li1.addEventListener("click", liClick); 

li2.addEventListener("click", function (event) {
    li1.removeEventListener("click", liClick);
    alert("remove li1 event");
}); 

Challenges

  1. In this part we will create a HTML page to display, sort and filter the inventors array from Array method review questions
  • Create function called displayInventors to display the inventors in array in the browser. It will take the inventors array as a parameter. Each attribute of the inventor should be displayed as well as the yearsLived.
  • Call the displayInventors function so the array is displayed as the page loads
  • At the top of the page add a link
    <a href="#" id-="bornIn1500s">Born In 1500s</a>
  • Add an event listener for click on this link

  • This event handler will filter the array and display the results using the displayInventors function

  • Add links and corresponding click event handlers for

    • sort by birth ascending
    • sort by birth descending
    • sort by years lived ascending
    • sort by years lived descending
    • sort by surname ascending
    • sort by surname descending
  1. In this part we will create a HTML page to display, sort and filter the following products array
    rrp is Recommended Retail Price savings = rrp - price
const products = [
  { name: 'Warty Warthog', rrp: 9.99, price: 4.10 },
  { name: 'Hoary Hedgehog', rrp: 19.99, price: 14.10 },
  { name: 'Breezy Badger', rrp: 22.99, price: 12.10 },
  { name: 'Dapper Drake', rrp: 15.99, price: 5.10 },
  { name: 'Edgy Eft', rrp: 99.99, price: 45.44 },
  { name: 'Feisty Fawn', rrp: 19.99, price: 11.10 },
  { name: 'Gutsy Gibbon', rrp: 39.99, price: 34.33 },
  { name: 'Hardy Heron', rrp: 9.19, price: 4.90 },
  { name: 'Intrepid Ibex', rrp: 33.33, price: 14.40 },
  { name: 'Karmic Koala', rrp: 23.45, price: 14.22 },
  { name: 'Lucid Lynx', rrp: 19.99, price: 14.66 },
  { name: 'Maverick Meerkat', rrp: 22.99, price: 24.10 },
  { name: 'Natty Narwhal', rrp: 49.99, price: 44.40 },
  { name: 'Oneiric Ocelot', rrp: 79.99, price: 47.10 },
  { name: 'Precise Pangolin', rrp: 89.99, price: 24.10 },
  { name: 'Quantal Quatzal', rrp: 39.99, price: 34.10 },
  { name: 'Raring Ringtail', rrp: 29.99, price: 14.10 },
  { name: 'Saucy Salamander', rrp: 39.99, price: 19.90 },
  { name: 'Trusty Tahr', rrp: 69.99, price: 34.10 },
  { name: 'Utopic Unicorn', rrp: 29.99, price: 14.40 },
  { name: 'Vivid Vervet', rrp: 39.99, price: 15.50 },
  { name: 'Wily Werewolf', rrp: 89.99, price: 34.10 },
  { name: 'Xenial Xerus', rrp: 99.99, price: 84.10 },
  { name: 'Yakkety Yak', rrp: 99.99, price: 24.10 },
  { name: 'Zesty Zapus', rrp: 19.99, price: 14.50 },
];
  • Create a function to display the products and include the savings

  • Add links and corresponding click event handlers for

    • sort by highest to lowest price
    • sort by lowest to highest price
    • sort by name ascending
    • sort by name descending
    • sort by savings ascending
    • sort by savings descending
  • Display total savings

  • Add links and corresponding click events for the following filters

    • items under $20
    • items more the %50 off

Beast

  1. Use the Pagination class from Mondays challenge to add pagination to the pages you created above

  2. Canvas DOM and Web API Challenges

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment