المعرفة:: JavaScript الحالة::مؤرشفة المراجع:: JavaScript Essential Training, The Complete JavaScript Course 2022 From Zero to Expert, https://javascript.info/bubbling-and-capturing, https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling_and_capture


Events

  • An Event is a signal generated by a certain DOM node.
  • For example, if the user selects a button on a web page, you might want to react to that action by displaying an information box.
  • More info on MDN.

Handling Events

addEventListener

  • Events can be handled using an Event Listener. There are many events such as click, keydown, submit, mouseenter, mouseexit, mouseleave, etc.
// element.addEventListener(target_event, callback [, options]);
// Optional: Options. Typically “false” or left blank.
// Or true to set the handler to pick the event in capturing phase
 
// click event
const greet = () => console.log("Hello");
el.addEventListener('click', greet);
 
// keydown event
document.addEventListener('keydown', function (e) {
  if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
    closeModal();
  }
});
 
// Update the x and y displays to show the current mouse position.
const mousePosition = event => {
  posX.innerText = event.pageX;
  posY.innerText = event.pageY;
};
 
window.addEventListener("mousemove", mousePosition);

On-event Properties

  • Another way to attach event listeners to elements is to use on-event element’s properties such as onmouseenter and onclick.
h1.onmouseenter = function (e) {
  alert('onmouseenter: Great! You are reading the heading :D');
};
  • This way isn’t recommended, because addEventListener allows us to attach more than event listener to the same element, and using addEventListener we can remove the event handler in case we don’t need it anymore.
const h1 = document.querySelector('h1');
const alertH1 = function (e) {
  alert('addEventListener: Great! You are reading the heading :D');
};
h1.addEventListener('mouseenter', alertH1);
// h1.removeEventListener('mouseenter', alertH1);
// Remove the event listener after 3 seconds
setTimeout(() => h1.removeEventListener('mouseenter', alertH1), 3000);

HTML Attributes

  • This way shouldn’t be used.
<h1 onclick="alert('HTML Alert')">Title</h1>

Removing an Event Listener

  • removeEventListener() can be used to remove an event listener previously registered with addEventListener.
// removeEventListener(type, listener);
h1.removeEventListener('mouseenter', alertH1)

Event Bubbling and Capturing (Propagation)

  • There are 3 phases of event propagation:
    • Capturing phase – the event goes down to the element.
    • Target phase – the event reached the target element.
    • Bubbling phase – the event bubbles up from the element. (Event handlers pick up events during bubbling phase by default)

Each handler can access event object properties:

  • event.target – the deepest element that originated the event.
  • event.currentTarget (=this) – the current element that handles the event (the one that has the handler on it).
  • event.eventPhase – the current phase (capturing=1, target=2, bubbling=3).

Capturing

  • The event moves down from the document root to event.target, calling handlers assigned with addEventListener(..., true) on the way (true is a shorthand for {capture: true}).

In the capturing phase:

  • The browser checks to see if the element’s outer-most ancestor (<html>) has a click event handler registered on it for the capturing phase, and runs it if so.
  • Then it moves on to the next element inside <html> and does the same thing, then the next one, and so on until it reaches the direct parent of the element that was actually clicked.

Target

  • When an event happens – the most nested element where it happens gets labeled as the “target element” (event.target).

In the target phase:

  • The browser checks to see if the target property has an event handler for the click event registered on it, and runs it if so.
  • Then, if bubbles is true, it propagates the event to the direct parent of the clicked element, then the next one, and so on until it reaches the <html> element. Otherwise, if bubbles is false, it doesn’t propagate the event to any ancestors of the target.

Bubbling

  • When an event happens on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors.
  • Almost all events bubble, but not all. For example, a focus event does not bubble.

In the bubbling phase, the exact opposite of the capturing phase occurs:

  • The browser checks to see if the direct parent of the clicked element has a click event handler registered on it for the bubbling phase, and runs it if so.
  • Then it moves on to the next immediate ancestor element and does the same thing, then the next one, and so on until it reaches the <html> element.

Stopping bubbling

  • event.stopPropagation()prevents further propagation of the current event in the capturing and bubbling phases.
<body onclick="alert(`the bubbling doesn't reach here`)">
  <button onclick="event.stopPropagation()">Click me</button>
</body>
  • If an element has multiple event handlers on a single event, then even if one of them stops the bubbling, the other ones still execute. To stop the bubbling and prevent handlers on the current element from running, there’s a method event.stopImmediatePropagation(). After it no other handlers execute.

  • Don’t stop bubbling without a need! Sometimes event.stopPropagation() creates hidden pitfalls that later may become problems. There’s usually no real need to prevent the bubbling. A task that seemingly requires that may be solved by other means.

Event Delegation

  • Capturing and bubbling allow us to implement one of the most powerful event handling patterns called event delegation.
  • The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them – we put a single handler on their common ancestor.
  • In the handler we get event.target to see where the event actually happened and handle it.
