📝
marcyannotes
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • Fullstack Software Engineering Curriculum
    • Overview
    • How-Tos
      • How To Code at Marcy: Code Style Guide
      • How to Do Short Response and Coding Assignments
      • How to Debug
      • How to PEDAC
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
      • How to Deploy on Render
    • Mod 0 - Command Line Interfaces, Git, and GitHub
      • Mod 0 Overview
      • Command Line Interfaces
      • Git & GitHub
      • Git Pulling & Merging
      • Git Branching & PRs
      • Pair Programming: BONUS
    • Mod 1 - JavaScriptFundamentals
      • Mod 1 Overview
      • Intro to Programming
      • Errors
      • Node & Node Modules
      • Variables, Functions & String Methods
      • Control Flow, typeof, and Math
      • Loops
      • Arrays
      • Objects
      • Higher Order Functions: Callbacks
      • Higher Order Functions: Array Methods
      • Regex
    • Mod 2 - HTML, CSS & the DOM
      • Mod 2 Overview
      • HTML
      • CSS
      • Accessibility (a11y)
      • The DOM
      • Events
      • Forms
      • The Box Model and Positioning
      • Flexbox
      • Grid & Media Queries
      • ESModules
      • LocalStorage
    • Mod 3 - Async & APIs
      • Mod 3 Overview
      • Promises
      • Fetch
      • Building a Fetching App
      • Async & Await
    • Mod 4 - Project Week!
      • Project Week Overview
    • Mod 5 - Object-Oriented Programming
      • Mod 5 Overview
      • Intro to OOP, Encapsulation, Factory Functions, and Closure
      • Classes
      • Private & Static
      • Has Many/Belongs To
      • Polymorphism
    • Mod 6 - Data Structures & Algorithms
      • Mod 6 Overview
      • Stacks & Queues
      • Nodes & Linked Lists
      • Singly & Doubly Linked Lists
      • Recursion
      • Trees
    • Mod 7 - React
      • Mod 7 Overview
      • Intro to React
      • Events, State, and Forms
      • Fetching with useEffect
      • Building a Flashcards App
      • React Context
      • Global Context Pattern
      • React Router
    • Mod 8 - Backend
      • Mod 8 Overview
      • Intro to Express
      • Building a Static Web Server with Middleware
      • Securing API Keys with Environment Variables
      • Building a RESTful API with MVC
      • SQL and Databases
      • JOIN (Association) SQL Queries
      • Knex
      • Your First Fullstack App!
      • Migrations & Seeds
      • Schema Design & Normalization
      • Hashing Passwords with Bcrypt
  • Code Challenge Curriculum
    • Unit 0
      • Lecture: Functions in JS
      • CC-00: Functions and Console Logs
      • CC-01: Conditionals
      • CC-02: Conditionals 2
    • Unit 1
      • CC-03: For Loops
      • CC-04: For Loops and Conditionals
      • CC-05: For Loops and Conditionals 2
    • Unit 2
      • CC-06: String Mutations
      • CC-07: Array Iteration
      • CC-08: String Mutation and Array Iteration
      • CC-09: Array Mutations
      • CC-10: Reading Objects
      • CC-11: Objects
      • CC-12: Objects
      • Unit 2 Diagnostic
    • Unit 3
      • Intro to PEDAC (and Algorithms)
      • validTime
      • fizzBuzz (array)
      • digitSumDifference
      • firstNotRepeating
      • compareEvenAndOddSum
      • countVowelConsonants
      • finalHP
      • canMakeTriangle
    • Unit 4
    • Unit 5
    • Unit 6
    • Unit 7
    • Unit 8
    • Sorting
Powered by GitBook
On this page
  • Event Driven Programming: Listen and React
  • addEventListener
  • Event Type
  • Event Handlers and the event Object
  • Challenge
  • Good to Be Aware of, Not to Use: Inline Handlers
  • Event Propagation
  • Event Delegation
  • Removing Event Listeners
  1. Fullstack Software Engineering Curriculum
  2. Mod 2 - HTML, CSS & the DOM

Events

PreviousThe DOMNextForms

Last updated 6 months ago

