Marcy Lab School Docs
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • 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 A GitHub Organization and Scrumboard
    • How to Create Projects with Vite
    • How to Deploy on GitHub Pages
    • How to Deploy on Render
    • How to Test your API with Postman
  • Mod 0 - Command Line Interfaces, Git, and GitHub
    • Overview
    • 1. Command Line Interfaces
    • 2. Git & GitHub
    • 3. Git Pulling & Merging
    • 4. Git Branching & PRs
  • Mod 1 - JavaScript Fundamentals
    • Overview
    • 1. Intro to Programming
    • 2. Errors
    • 3. Node & Node Modules
    • 4. Variables, Functions & String Methods
    • 5. Control Flow, typeof, and Math
    • 6. Loops
    • 7. Arrays
    • 8. Objects
    • 9. Higher Order Functions: Callbacks
    • 10. Higher Order Functions: Array Methods
    • 11. Regex
  • Mod 2 - HTML, CSS & the DOM
    • Overview
    • 1. HTML
    • 2. CSS
    • 3. Accessibility (a11y)
    • 4. The Document Object Model (DOM) API
    • 5. Events
    • 6. Forms
    • 7. The Box Model and Positioning
    • 8. Flexbox
    • 9. Grid & Media Queries
    • 10. ESModules
    • 11. Vite
    • 12. LocalStorage
  • Mod 3 - Async & APIs
    • Overview
    • 1. Promises
    • 2. Fetch
    • 3. Building a Fetching App
    • 4. Async & Await
    • 5. A Generic Fetch Handler
  • Mod 4 - Project Week!
    • Important How Tos and Guides
      • How to Create a GitHub Organization and Scrum Board
      • How To Start a Project with Vite
      • How To Deploy a Project with GitHub Pages
    • Project Week Overview
    • Agile Methodologies
    • Deliverables & Milestones
    • Technical Requirements Checklist
    • Free API List
    • Collaborative GitHub
  • Mod 5 - Object-Oriented Programming
    • Overview
    • 1. Intro to OOP, Encapsulation, Factory Functions, and Closure
    • 2. Classes
    • 3. Private & Static
    • 4. UML Diagrams & Has Many/Belongs To Relationships
    • 5. Challenge: Implementing Has Many/Belongs To
    • 6. Inheritance
    • 7. Polymorphism
    • 8. Review and Practice
    • MDN: Object Prototypes
  • Mod 6 - Data Structures & Algorithms
    • Overview
    • Important How Tos and Guides
      • How to Debug
      • How to PEDAC
    • 1. Nodes & Linked Lists
    • 2. Singly & Doubly Linked Lists
    • 3. Stacks & Queues
    • 4. Recursion
    • 5. Trees
  • Mod 7 - React
    • Overview
    • Important How Tos and Guides
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
    • 1. Intro to React
    • 2. Events, State, and Forms
    • 3. Fetching with useEffect
    • 4. React Router
    • 5. Building a Flashcards App
    • 6. React Context
    • 7. Global Context Pattern
  • Mod 8 - Backend
    • Overview
    • Important How Tos and Guides
      • How to Deploy on Render
      • How to Test your API with Postman
      • Postgres Setup
    • 1. Intro to Express
    • 2. Building a Static Web Server with Middleware
    • 3. Securing API Keys and Environment Variables
    • 4. RESTful CRUD API
    • 5. Model-View-Controller Architecture
    • 6. SQL and Databases
    • 7. JOIN (Association) SQL Queries
    • 8. Knex
    • 9. Your First Fullstack App!
    • 10. Migrations & Seeds
    • 11. Schema Design & Normalization
    • 12. Hashing Passwords with Bcrypt
    • 13. React Express Auth Template Overview
  • Mod 9 - Civic Tech Hackathon
    • Overview
    • Rubric
  • Mod 10 - Capstone
    • Overview
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. Mod 2 - HTML, CSS & the DOM

5. Events

Previous4. The Document Object Model (DOM) APINext6. Forms

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