<nav class="nav">
<img src="img/logo.png" alt="Bankist logo" class="nav__logo" id="logo" designer="Jonas" data-version-number="3.0"/>
<ul class="nav__links">
  <li class="nav__item">
 <a class="nav__link" href="#section--1">Features</a>
  </li>
  <li class="nav__item">
 <a class="nav__link" href="#section--2">Operations</a>
  </li>
  <li class="nav__item">
 <a class="nav__link" href="#section--3">Testimonials</a>
  </li>
  <li class="nav__item">
 <a class="nav__link nav__link--btn btn--show-modal" href="#">Open account</a>
  </li>
</ul>
</nav>
// This is a bad approach and inefficient, because it attaches a copy of this function to each element that matches this selector.
document.querySelectorAll('.nav__link').forEach(function (el) {
  el.addEventListener('click', function (e) {
    e.preventDefault();
    const id = this.getAttribute('href');
    console.log(id);
    document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
  });
});
 
// Instead we can do it in a better way:
// 1. Add event listener to common parent element
// 2. Determine what element originated the event
document.querySelector('.nav__links').addEventListener('click', function (e) {
  e.preventDefault();
  // Matching strategy
  if (e.target.classList.contains('nav__link')) { // e.target and not this!
    const id = e.target.getAttribute('href');
    document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
  }
});

Passing arguments through event listeners

  • We can workaround the problem of not being able to pass arguments into a callback function by using the callback function as a function wrapper to call other functions.
  • A better approach is to use bind method to pass the argument to a function copy.
const nav = document.querySelector('.nav');
// const handleHover = function (e, opacity) {
const handleHover = function (e) {
  if (e.target.classList.contains('nav__link')) {
    const link = e.target;
    const siblings = link.closest('.nav').querySelectorAll('.nav__link');
    const logo = link.closest('.nav').querySelector('img');
 
    siblings.forEach(el => {
      // if (el !== link) el.style.opacity = opacity;
      if (el !== link) el.style.opacity = this; // this = 0.5 or 1
    });
    // logo.style.opacity = opacity;
    logo.style.opacity = this;
  }
};
 
// Passing "argument" into handler using wrapper function
nav.addEventListener("mouseover", event => {
  handleHover(event, 0.5);
});
nav.addEventListener("mouseout", event => {
  handleHover(event, 1);
});
 
// Passing "argument" into handler using bind
nav.addEventListener('mouseover', handleHover.bind(0.5));
nav.addEventListener('mouseout', handleHover.bind(1));
 

Scroll Event

  • The scroll event fires when the document view has been scrolled.
  • Scroll events can fired at a high rate, the event handler shouldn’t execute computationally expensive operations such as DOM modifications.

LifeCycle DOM Events

  • There are different events that occur in the DOM during a webpage’s life cycle.
    • Life cycle means from the moment that the page is first accessed, until the user leaves it.

DOMContentLoaded

  • It’s fired by the document as soon as the HTML is completely parsed.
  • Happens when HTML has been downloaded and been converted to the DOM tree and all scripts are downloaded and executed.
  • It does not wait for images and other external resources to load.
  • By placing the script tag at the end of the HTML, we do not need to listen for the DOM content loaded event and then execute our JavaScript code.
document.addEventListener('DOMContentLoaded', function (e) {
  console.log('HTML parsed and DOM tree built!', e);
});

load

  • The load event is fired by the window as soon as not only the HTML is parsed, but also all the images and external resources like CSS files are also loaded.
window.addEventListener('load', function (e) {
  console.log('Page fully loaded', e);
});

beforeunload

  • This event is created immediately before a user is about to leave a page.
  • It can be used to ask the user if he is sure about leaving the website like the following code:
window.addEventListener('beforeunload', function (e) {
  e.preventDefault();
  console.log(e);
  e.returnValue = '';
});

Old Course Notes

Advanced event listeners and this

button.addEventListener("click", function (event) {
  console.log(event);
  this.innerText === "Open lid" ? (this.innerText = "Close lid") : (this.innerText = "Open lid");
  status.innerText === "open" ? (status.innerText = "closed") : (status.innerText = "open");
});
/**
 * Add event listener to the lid-toggle button.
 */
const lidToggle = function () {
  // Find the current backpack object in backpackObjectArray
  let backpackObject = backpackObjectArray.find(({ id }) => id === this.parentElement.id);
 
  // Toggle lidOpen status
  backpackObject.lidOpen == true ? (backpackObject.lidOpen = false) : (backpackObject.lidOpen = true);
 
  // Toggle button text
  this.innerText == "Open lid" ? (this.innerText = "Close lid") : (this.innerText = "Open lid");
 
  // Set visible property status text
  let status = this.parentElement.querySelector(".backpack__lid span");
  status.innerText == "closed" ? (status.innerText = "open") : (status.innerText = "closed");
};
 
let button = backpackArticle.querySelector(".lid-toggle");
// Add event listener
button.addEventListener("click", lidToggle);

Form submit event

// Add event listener to the form submit action
lengthForm.addEventListener("submit", e => {
  // Stop form from reloading the page
  e.preventDefault();
 
  // Get the value from the form input
  let newValue = lengthForm.querySelector("input").value;
 
  // Set the value of the field
  listElement.querySelector("span").innerHTML = `${newValue} inches`;
 
  // Clear the form input
  lengthForm.querySelector("input").value = "";
});