0

I'm trying to build a simple toggle class with JS, but struggle to pass variables to eventlisteners as I need also to remove them.

Is it possible to pass btn and possibly more parameters into handleClick()? I can't pass directly or use anonymous function as I need to remove listeners in destroy().

If I use e.target I sometimes receive span or svg as target so ideally I want to pass btn variable directly.

html:

<div class="toggle" id="toggle-1">
        <button class="toggle-button">
            <span>open toggle</span>
            <svg>...</svg> <!--icon --> 
        </button>
        
        <div class="toggle-content" hidden>
            ...content
        </div>
    </div>
class Toggle {
    constructor(element) {
        this.el = document.querySelector(element);
        this.addListeners();
    }

    addListeners() {
        const btn = this.el.querySelector('button');
        btn.addEventListener('click', this.handleClick) // <!--- how do I pass btn here?
    }

    handleClick = (e, btn) => {  <!--- how do I receive btn here?
        const panelID = btn.closest('.toggle').id;
        this.open(panelID);
    }


    open(panelID) {
        // open functionality
    }



    destroy() {
        const btn = this.el.querySelector('button');
        btn.removeEventListener('click', this.handleClick)
    }
}
Runnick
  • 613
  • 2
  • 12
  • 30
  • The signature for `handleClick` should accept the `event`, which holds the `target` ("button"). – Mr. Polywhirl Mar 13 '23 at 14:00
  • 1
    See [How to access the correct `this` inside a callback](/q/20279484/4642212) and [What is the difference between class method vs. class field function vs. class field arrow function?](/q/56055658/4642212). Consider using `handleClick = (event) => {`…`}` instead of `handleClick(event){`…`}`; `this.open` will not be correct, otherwise. In `destroy`, `handleClick` doesn’t exist; use `this.handleClick` there. – Sebastian Simon Mar 13 '23 at 14:03
  • @SebastianSimon thanks, I edited the code. So is it possible to pass btn somehow in `handleClick` or do I need to check whether target is `btn` or it's parent is `btn`? – Runnick Mar 13 '23 at 14:11
  • @Runnick Have you tried the advice of Mr. Polywhirl? Use [`target`](//developer.mozilla.org/en/docs/Web/API/Event/target). – Sebastian Simon Mar 13 '23 at 14:17
  • @SebastianSimon yes, the problem is I sometimes receive `span` or `svg` as a `e.target` – Runnick Mar 13 '23 at 14:24
  • Then use `e.target.closest("button")`. – Sebastian Simon Mar 13 '23 at 14:34

1 Answers1

0

You can bind the handler to this:

const listener = this.handleToggle.bind(this);

To cleanup (remove) the listener(s), you can establish a cleanup() function on the instance that you can call inside destroy().

class Toggle {
  constructor(element) {
    this.el = document.querySelector(element);
    this.addListeners();
  }
  addListeners() {
    const
      button = this.el.querySelector('.toggle-button'),
      listener = this.handleToggle.bind(this); // Bind function
    button.addEventListener('click', listener);
    this.cleanup = () => {
      button.removeEventListener('click', listener);
    };
  }
  handleToggle(event) {
    const
      toggle = event.target.closest('.toggle'),
      content = toggle.querySelector('.toggle-content'),
      button = toggle.querySelector('.toggle-button');
    if (content.hasAttribute('hidden')) {
      content.removeAttribute('hidden');
      button.innerText = 'Collapse';
    } else {
      content.setAttribute('hidden', '');
      button.innerText = 'Expand';
    }
  }
  destroy() {
    this.cleanup(); // Hook to remove listeners
  }
}

const toggle = new Toggle('#toggle-1'), handleDestroy = () => toggle.destroy();
<div class="toggle" id="toggle-1">
  <button class="toggle-button">Expand</button>
  <div class="toggle-content" hidden>
    ...content
  </div>
</div>
<button onclick="handleDestroy()">Destroy</button>

Alternatively, you can return the cleanup function from addListeners. This approach looks very similar to the useEffect hook in React:

constructor(element) {
  this.el = document.querySelector(element);
  this.cleanup = this.addListeners(); // Assign the resulting callback
}

addListeners() {
  const
    button = this.el.querySelector('.toggle-button'),
    listener = this.handleToggle.bind(this); // Bind function
  button.addEventListener('click', listener);
  return () => {
    button.removeEventListener('click', listener);
  };
}
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132