Follow along with code examples !

Table of Contents

Event Driven Programming: Listen and React

Imagine a website with a button. Each time you click on the button, something happens (maybe the color changes!). How would you program something like this?

In event-driven programming, we instruct our program to listen for events and react when the event is triggered.

An event can be triggered by:

  • clicking a button

  • moving your mouse

  • pressing a key on your keyboard

  • the webpage finishes loading

  • the window is resized

  • the user scrolls down the page

In JavaScript (and many other languages), we set up our program to react to events by "registering" a callback function called an event handler that is tied to an element and an event type.

0/basic-examples/index.js.js
// 1. Select the "target" element
const button = document.querySelector('button#click-me')

// 2. Add an event listener to invoke a callback when a 'click' event occurs
button.addEventListener('click', () => {
  console.log('a click event occurred!')
});

The terms "event listener" and "event handler" are often used interchangeably but technically they work together.

addEventListener

The addEventListener method is available on all elements in the DOM and is invoked with two values, an event type string and an event handler callback.

A single element can have multiple event listeners / event handlers.

button.addEventListener('click', () => {
  console.log('a click event occurred!')
});

button.addEventListener('mousemove', () => {
  console.log('a mousemove event occurred!')
});

Event Type

The first argument of .addEventListener() is a string that defines the event type to listen for such as:

  • "click" - an element was clicked

  • "mousemove" - the mouse moved over an element

  • "keydown" - a key was pressed down

  • "keyup" - a key was released

  • "submit" - a form was submitted

  • "input" - the value of an input, select, or textarea has changed

Event Handlers and the event Object

The second argument of addEventListener is an event handler, a callback function that is invoked when the specified event fires "on" the given element.

The handler will be invoked by addEventListener with an event object as an input. This event object has many useful properties / methods about the event, like the event.type and event.target:

const handleEvent = (event) => {
  console.log(`An event of type "${event.type}" occurred!`);
  console.log("It was triggered by:", event.target);
  console.log(event);
}

const button = document.querySelector('button#click-me');
button.addEventListener("click", handleEvent);

These two properties are perhaps the most important. They are on every event object regardless of the event type:

  • event.target — the Element that triggered the event.

Challenge

Suppose you had this event handler:

const changeToRandomColor = (event) => {
  // Generate a random color string
  const red = Math.floor(Math.random() * 256);
  const green = Math.floor(Math.random() * 256);
  const blue = Math.floor(Math.random() * 256);
  const color = `rgb(${red}, ${green}, ${blue})`

  // event.target is the element that "triggered" the event
  event.target.style.backgroundColor = color;
}

How would you trigger it to be invoked whenever a key was pressed anywhere on the page? What about if you moved your mouse over an element with the id mouse-area?

Solution
document.querySelector("#mouse-area").addEventListener('mousemove', changeToRandomColor)
document.body.addEventListener('keydown', changeToRandomColor)

Good to Be Aware of, Not to Use: Inline Handlers

You can also define event handlers inline directly in HTML:

<button onclick="console.log('hello world');">I have an inline handler!</button>

This is good to be aware of for when we get to React but you should NOT use this since we want to keep our JavaScript logic in our .js files and out of .html files.

Event Propagation

Propagation: the act or process of spreading something

Imagine we had the following structure:

<div id="outer">
  <div id="middle">
    <button id="inner">Click me!</button>
  </div>
</div>

When an event is triggered by an element (e.g. a button is clicked), that element and all of its parent elements can "hear" that event and can listen to/handle those events. We call this event propagation or bubbling.

This means that an event listener added to div#middle and div#outer will be triggered when we click on the button#inner element.

const testPropagation = (event) => {
  console.log(`Event triggered by: #${event.target.id} (event.target)`);
  console.log(`Handled by: #${event.currentTarget.id} (event.currentTarget)`);
}

document.querySelector('#inner').addEventListener('click', testPropagation);
document.querySelector('#middle').addEventListener('click', testPropagation);
document.querySelector('#outer').addEventListener('click', testPropagation);
  • event.target is the Element that triggered the event

  • event.currentTarget is the Element handling the event

