Home > OS >  this in the DOM and event listeners
this in the DOM and event listeners

Time:02-23

Could some one please tell me how to refactor the JavaScript without "this" in a way that explains the use of "this" in the browser context? (please don't answer the following question with jQuery solutions)

I have passed e (event) into the callback function and implemented:
e.target.classList.toggle("active");
var panel = e.target.nextElementSibling;

What I have implemented has worked. However I want to know to what benefit/why you would use "this" in the context shown, it doesn't seem as declarative as e.target. Is this a function binding issue?

(other snippet related questions below snippet)

I have taken the below snippet from W3schools, it is used for creating an accordion menu:

var acc = document.getElementsByClassName("accordion");
var i;

for (i = 0; i < acc.length; i  ) {
  acc[i].addEventListener("click", function() {
    /* Toggle between adding and removing the "active" class,
    to highlight the button that controls the panel */
    this.classList.toggle("active");

    /* Toggle between hiding and showing the active panel */
    var panel = this.nextElementSibling;
    if (panel.style.display === "block") {
      panel.style.display = "none";
    } else {
      panel.style.display = "block";
    }
  });
}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  text-align: left;
  border: none;
  outline: none;
  transition: 0.4s;
}

/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
  background-color: #ccc;
}

/* Style the accordion panel. Note: hidden by default */
.panel {
  padding: 0 18px;
  background-color: white;
  display: none;
  overflow: hidden;
}
<button >Section 1</button>
<div >
  <p>Lorem ipsum...</p>
</div>

<button >Section 2</button>
<div >
  <p>Lorem ipsum...</p>
</div>

<button >Section 3</button>
<div >
  <p>Lorem ipsum...</p>
</div>

Could some one please explain the relationship between the loop and the event listener?

The way I understand it, I would expect that:
every time the DOM is altered/reloads =>
the loop runs =>
adding an event listener on each html collection element=>
if HTML element clicked == true (or) the event is fired
the class is toggled to active
the following if statement is executed and styles may or may not be applied inline to the html element. This seems like a computationally expensive method of adding event listeners.

Is the above statement that I have written correct if not exactly what is happening?
Is there a more efficient way of writing the event listener loop snippet?
I.e. using event bubbling on a containing element perhaps?

CodePudding user response:

Here's a simple HTML layout:

<main>
  <div class='A'></div>
  <section></section>
  <div class='B'></div>
  <section></section>
  <div class='C'></div>
</main>

Here's the JavaScript using a programming paradigm called Event Delegation:

document.querySelector('main').addEventListener('click', eventHandler);

Because the majority of the events bubble (click bubbles), the event listener should be registered to an ancestor tag (in this example that would be <main>) of the tags you want to control via events (in this example it's .A, .B, and .C). Now the event handler:

function eventHandler(event) {
  const listener = event.currentTarget; // or `this` points to `<main>`
  const clicked = event.target; // This is the tag user actually clicked
    ....

We need to control exactly what reacts to a click and what doesn't react when clicked. We can use if/if else or switch() or even a ternary to delegate events to what we want while excluding what we don't want. Continuing within eventHandler(e)...

....
  // All <section>s and even <main> is excluded
  if (clicked.matches('div')) {
    
    if (clicked.matches('.A')) {
       clicked.style.background = 'red';
    }
    if (clicked.matches('.C') {
       clicked.style.background = 'blue';
    }
  }
  // .B was never mentioned with any specific intructions so it's also excluded.
 }

The example below is pretty much the same delegation scheme as the previous explination except with an additional CSS trick with adjacent sibling combinator:

.accordion.active .panel {
  display: block;
}

Whenever a button is .active, the .panel that follows it will disappear.

document.body.addEventListener("click", togglePanel);

function togglePanel(e) {
  const clk = e.target;

  if (clk.matches('.accordion')) {
    clk.classList.toggle("active");
  }
};
.accordion {
  background-color: #eee;
  color: #444;
  cursor: pointer;
  padding: 18px;
  width: 100%;
  text-align: left;
  border: none;
  outline: none;
  transition: 0.4s;
}

.active,
.accordion:hover {
  background-color: #ccc;
}

.panel {
  padding: 0 18px;
  background-color: white;
  display: none;
  overflow: hidden;
}

.accordion.active .panel {
  display: block;
}
<button >Section 1</button>
<div >
  <p>Lorem ipsum...</p>
</div>

<button >Section 2</button>
<div >
  <p>Lorem ipsum...</p>
</div>

<button >Section 3</button>
<div >
  <p>Lorem ipsum...</p>
</div>

  • Related