JavaScript Event Handling

JavaScript Event Handling

Events are actions that happen in the browser, like clicking a button, hovering over an element, or pressing a key. JavaScript provides powerful event handling capabilities to respond to these events and create interactive web applications. This tutorial covers event listeners, event objects, and common event handling patterns.

What are Events?

Events are signals that something has happened in the browser. They can be triggered by user actions (clicks, key presses) or by the browser itself (page load, errors).

Adding Event Listeners

Traditional DOM Method

// Get the element
const button = document.getElementById('myButton');

// Add event listener
button.addEventListener('click', function(event) {
  console.log('Button was clicked!');
  console.log('Event type:', event.type);
  console.log('Target element:', event.target);
});

Inline Event Handlers (Not Recommended)

<!-- This is not recommended for modern JavaScript -->
<button onclick="handleClick()">Click me</button>

Event Object

Every event handler receives an event object with information about the event.

function handleClick(event) {
  // Prevent default behavior (like form submission)
  event.preventDefault();
  
  // Stop event bubbling
  event.stopPropagation();
  
  // Event properties
  console.log('Event type:', event.type);
  console.log('Target element:', event.target);
  console.log('Current target:', event.currentTarget);
  console.log('Mouse position:', event.clientX, event.clientY);
}

Common Mouse Events

Click Events

const button = document.getElementById('myButton');

button.addEventListener('click', function(event) {
  alert('Button clicked!');
});

// Double click
button.addEventListener('dblclick', function(event) {
  alert('Button double-clicked!');
});

Mouse Movement

<div id="hoverArea" style="width: 200px; height: 200px; background: lightblue;">
  Hover over me
</div>
const hoverArea = document.getElementById('hoverArea');

hoverArea.addEventListener('mouseenter', function() {
  this.style.backgroundColor = 'lightgreen';
});

hoverArea.addEventListener('mouseleave', function() {
  this.style.backgroundColor = 'lightblue';
});

hoverArea.addEventListener('mousemove', function(event) {
  // Show mouse coordinates
  const coords = document.getElementById('coordinates');
  coords.textContent = `X: ${event.clientX}, Y: ${event.clientY}`;
});

Mouse Buttons

document.addEventListener('mousedown', function(event) {
  switch(event.button) {
    case 0:
      console.log('Left button pressed');
      break;
    case 1:
      console.log('Middle button pressed');
      break;
    case 2:
      console.log('Right button pressed');
      break;
  }
});

document.addEventListener('contextmenu', function(event) {
  event.preventDefault(); // Prevent right-click menu
  console.log('Right-click detected');
});

Keyboard Events

Key Press Detection

<input type="text" id="textInput" placeholder="Type something...">
<div id="output"></div>
const textInput = document.getElementById('textInput');
const output = document.getElementById('output');

textInput.addEventListener('keydown', function(event) {
  console.log('Key down:', event.key);
  console.log('Key code:', event.code);
});

textInput.addEventListener('keyup', function(event) {
  output.textContent = `Last key pressed: ${event.key}`;
});

textInput.addEventListener('keypress', function(event) {
  // Note: keypress is deprecated, use keydown/keyup instead
  console.log('Character entered:', event.key);
});

Special Keys Handling

document.addEventListener('keydown', function(event) {
  // Prevent default behavior for certain keys
  if (event.key === 'F12') {
    event.preventDefault();
    console.log('F12 pressed - dev tools blocked');
  }
  
  // Handle arrow keys
  if (event.key.startsWith('Arrow')) {
    event.preventDefault();
    switch(event.key) {
      case 'ArrowUp':
        console.log('Moving up');
        break;
      case 'ArrowDown':
        console.log('Moving down');
        break;
      case 'ArrowLeft':
        console.log('Moving left');
        break;
      case 'ArrowRight':
        console.log('Moving right');
        break;
    }
  }
  
  // Ctrl/Cmd + S for save
  if ((event.ctrlKey || event.metaKey) && event.key === 's') {
    event.preventDefault();
    saveDocument();
  }
});

function saveDocument() {
  console.log('Document saved!');
}

Form Events

Input Events

<form id="myForm">
  <input type="text" id="username" placeholder="Username">
  <input type="password" id="password" placeholder="Password">
  <button type="submit">Submit</button>
</form>
<div id="feedback"></div>
const username = document.getElementById('username');
const password = document.getElementById('password');
const form = document.getElementById('myForm');
const feedback = document.getElementById('feedback');

