Interactive Narrative: Intermediate JavaScript

Celeste Layne
Programming for Design Practices
11 min readJun 25, 2020

--

JavaScript in the Browser

HTML gives us a nested tree structure of elements. JavaScript allows us to manipulate these elements.

The Document Object Model (DOM)

The Document Object Model, commonly referred to as the “DOM”, is a programming interface for HTML. When you load HTML into the browser, it gets converted into a dynamic object-based structure.

The visual representation of this is what you see when you open up Developer Tools in the browser. The DOM is available for us to manipulate as an object, and this object is structured and stored like an upside down tree.

Basics of Working with the DOM

Understanding the DOM is central to working in JavaScript. JavaScript uses the DOM to create dynamic HTML.This includes adding new HTML elements and attributes, changing CSS styles in a page, as well as removing existing elements and attributes.

JavaScript and the Browser, Chapter 13. Eloquent Javascript.

Accessing Elements

When it comes to using JavaScript to manipulate to DOM, there are usually two types of elements someone would need to deal with, existing and non-existing. First, let’s look at accessing and manipulating existing elements.

Getting

To use the getElementById method, we first need to reference the document object (where the method lives). Then, we pass in a string that matches the ID of an element in our HTML.

let titleElement = document.getElementById('title')

The above code will start at the document (top of the tree), and look for an element with an id called title and save it to the variable titleElement. If there is no element with the given id, this function returns null.

Query Selector

There are only two methods in this group: querySelector and querySelectorAll. querySelector returns a single value, and querySelectorAll returns everything that it can find. You can even select multiple IDs this way.

querySelector

With querySelector, we pass in a CSS selector for the element we want to retrieve from the DOM. The element that we get back will be the first element that matches that selector.

let title = document.querySelector('.title')

We’ll only get one element back and it will always be the first element that matches the selector (in this case, .title). If we have more than one element in the page with that selector and we want to retrieve them all, then we'd use querySelectorAll()

querySelectorAll

The element method, querySelectorAll targets all the elements on the page matching the selector we pass in.

let title = document.querySelectorAll('h2')

Manipulating Elements

Now that we know how to get elements from the DOM, it’d probably be helpful to learn what we can do with them. Toggle, add or remove classes, change their styling, animate them, move them from one part of the page to another, replace their content with new content, etc. The list goes on!

1. Getting and Setting Attributes

Remember from our HTML lesson that some elements have attributes: the a tag has an href attribute and the img tag has a src attribute. In JavaScript, there are ways to access the list of attributes on a node and to get and set attributes.

Every node object has an attributes property where it lists it's attributes (like href and src). You can get and set data using the getAttribute and setAttribute method.

let div = document.querySelector(“div”);
div.setAttribute(“id”, “greeting”);

2. ClassList API

A very common task in JavaScript is toggling CSS classes. We’ll remove a .is-hidden class when the user clicks on something or we'll add an is-active class a navigation element when someone clicks on a hamburger menu.

The way we get and set classes on nodes is with the classList API. Every node has a classList property and there are methods we can use to add a class (addClass), remove a class (removeClass) or toggle a class (toggleClass).

let div = document.querySelector(“div”);div.classList.add(“visible”);
div.classList.remove(“visible”);

3. Content

Sometimes we have an element and want to change the text or html contained within that element. This is commonly called templating and there are libraries that will make it a little easier. With the new template literal syntax in ES6, we can often get away without a templating library. We could just use the list of properties below to reset the html or text of an element and interpolate data in to it.

let div = document.querySelector(“div”);
div.innerHTML = “<p>Hey!</p>”;

4. Change the Style

Something we may want to perform in JavaScript is updating or changing the styling of an element using JavaScript. A lot of web animation tools do that and there are tools for React (which we’ll learn about later) that do this so you can write all your styles in JavaScript.

let div = document.querySelector(“div”);
div.style[“width”] = “100px”;

Creating and Adding Elements

Now, let’s look at creating and manipulating new elements.

Step 1. Create a new element, using .createElement().

For example, if we need to create a list of items, we can do the following:

const listItem = document.createElement(“li”);

Step 2. Do something with that element.

