2

I have a navigation with multiple event listeners: click and hover. This event listeners can change, e.g. on browser resize. I add the event listeners in my own method which gets called if listeners should be re-initialized. My problem in my following class: removeEventListener does not work. So re-initialization causes that both click and hover fires.

class Navigation {
 constructor(element) {
  this.element = element;
  this.changeButton = this.element.querySelector("button");
  this.version = "hover";
  this.navItemElements = Array.from(this.element.querySelectorAll(".nav-item"));
  this.navItems = this.createNavItems(this.navItemElements);
  this.clickHandler = (event) => this.navItemHoverHandler(event);
  this.changeButton.addEventListener("click", () => {
   this.isHoverVersion = !(this.isHoverVersion);
   console.log("Change trigger. Is hover version --> " + this.isHoverVersion);
   this.initListeners();
  });
  this.clickHandler = (event, navItem) => this.navItemClickHandler(event, navItem);
  this.hoverHandler = (event, navItem) => this.navItemHoverHandler(event, navItem);
  this.initListeners();
 }

 initListeners() {

  console.log("Init listeners");
  console.log(this.navItems);

  if (this.isHoverVersion) {
   this.navItems.forEach((navItem) => {
    console.log(navItem);
    navItem.link.addEventListener('mouseover', e => this.hoverHandler(e, navItem));
    navItem.link.addEventListener('mouseout', e => this.hoverHandler(e, navItem));
    navItem.link.removeEventListener('click',this.clickHandler);
   })
  } else {
   this.navItems.forEach((navItem) => {
    navItem.link.addEventListener('click', e => this.clickHandler(e, navItem));
    navItem.link.removeEventListener('mouseover', this.hoverHandler);
    navItem.link.removeEventListener('mouseout', this.hoverHandler);
   })
  }
 }

 navItemClickHandler(event, navItem) {
  event.preventDefault();
  console.log('Click handler executed');
  console.log(navItem);
 }

 navItemHoverHandler(event, navItem) {
  event.preventDefault();
  console.log('Hover handler executed');
  console.log(navItem);
 }

 createNavItems(navItemElements) {
  let navItems = [];
  navItemElements.forEach((itemElement) => {
   navItems.push(this.createNavItemObject(itemElement));
  });
  return navItems;
 }

 createNavItemObject(navItem) {
  let container = navItem;
  let link = navItem.querySelector("a");
  return  {
   container: container,
   link: link,
  };
 }
}

let navigationElement = document.querySelector(".navigation");

new Navigation(navigationElement);
.navigation {
  display: flex;
  flex-flow: row nowrap;
  width: 100%;
  justify-content: space-between;
  width: 100%;
  max-width: 280px;
}

ul {
  display: flex;
  flex-flow: row nowrap;
  list-style: none;
  margin: 0;
  padding: 0;
}

li {
  margin-right: 20px;
}
<div class="navigation">
  <button id="changeButton">Change trigger</button>
  <ul>
    <li class="nav-item">
      <a href="">Link 1</a>
    </li>
    <li class="nav-item">
      <a href="">Link 2</a>
    </li>
  </ul>
</div>

navItem is an object, which I created before. navItem.link is the HTML Anchor Element.

Question:

How can I bind the listener to the specific object? I need to call the same method, but with its specific navItem object...

Update:

Added working code example. The navigation gets initialized with click trigger. Clicking the button will toggle click / hover. In this example, after toggling from click to hover, both listeners get executed.

Mikel Wohlschlegel
  • 1,364
  • 1
  • 11
  • 23

1 Answers1

1

You need to use the exact function you used in addEventListener, check the explanation on MDN.

So in your example you define a new anonymous function:

navItem.link.addEventListener('mouseout', e =>
    this.hoverHandler(e, navItem)
);

The arrow function is the new anonymous function: e => .... So instead using this anonymous function you need to use the exact same function.

To get the navItem in the event handler you can use bind(). The first argument of bind defines the context (this) in the event handler and all other arguments are preceding any provided (in this case the event argument). But you should note that bind() creates a new function reference every time, so you need to save the reference for the removing. One way to achieve this could be to save the function on the navItem. For the hover events it would be:

if (this.isHoverVersion) {
    this.navItems.forEach(navItem => {
        // save function
        navItem.hoverHandler = this.navItemHoverHandler.bind(null, navItem);

        navItem.link.addEventListener('mouseover', navItem.hoverHandler);
        navItem.link.addEventListener('mouseout', navItem.hoverHandler);
    });
} else {
    this.navItems.forEach(navItem => {
        navItem.link.removeEventListener('mouseover', navItem.hoverHandler);
        navItem.link.removeEventListener('mouseout', navItem.hoverHandler);
    });
}

Additionally you need to change your navItemHoverHandler to this:

navItemHoverHandler(navItem, event) { // reversed order
    event.preventDefault();
    console.log('Hover handler executed');
    console.log(navItem);
}
Coli
  • 927
  • 8
  • 28
  • Thanks for your answer. Unfortunately this does not help for my purpose. currentTarget is the HTML element, which as fired the event. But I need to pass an object to the handler (navItem). So what I need to do: pass a local argument... – Mikel Wohlschlegel Mar 27 '20 at 11:48
  • Ahh okay, then the solution depends on your code. Can you add a running code snippet, as @jsduniya has already suggested? – Coli Mar 27 '20 at 13:45