// Input validation
username.addEventListener('input', function(event) {
  if (this.value.length < 3) {
    this.style.borderColor = 'red';
    feedback.textContent = 'Username must be at least 3 characters';
  } else {
    this.style.borderColor = 'green';
    feedback.textContent = '';
  }
});

password.addEventListener('input', function(event) {
  const strength = checkPasswordStrength(this.value);
  this.style.borderColor = strength.color;
  feedback.textContent = strength.message;
});

function checkPasswordStrength(password) {
  if (password.length < 6) {
    return { color: 'red', message: 'Password too short' };
  } else if (password.length < 10) {
    return { color: 'orange', message: 'Password strength: medium' };
  } else {
    return { color: 'green', message: 'Password strength: strong' };
  }
}

// Form submission
form.addEventListener('submit', function(event) {
  event.preventDefault(); // Prevent page reload
  
  const formData = new FormData(this);
  const data = Object.fromEntries(formData);
  
  console.log('Form submitted with data:', data);
  
  // Simulate API call
  submitForm(data).then(result => {
    feedback.textContent = 'Form submitted successfully!';
    feedback.style.color = 'green';
  }).catch(error => {
    feedback.textContent = 'Error submitting form: ' + error.message;
    feedback.style.color = 'red';
  });
});

async function submitForm(data) {
  // Simulate network delay
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  if (data.username && data.password) {
    return { success: true };
  } else {
    throw new Error('Invalid data');
  }
}

Focus and Blur Events

const inputs = document.querySelectorAll('input');

inputs.forEach(input => {
  input.addEventListener('focus', function() {
    this.style.backgroundColor = '#e8f4f8';
  });
  
  input.addEventListener('blur', function() {
    this.style.backgroundColor = 'white';
    
    // Validate on blur
    if (this.value === '') {
      this.style.borderColor = 'red';
    } else {
      this.style.borderColor = '#ccc';
    }
  });
});

Window and Document Events

Load Events

// When the entire page loads
window.addEventListener('load', function() {
  console.log('Page fully loaded');
});

// When DOM is ready (before images, etc.)
document.addEventListener('DOMContentLoaded', function() {
  console.log('DOM is ready');
  initializeApp();
});

function initializeApp() {
  // Set up event listeners and initialize components
  console.log('App initialized');
}

Resize and Scroll Events

// Window resize
window.addEventListener('resize', function(event) {
  console.log('Window size:', window.innerWidth, 'x', window.innerHeight);
});

// Scroll events (throttled for performance)
let scrollTimeout;
window.addEventListener('scroll', function() {
  clearTimeout(scrollTimeout);
  scrollTimeout = setTimeout(function() {
    console.log('Scroll position:', window.pageYOffset);
  }, 100);
});

Visibility Change

document.addEventListener('visibilitychange', function() {
  if (document.hidden) {
    console.log('Page is hidden (user switched tabs)');
    pauseVideo(); // Example: pause video playback
  } else {
    console.log('Page is visible');
    resumeVideo(); // Example: resume video playback
  }
});

Event Delegation

Instead of adding listeners to many elements, add one listener to a parent element.

<ul id="todoList">
  <li>Task 1 <button class="delete">Delete</button></li>
  <li>Task 2 <button class="delete">Delete</button></li>
  <li>Task 3 <button class="delete">Delete</button></li>
</ul>
const todoList = document.getElementById('todoList');

// Event delegation
todoList.addEventListener('click', function(event) {
  // Check if the clicked element is a delete button
  if (event.target.classList.contains('delete')) {
    const listItem = event.target.parentElement;
    listItem.remove();
    console.log('Task deleted');
  }
});

Custom Events

You can create and dispatch your own custom events.

// Create a custom event
const customEvent = new CustomEvent('userAction', {
  detail: { action: 'login', userId: 123 }
});

// Listen for the custom event
document.addEventListener('userAction', function(event) {
  console.log('User action:', event.detail);
});

// Dispatch the event
document.dispatchEvent(customEvent);

Event Propagation

Events bubble up from child to parent elements.

<div id="outer">
  <div id="inner">
    <button id="button">Click me</button>
  </div>
</div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const button = document.getElementById('button');

// Event bubbling (default behavior)
outer.addEventListener('click', function() {
  console.log('Outer div clicked');
});

inner.addEventListener('click', function() {
  console.log('Inner div clicked');
});

button.addEventListener('click', function(event) {
  console.log('Button clicked');
  // event.stopPropagation(); // Uncomment to stop bubbling
});

Removing Event Listeners

function handleClick() {
  console.log('Button clicked');
}

const button = document.getElementById('myButton');

// Add listener
button.addEventListener('click', handleClick);

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