With event propagation, the element handling the event (event.currentTarget) will be a parent of the event that triggered the event (event.target)

To prevent events from bubbling up, use the event.stopPropagation() method available on all events:

const testPropagation = (event) => {
  console.log(`Event detected on #${event.target.id}`);
  console.log(`Event handled by: #${event.currentTarget.id}`);
  event.stopPropagation()
}

Q: What would happen if we removed the event handlers for #inner and #middle?

Event Delegation

Delegation: the act of empowering to act for another.

Event propagation/bubbling allows a really powerful design pattern called event delegation. Suppose you had the following list:

<ul id="picture-list">
  <li><img src="cat1.webp" alt="a funny cat"></li>
  <li><img src="cat2.jpg" alt="a funny cat"></li>
  <li><img src="cat3.jpg" alt="a funny cat"></li>
</ul>

Each list item has a picture and a solid black border. As long as we have our mouse hovering over on an image, we want the border of that image (and only that image) to turn red! We can do that with an event listener like this:

const toggleBorder = (event) => {
  console.log(event.type + ' event detected on: ', event.target);
  console.log('event handled by: ', event.currentTarget);

  // toggle the highlight class (which will make the border red) on the closest li to the image
  event.target.closest('li').classList.toggle('highlight');
}

Now, to get that to work for all of our images, one solution would be to add mouseover and mouseout event handlers to every single image...

document.querySelector("#picture-list img:nth-child(1)").addEventListener('mouseover', toggleBorder);
document.querySelector("#picture-list img:nth-child(1)").addEventListener('mouseout', toggleBorder);
document.querySelector("#picture-list img:nth-child(2)").addEventListener('mouseover', toggleBorder);
document.querySelector("#picture-list img:nth-child(2)").addEventListener('mouseout', toggleBorder);
document.querySelector("#picture-list img:nth-child(3)").addEventListener('mouseover', toggleBorder);
document.querySelector("#picture-list img:nth-child(3)").addEventListener('mouseout', toggleBorder);

...but that looks kind of awful. If we had 100 images, then we'd need 200 event listeners... 🤮

Instead, we can just add the event listener to the container, the ul#picture-list. This requires one important tweak: we have to make sure that only events triggered by the img elements themselves are handled with a guard clause

const toggleBorder = (event) => {
  console.log(event.type + ' event detected on: ', event.target);
  console.log('event handled by: ', event.currentTarget);

  // Element.matches returns true if the given element would be selected by the given CSS selector
  // If the target of the event wasn't an image, we don't care about it
  if (!event.target.matches('img')) return;

  event.target.closest('li').classList.toggle('highlight');
}
const ul = document.querySelector('#picture-list');
ul.addEventListener('mouseover', toggleBorder);
ul.addEventListener('mouseout', toggleBorder);

Pretty neat, right?!

Removing Event Listeners

One of the reasons why passing a named callback function to your listeners is better is because you can then remove them if you need to.

const handleCountClick = (e) => {
  e.target.dataset.count++;
  e.target.innerText = e.target.dataset.count;
};
const counterButton = document.querySelector("#counter");
counterButton.addEventListener('click', handleCountClick);

const removeListenerButton = document.querySelector("#remove-listener");
removeListenerButton.addEventListener('click', (e) => {
  // To remove an event listener, provide the event type and the handler
  counterButton.removeEventListener('click', handleCountClick);
})

We remove event listeners to limit user interactions and also be 100% sure that we aren't committing memory leaks when we remove elements. (However, modern browsers are pretty good at cleaning up after us).

Q: Why can we write the removeListenerButton event listener as an inline arrow function but we can't for the counterButton event listener?

You can find more information about .

event.currentTarget — The Element that is is handling the event (often the same as event.target but can also be different. See below).

Tip: Whenever you are trying a new type of event, log the event object to the console to see what properties are available! For example, the has different properties than the .

Events on MDN
MouseEvent object
KeyboardEvent object
event delegation
here
Event Driven Programming: Listen and React
addEventListener
Event Type
Event Handlers and the event Object
Challenge
Good to Be Aware of, Not to Use: Inline Handlers
Event Propagation
Event Delegation
Removing Event Listeners