Lecture 9

Timers and DOM Element Creation

Agenda

  • HW2 out (link to video demo on homeworks page!)
  • CP2 due Wednesday
    • Any questions?
  • More on Timers in JS
  • Adding to the DOM in JS

Reminder: Two Handy Shortcut Functions

Two methods we will use a LOT are document.getElementById and document.querySelectorAll. It's handy to declare a shortcut to help us out. You may use the following in your JS programs (these are exceptions to the rule of having description function names):

function $(id) {
  return document.getElementById(id);
}

function qsa(query) {
  return document.querySelectorAll(query);
}

JS

We will start using these in lecture examples!

Animations in Programs

In programs, we often want to repeat some behavior - what can we do to repeat some behavior N times? (loops!)

In simple Java/Python programs, there isn't usually motivation to delay code execution

On webpages with JavaScript, there is often motivation to delay or repeat behavior every X seconds

  • What are some examples you can think of?

Counting Down: A Classic Loop Problem

function startCountDown() {
  let count = 10;
  for (let i = 10; i > 0; i--) {
    console.log(i + "...");
  }
  console.log("0!");
}

JS

This prints a countdown to the console as soon as it's called. But what if we want to delay each line printed by 1 second?

Timers

Used to delay or set intervals for executing functions

Setting a Timer

method description
setTimeout(responseFn, delayMS) Arranges to call given function after given delayMS, returns timer id
setInterval(responseFn, delayMS) Arranges to call function repeatedly every delayMS ms, returns timer id
clearTimeout(timerID)
clearInterval(timerID)
Stops the given timer

Both setTimeout and setInterval return an ID representing the timer

  • This is not the same as a DOM id! It's a unique identifier the window has access to in order to manage the page timers.
  • If you have access to the id, you can tell the window to stop that particular timer by passing it to clearTimeout/Interval later (e.g. when clicking a "stop timer" button)

setTimeout Example

<button id="demo-btn">Click me!</button>
<span id="output-text"></span>

HTML

function initialize() {
  $("demo-btn").addEventListener("click", delayedMessage);
}

function delayedMessage() {
  $("output-text").innerText = "Wait for it...";
  setTimeout(sayHello, 5000);
}

function sayHello() { // called when the timer goes off
  $("output-text").innerText = "Hello!";
}

JS

output (full example page)

setInterval Example

<button id="demo-btn">Click me!</button>
<span id="output-text"></span>

HTML

let timer = null; // stores ID of interval timer
function repeatedMessage() {
  timer = setInterval(sayHello, 1000);
}

function sayHello() {
  $("output-text").innerText += "Hello!"
}

JS

output (full example page)

More details on timer variable on slide below

Motivating the timer variable

  • We sometimes need to keep track of our timer(s) when managing them between functions so we can use clearInterval/clearTimeout or know if we have a timer already running on our page.
  • When we can't keep track of them as local variables, it is good practice to store them as module-global variables (within the scope of the module pattern, but accessible to all functions in your program).
  • These examples will assume we are writing inside a module pattern for brevity, but you can refer to the full examples (linked on slides).

clearInterval Example

<button id="toggle-btn">Start/Stop<button>

HTML

let timer = null; // stores ID of interval timer
function initialize() {
  $("toggle-btn").addEventListener("click", toggleMessageInterval);
}

// 1. What does this function do?
function toggleMessageInterval() {
  if (timer === null) {
    timer = setInterval(sayHello, 1000);
  } else {
    clearInterval(timer);
    timer = null; // 2. Why is this line important?
  }
}

function sayHello() {
  $("output-text").innerText += "Hello!"
}

JS

output (full example page)

Back to our Countdown Example

function startCountDown() {
  let count = 10;
  for (let i = count; i > 0; i--) {
    console.log(i + "...");
  }
  console.log("0!");
}

JS

countdown in console

Recall that this function prints each line immediately (in order). If we want to output each line every 1 second (1000ms), what kind of timer should we use?

Timed Countdown: An Initial Attempt

let timer = null;
function startCountDown() {
  let i = 10;
  timer = setInterval(function() {
    console.log(i + "...");
    i--;
  }, 1000);
  console.log("0!");
}

JS

What's wrong here? (remember we want a 10 second countdown printed to the console)

A Better Attempt

let timer = null;
function startCountDown() {
  let i = 10;
  timer = setInterval(function() {
    if (i === 0) {
      console.log("0!");
    } else {
      console.log(i + "...");
      i--;
    }
  }, 1000);
}

JS

This is closer! But there's still something wrong...

Our timer won't stop when we reach 0!

A Solution

