All Four Patterns at a Glance
addEventListener (Station 1)
target.addEventListener(eventType, callback) — three parts. Pass the callback by reference (no parentheses). The browser stores it and calls it when the event fires.
The Event Object (Station 2)
The browser passes an event object to every callback. Key properties: event.target, event.key, event.type. Key methods: event.preventDefault(), event.stopPropagation().
View Switching and Forms (Station 3)
Toggle classList to swap between two views. Listen on the form element for submit events. Call event.preventDefault() to stop page reload.
Delegation (Station 4)
One listener on a parent container catches events from all children — including dynamically added ones. Use event.target.closest(selector) to identify which child was acted on.
Station 1 — addEventListener
// The three-part pattern (Station 1)
const button = document.querySelector('.my-button');
button.addEventListener('click', handleClick);
// ^^^^^^ ^^^^^^^^^^^
// event callback — no parentheses!
// type
function handleClick(event) {
// event is passed automatically by the browser
}
// Pass the reference, never call it:
// ✓ addEventListener('click', handleClick)
// ✗ addEventListener('click', handleClick()) Station 2 — The Event Object
// The event object (Station 2)
element.addEventListener('click', function(event) {
event.target // element that was clicked
event.currentTarget // element the listener is on
event.type // 'click', 'submit', etc.
event.key // keyboard: 'Enter', 'a', 'Escape'
event.preventDefault() // stop default browser action
event.stopPropagation() // stop bubbling
});
// event.target vs event.currentTarget:
// target — where the event originated (may be a child)
// currentTarget — where the listener is attached (always same) Station 3 — View Switching and Forms
// View switching (Station 3)
const listView = document.querySelector('.list-view');
const detailView = document.querySelector('.detail-view');
const showDetailBtn = document.querySelector('.show-detail');
const backBtn = document.querySelector('.back-button');
showDetailBtn.addEventListener('click', function() {
listView.classList.add('hidden');
detailView.classList.remove('hidden');
});
backBtn.addEventListener('click', function() {
detailView.classList.add('hidden');
listView.classList.remove('hidden');
});
// CSS: .hidden { display: none; } // Form with preventDefault (Station 3)
const form = document.querySelector('.my-form');
form.addEventListener('submit', function(event) {
event.preventDefault(); // stop page reload
const input = form.querySelector('.my-input');
const value = input.value.trim();
if (!value) return;
// Process the input...
input.value = ''; // clear after handling
});
// Listen on the FORM, not the submit button.
// Catches keyboard Enter submission too. Station 4 — Event Delegation
// Event delegation (Station 4)
const container = document.querySelector('.card-container');
container.addEventListener('click', function(event) {
// Find the card, wherever the click landed
const card = event.target.closest('.robot-card');
if (!card) return; // clicked outside any card
// Work with the identified card
const robotId = card.dataset.robotId;
console.log('Clicked card:', robotId);
});
// Dynamic cards added later work automatically —
// they bubble up to the same container listener. Station 5 — Initialization Order
// Initialization order (Station 5)
document.addEventListener('DOMContentLoaded', function() {
// All querySelector calls go here — after DOM is ready
const container = document.querySelector('.card-container');
const backBtn = document.querySelector('.back-button');
const notesForm = document.querySelector('.notes-form');
// Wire listeners here too
container.addEventListener('click', handleCardClick);
backBtn.addEventListener('click', handleBack);
notesForm.addEventListener('submit', handleNoteSubmit);
});
// ✗ Never querySelector or addEventListener outside
// DOMContentLoaded — elements may not exist yet Common Event Types
// Common event types
// Mouse
'click' // single click or tap
'dblclick' // double click
'mouseover' // pointer enters element
'mouseout' // pointer leaves element
// Keyboard
'keydown' // key pressed (fires repeatedly when held)
'keyup' // key released
// Check event.key: 'Enter', 'Escape', 'a', 'ArrowUp', etc.
// Form
'submit' // form submitted (listen on form, not button)
'change' // select, checkbox, radio changed
'input' // text typed in input or textarea
// Page
'DOMContentLoaded' // HTML parsed, DOM ready
'load' // page + all resources fully loaded Quick Checklist
No Parentheses on Callbacks
addEventListener('click', handleClick) — not handleClick(). Parentheses call the function immediately and pass its return value instead of the function itself.
Listen on the Form, Not the Button
Form submit events fire on the <form> element. Listening on the submit button misses keyboard submissions and other ways a form can be submitted.
Guard After .closest()
const card = event.target.closest('.robot-card'); if (!card) return; — always check for null before using the result.
Wrap Setup in DOMContentLoaded
All querySelector calls and addEventListener calls must run after the DOM is ready. Put them inside a DOMContentLoaded listener.
See It in Action 🟠
All of these patterns are working together in the Robot ID Card App demo. Open the console and click around. Curious about the code? The source is on GitHub.