// Note: Anonymous functions cannot be removed
button.addEventListener('click', function() {
  console.log('This cannot be removed');
});

Event Listener Options

button.addEventListener('click', handleClick, {
  once: true,        // Listener fires only once
  capture: true,     // Use capture phase instead of bubbling
  passive: true      // Improves scroll performance
});

// Touch events should be passive for better performance
element.addEventListener('touchstart', handleTouch, { passive: true });

Performance Considerations

Debouncing

Prevent function calls from happening too frequently.

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// Usage
window.addEventListener('resize', debounce(function() {
  console.log('Window resized');
}, 250));

Throttling

Ensure function is called at most once per time interval.

function throttle(func, limit) {
  let inThrottle;
  return function() {
    const args = arguments;
    const context = this;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Usage
window.addEventListener('scroll', throttle(function() {
  console.log('Scroll event');
}, 100));

Complete Example

<!DOCTYPE html>
<html>
<head>
  <title>Event Handling Demo</title>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; }
    .box { width: 200px; height: 200px; background: lightblue; margin: 10px; padding: 10px; }
    .hover { background: lightgreen !important; }
    #output { margin-top: 20px; padding: 10px; background: #f5f5f5; }
  </style>
</head>
<body>
  <h1>Event Handling Demo</h1>
  
  <button id="clickBtn">Click me</button>
  <button id="toggleBtn">Toggle Events</button>
  
  <div class="box" id="hoverBox">
    Hover over me
  </div>
  
  <input type="text" id="textInput" placeholder="Type something...">
  
  <div id="output"></div>

  <script>
    const output = document.getElementById('output');
    let eventsEnabled = true;
    
    function log(message) {
      output.innerHTML += message + '<br>';
      output.scrollTop = output.scrollHeight;
    }
    
    // Button click
    const clickBtn = document.getElementById('clickBtn');
    clickBtn.addEventListener('click', function(event) {
      if (!eventsEnabled) return;
      log(`Button clicked! Mouse position: ${event.clientX}, ${event.clientY}`);
    });
    
    // Toggle events
    const toggleBtn = document.getElementById('toggleBtn');
    toggleBtn.addEventListener('click', function() {
      eventsEnabled = !eventsEnabled;
      this.textContent = eventsEnabled ? 'Disable Events' : 'Enable Events';
      log(`Events ${eventsEnabled ? 'enabled' : 'disabled'}`);
    });
    
    // Hover effects
    const hoverBox = document.getElementById('hoverBox');
    hoverBox.addEventListener('mouseenter', function() {
      if (!eventsEnabled) return;
      this.classList.add('hover');
      log('Mouse entered the box');
    });
    
    hoverBox.addEventListener('mouseleave', function() {
      if (!eventsEnabled) return;
      this.classList.remove('hover');
      log('Mouse left the box');
    });
    
    // Text input
    const textInput = document.getElementById('textInput');
    textInput.addEventListener('input', function() {
      if (!eventsEnabled) return;
      log(`Text changed to: "${this.value}"`);
    });
    
    textInput.addEventListener('keydown', function(event) {
      if (!eventsEnabled) return;
      if (event.key === 'Enter') {
        log('Enter key pressed - clearing input');
        this.value = '';
      }
    });
    
    // Window events
    window.addEventListener('resize', function() {
      log(`Window resized to: ${window.innerWidth}x${window.innerHeight}`);
    });
    
    document.addEventListener('visibilitychange', function() {
      log(`Page ${document.hidden ? 'hidden' : 'visible'}`);
    });
    
    log('Event handling demo loaded. Try interacting with the elements above!');
  </script>
</body>
</html>

Summary

JavaScript event handling allows you to create interactive web applications:

Event TypeDescriptionCommon Use Cases
clickMouse clickButton interactions
mouseenter/mouseleaveMouse hoverTooltip display
keydown/keyupKeyboard inputForm validation, shortcuts
submitForm submissionData processing
loadPage/resource loadedInitialization
resizeWindow resizeResponsive design
scrollPage scrollInfinite scroll, animations

Key concepts:

  • Event listeners attach functions to events
  • Event objects contain event information
  • Event propagation controls event flow
  • Event delegation efficiently handles multiple elements
  • Custom events allow component communication
  • Performance optimization with debouncing/throttling

Mastering event handling is essential for creating dynamic, user-friendly web applications.


External Resources:

Related Tutorials:

  • Learn about JavaScript functions here to understand event handler functions.
  • Check out DOM manipulation techniques to work with elements that trigger events.
Last updated on