After we create the element and make it available in JavaScript code, we can do whatever we want with it, for example we can add an attribute to it like this:

listItem.classList.add(“list-item”);

Using .classList.add() we’ve added a class to the list item element. HTML representation of this element now looks like this:

<li class=“list-item”></li>

The CSS for this element may look like the following:

.list-item {
color: #141414;
font-family: Georgia, 'Times New Roman', Times, serif;
font-style: italic;
}

Step 3. Append created element to HTML.

Since the “listItem” element was just created, it needs to become part of the DOM in order to show up on the page. Therefore, we need to append it to HTML using .appendChild():

div.appendChild(listItem);

The resulting HTML will look like the following:

<div>
<li class="list-item"></li>
<div>

Create element, MDN Documentation

Removing Elements

To remove the HTML element we just created from the DOM:

let deleteItem = div.removeChild(listItem);

Tutorial 13

Instructions

In this tutorial, we are going to create a list of images and iterate over them. Then, render the images to the web page.

Part One

  1. Create an empty array to hold a list of objects (elements must center around a theme). In this example, I’ve chosen modern architecture.
  2. Use an array helper method to add items to the empty array, e.g. push()
  3. Select the parent HTML element that you want to append the list items. In this example, it’s the element with the class, “bookmarks”

Part Two

  1. Write a function that will render the images you’ve pushed into the array, to the web page. e.g. const renderImages = () => {};
  2. Inside the function, create a new block level element to nest inside the <div class="bookmarks"> Note: images are inline level elements to they tend to stack next to each other.
  3. Append the new element to the DOM.
  4. Add each image to the new block level HTML element.
  5. Don’t forget to invoke your function.

Codepen Solution

Events

Wouldn’t it be nice if we could write code that allows elements on the webpage to respond to user interactions? The DOM not only lets us manipulate the document (or webpage) using JavaScript, but also gives us the ability to write JavaScript that responds to interactions with the page. These interactions are communicated as events. We can listen for certain kinds of user-driven events, such as clicking a button, entering data into a form, pressing a particular key on your keyboard, and more.

Types of Events

There are many events that can trigger a function. Here are some commonly used ones:

  • click — When the button (usually a mouse button) is pressed and released on a single element.
  • scroll— When the user scrolls up or down on a page.
  • keydown— When the user first presses a key on the keyboard.
  • submit— When the user submits a form.

Now that we have an idea of what DOM events are in theory, let’s wire up our code and begin interacting with them. There are two steps to working with events:

  1. We set up an event listener with .addEventListener
  2. We define an event handler, a function that get’s passed to .addEventListener

Step 1: Setting Up An Event Listener

In order to listen for an event, we need to define an event listener. Below you’ll find a simple event listener associated with a 'click' event on a button element.

First, we target the button with a class name js-button:

const button = document.querySelector('.js-button');

Then, we tell JavaScript to listen for an event:

button.addEventListener('click', handleClickEvent);

The first argument is the event, the second argument is the function (event handler) that will run once the button is clicked.

Step 2: Setting Up An Event Handler

For step two, we need to define the function that will be called whenever this event is emitted. This is just a function, but it has a special name due to how it’s being used — a callback function:

function handleClickEvent() {
console.log('I was clicked!');
}

The resulting code looks like the following:

  1. The button refers to the DOM node to which we want to tie the event
  2. Then, it attaches an event listener to the button element with the addEventListener() method
  3. The addEventListener() method takes two arguments:
    — The event we want to listen for
    — The function that should be called whenever that event is invoked.
  4. In the case of the code above, we’re saying we want to listen for click events on our button, and whenever someone does click on our button, call the handleClickEvent() function.

Handling Events, Eloquent JavaScript

This

The keyword this refers to the object that "owns" the function that the executed code runs within. It’s important to remember that, when we have a method that is inside an object, this refers to the object that contains that method.

Let’s look at an example where we’ll change the background color of a circle from blue to red, just by clicking on it.

When we click on the circle and trigger the turnRed function, this will refer to the element with the class circle within the turnRed function.

Here’s what that looks like in action:

CSS Transitions with JavaScript

You can trigger CSS transitions through JavaScript by adding or removing a class.

CSS