let timer = null;
function startCountDown() {
  let i = 10;
  timer = setInterval(function() {
    if (i === 0) {
      clearInterval(timer);
      timer = null;
      console.log("0!");
    } else {
      console.log(i + "...");
      i--;
    }
  }, 1000);
}

JS

This is a working solution! When startCountDown is called, we assign a new interval to our timer and start a 1-second countdown at 10.

When we reach 0, we need to clear the interval from the window's tasks and set our timer to null to indicate when our timer is finished (if we want to start a new timer later).

Passing Optional Parameters to Timers

function delayedMultiply() {
  // 6 and 7 are passed to multiply when timer goes off
  setTimeout(multiply, 2000, 6, 7);
}

function multiply(a, b) {
  alert(a * b);
}

JS

Any parameters after the delay are eventually passed to the timer function

  • Doesn't work in IE; must create an intermediate function to pass the parameters

Why not just write this?

setTimeout(multiply(6 * 7), 2000);

JS

Common Timer Errors

Many students mistakenly write () when passing the function

setTimeout(booyah(), 2000);
setTimeout(booyah, 2000);

setTimeout(multiply(num1 * num2), 2000);
setTimeout(multiply, 2000, num1, num2);

JS

What does it actually do if you have the ()?

  • It calls the function immediately, rather than waiting the 2000ms!

Note: This is also a common bug with addEventListener!

Summary

When you want to call a function after a specified delay in time, use setTimeout.

When you want to call a function repeatedly every X seconds, use setInterval (though you can also use setTimeout recursively!)

For both types of timers, you'll need a variable with the timer id (returned by both functions) to pass to clearTimeout/clearInterval when you want to stop the delay/interval.

Changing the DOM with JS

Recall: How to get DOM elements in JS

  1. Ask for them by id: document.getElementyById(...)
  2. Query for them with CSS style selectors:
    • document.querySelector(...)
    • document.querySelectorAll(...)
  3. Make new ones! document.createElement(...) (introduced now!)

Selecting Groups of DOM Objects

Methods in document and other DOM objects:

Name Description
querySelector(selector) returns the first element that would be matched by the given CSS selector string
querySelectorAll(selector) returns an array of all elements that would be matched by the given CSS selector string
getElementsByName(name) returns array of descendants with the specified name (mostly useful for accessing form controls)

Getting all elements of a certain type

Use querySelectorAll to highlight all paragraphs in the document:

<body>
  <p>This is the first paragraph</p>
  <p>This is the second paragraph</p>
  <p>You get the idea...</p>
</body>

HTML

// get all DOM objects that are <p>
let allParas = document.querySelectorAll("p");
for (let i = 0; i < allParas.length; i++) {
  allParas[i].classList.add("highlighted");
}

JS

This is the first paragraph

This is the second paragraph

You get the idea...

output

Complex selectors: Taking what we know from CSS Selectors!

Let's say we want to highlight all paragraphs inside of the section with ID 'address':

<p>This won't be highlighted!</p>
<div id="address">
  <p>1234 Street</p>
  <p>Seattle, WA</p>
</div>

HTML

We can use querySelectorAll to return an array of all the elements we want. What is the CSS query we would use to select all the paragraphs we want? (#address p)

CSS Selector Mystery practice if needed: here and here and here

Complex Selectors with querySelectorAll

<p>This won't be highlighted!</p>
<div id="address">
  <p>1234 Street</p>
  <p>Seattle, WA</p>
</div>

HTML

let addrParas = document.querySelectorAll("#address p");
for (let i = 0; i < addrParas.length; i++) {
  addrParas[i].style.backgroundColor = "yellow";
}

JS

This won't be highlighted!

1234 Street

Seattle, WA

output

Common querySelectorAll issues

Many students forget to write . or # in front of a class or id

// get all buttons with a class of "control"
let gameButtons = document.querySelectorAll("control");
let gameButtons = document.querySelectorAll(".control");

JS

querySelectorAll returns an array, not just a single element;
you must loop over the results

// set all buttons with a class of "control" to have red text
document.querySelectorAll(".gamebutton").classList.add("hidden");
let gameButtons = document.querySelectorAll(".gamebutton");
for (let i = 0; i < gameButtons.length; i++) {
  gameButtons[i].classList.add("hidden");
}

JS

Q: Can I still select a group of elements using querySelectorAll even if my CSS file doesn't have any style rule for that same group? (A: Yes!)

Getting the first element of a certain type

Use querySelector to highlight the first paragraph in the document:

<body>
  <p>This is the first paragraph</p>
  <p>This is the second paragraph</p>
  <p>You get the idea...</p>
</body>

HTML

// get all DOM objects that are <p>
let para = document.querySelector("p");
para.classList.add("highlighted");

JS

This is the first paragraph

This is the second paragraph

You get the idea...

output