Mikel Wohlschlegel
Mikel Wohlschlegel

Reputation: 1476

Add and remove eventListeners with callback function and arguments

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.

Upvotes: 2

Views: 285

Answers (1)

Coli
Coli

Reputation: 1031

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);
}

Upvotes: 1

Related Questions