.button {
background-color: #33ae74;
transition: background-color 0.5s ease-out;
}
.button.is-active {
background-color: #1ce;
}

JavaScript

const button = document.querySelector('.button')button.addEventListener('click', _ => button.classList.toggle('is-active'))

Tutorial 14

Instructions

In this tutorial, we are going to change the color of the web page based on visitor input.

Part One

  1. Select #color-button from the page and add an event listener that triggers an event handler function, called changeColor;
  2. Write the function, named changeColor, that is called when #color-button is clicked

Part Two

  1. Inside of the function changeColor, use variables to store the <input> values from #red, #green, and #blue
    — Use .value to get the values from each input
    — Hint: let red = document.getElementById(‘red’).value;
  2. Create a variable, named colorStr, which concatenates the above red, green, and blue variables into the format: rgb(x, y, z)
    — If the user enters: 100, 150, and 200, colorStr should be: rgb(100, 150, 200)
  3. Use .innerHtml or .textContent to change the text inside of #colorful-text to colorStr
  4. Use .style.background to change the background of #container to colorStr

Codepen Solution

The Event Object

Now that we’ve gotten the hang of writing event handlers, let’s talk a bit about the event object.

When an event occurs, we might want to find out some information about it. For example, which element did the user interact with to cause the event? What type of event was it? A click event? A mouseover?

To obtain this information, we use the event object.

Accessing the Event Object

How do we gain access to the event object? First, we will need to pass the event object as a parameter.

const viewComments = (e) => {
console.log(e)
}

Now if we simply use whichever parameter name we chose (in our case, “e”) from within the function, we have access to the event object.

Preventing Default Behavior

As you may be able to tell from its name, preventDefault() allows us to prevent the default browser behavior for an event.

Some events, such as clicking on a link or submitting a form, are meant to take a user to another page. But maybe when a user clicks on a link or submits a form, we don’t want to take them to another page. Instead, we want to fade in some comments.

To do that, we could type:

HTML

<a href="#">View Comments</a>

JavaScript

document.querySelector('a').addEventListener('click', viewComments);

function viewComments(e) {
// Add a class that fades the comments in by changing the opacity to 1
let comments = document.querySelector('#comments')
comments.className = 'show-comments';
}

We want to override the default functionality of a link, and have comments appear instead of taking the user to another page. To prevent this default behavior, we can use the preventDefault() method:

let anchor = document.querySelector('a')
anchor.addEventListener('click', viewComments);

function viewComments(e) {
// Prevent page from jumping to top.
e.preventDefault();

let comments = document.querySelector('#comments')
comments.className = 'show-comments';
}

You’ll often use this method when you have anchors or submit buttons on a page that you want to provide with some JavaScript functionality, instead of having them take you to another page.

target

Here we access the target property of the event object using dot notation e.target. Then, log the target to the console.

let anchor = document.querySelector('a')
anchor.addEventListener('click', viewComments);

function viewComments(e) {
// access the target property of the event object using dot notation
let eventTarget = e.target;

// log the target to the console
console.log(eventTarget);
}

Let’s take a look at what we see in the console:

<a href="#">View Comments</a>

That’s the target of the event, or the element we clicked on that caused the event to fire.

Exercise 08

Life: A User’s Manual is Georges Perec’s most famous novel, published in 1978. The setting of Life: A User’s Manual is a fictitious Parisian apartment building at 11 Rue Simon-Crubellier, in the XVIIth arrondissement of Paris, at 8:00pm on June 23, 1975. In it, Perec describes the apartment block as if the entire façade were removed and every room exposed to the viewer. The narrative moves through the building, one chapter per room, detailing a single moment in time for each apartment’s resident. Perec created a system that would generate a list of items, references or objects that appear in each chapter, known as the “story-making machine”.

Using one of the architectural structures diagrammed in Layout Basics, create your own version of Life: A User’s Manual, weave a narrative that allows the visitor to navigate the building/floor/room with each telling its own story.

Instructions

Click on a button/element in each of the rooms and cause some aspect of the room to change: (a) trigger an animation, (b) change the color/pattern of the background, (c) play some audio or (d) causes the visitor to jump to a random page in the building/floor/room.

--

--