Events

User interaction handling

JavaScript Events

Events are actions that happen in the browser (clicks, key presses, page loads, etc.). JavaScript can "listen" for these events and respond to them.

Adding Event Listeners

const button = document.querySelector('#my-button');

// Modern way (preferred)
button.addEventListener('click', () => {
  console.log('Button clicked!');
});

// Can add multiple listeners to same event
button.addEventListener('click', () => {
  console.log('Another listener');
});

// Old way (avoid - can only have one handler)
button.onclick = () => {
  console.log('Old way');
};

Common Event Types

Mouse Events

element.addEventListener('click', (e) => {});
element.addEventListener('dblclick', (e) => {});   // Double click
element.addEventListener('mouseenter', (e) => {}); // Mouse enters
element.addEventListener('mouseleave', (e) => {}); // Mouse leaves
element.addEventListener('mousemove', (e) => {});  // Mouse moves
element.addEventListener('mousedown', (e) => {});  // Mouse button down
element.addEventListener('mouseup', (e) => {});    // Mouse button up

Keyboard Events

element.addEventListener('keydown', (e) => {
  console.log('Key pressed:', e.key);
  if (e.key === 'Enter') {
    console.log('Enter key pressed');
  }
});

element.addEventListener('keyup', (e) => {});
element.addEventListener('keypress', (e) => {});  // Deprecated

Form Events

const form = document.querySelector('form');
const input = document.querySelector('input');

form.addEventListener('submit', (e) => {
  e.preventDefault();  // Prevent page reload
  console.log('Form submitted');
});

input.addEventListener('input', (e) => {
  console.log('Value:', e.target.value);  // Fires on every change
});

input.addEventListener('change', (e) => {
  console.log('Changed:', e.target.value);  // Fires when input loses focus
});

input.addEventListener('focus', (e) => {});
input.addEventListener('blur', (e) => {});  // Loses focus

Window/Document Events

// Page load
window.addEventListener('load', () => {
  console.log('Page fully loaded');
});

// DOM ready
document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM ready');
});

// Scroll
window.addEventListener('scroll', () => {
  console.log('Scrolled to:', window.scrollY);
});

// Resize
window.addEventListener('resize', () => {
  console.log('Window size:', window.innerWidth, window.innerHeight);
});

The Event Object

Event listeners receive an event object with useful information:

button.addEventListener('click', (event) => {
  console.log(event.type);           // "click"
  console.log(event.target);         // Element that triggered event
  console.log(event.currentTarget);  // Element with listener attached
  console.log(event.timeStamp);      // When event occurred
  
  // Mouse position
  console.log(event.clientX, event.clientY);  // Relative to viewport
  console.log(event.pageX, event.pageY);      // Relative to page
  
  // Keyboard info
  console.log(event.key);      // "a", "Enter", "Escape", etc.
  console.log(event.code);     // "KeyA", "Enter", etc.
  console.log(event.ctrlKey);  // Was Ctrl pressed?
  console.log(event.shiftKey); // Was Shift pressed?
});

Event Propagation

Events bubble up from child to parent elements:


// All three will fire when button is clicked! document.querySelector('#btn').addEventListener('click', () => { console.log('Button clicked'); }); document.querySelector('#inner').addEventListener('click', () => { console.log('Inner div clicked'); }); document.querySelector('#outer').addEventListener('click', () => { console.log('Outer div clicked'); }); // Output when button clicked: // "Button clicked" // "Inner div clicked" // "Outer div clicked"

Stopping Propagation

button.addEventListener('click', (e) => {
  e.stopPropagation();  // Stop event from bubbling up
  console.log('Only this runs');
});

// Prevent default browser behavior
link.addEventListener('click', (e) => {
  e.preventDefault();  // Don't follow the link
  console.log('Link click prevented');
});

form.addEventListener('submit', (e) => {
  e.preventDefault();  // Don't reload page
  // Handle form with JavaScript instead
});

Event Delegation

Instead of adding listeners to many elements, add one to a parent:

// BAD: Adding listener to each item
const items = document.querySelectorAll('.item');
items.forEach(item => {
  item.addEventListener('click', () => {
    console.log('Item clicked');
  });
});

// GOOD: Event delegation
const list = document.querySelector('#item-list');
list.addEventListener('click', (e) => {
  // Check if clicked element is an item
  if (e.target.classList.contains('item')) {
    console.log('Item clicked:', e.target.textContent);
  }
});

// Works for dynamically added items too!
const newItem = document.createElement('div');
newItem.className = 'item';
newItem.textContent = 'New Item';
list.appendChild(newItem);  // Click handler automatically works!

Removing Event Listeners

// Named function (required for removal)
function handleClick() {
  console.log('Clicked');
}

button.addEventListener('click', handleClick);

// Remove later
button.removeEventListener('click', handleClick);

// Anonymous functions can't be removed!
button.addEventListener('click', () => {});  // Can't remove this

Practical Examples

// Example 1: Dropdown menu
const dropdown = document.querySelector('.dropdown');
const menu = document.querySelector('.dropdown-menu');

dropdown.addEventListener('mouseenter', () => {
  menu.classList.add('show');
});

dropdown.addEventListener('mouseleave', () => {
  menu.classList.remove('show');
});

// Example 2: Keyboard shortcuts
document.addEventListener('keydown', (e) => {
  // Ctrl+S to save
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault();
    console.log('Save triggered');
  }
  
  // Escape to close modal
  if (e.key === 'Escape') {
    modal.classList.add('hidden');
  }
});

// Example 3: Infinite scroll
window.addEventListener('scroll', () => {
  const scrolled = window.scrollY + window.innerHeight;
  const total = document.documentElement.scrollHeight;
  
  if (scrolled >= total - 100) {
    console.log('Near bottom - load more content');
  }
});

⚠️ Common Pitfalls

  • • Forgetting to preventDefault() on forms/links
  • • Adding listeners in loops without delegation
  • • Not removing listeners when no longer needed (memory leaks)
  • • Using anonymous functions when you need to remove listener later

✓ Best Practices

  • • Use event delegation for lists of similar elements
  • • Always preventDefault() on form submissions if handling with JS
  • • Debounce expensive handlers (scroll, resize, input)
  • • Use named functions if you need to remove listeners
  • • Check event.target in delegated handlers