TypeError: el.classList is undefined

I am not sure what is wrong here since the code is working.

203:12 TypeError: el.classList is undefined”

I’m confused here.

Code:

This was part of a different code, just removed.

button.classList.add("activated");
(function iife() {
  "use strict";

  function show(el) {
    el.classList.remove("hide");
  }

  function hide(el) {
    el.classList.add("hide");
  }

  function getButtonContainer(el) {
    while (el.classList.contains("playButton") === false) {
      el = el.parentNode;
    }
    return el;
  }

  function hideAllButtons(button) {
    const buttons = button.querySelectorAll(".play, .pause, .speaker");
    for (let i = 0; i < buttons.length; i++) {
      hide(buttons[i]);
    }
  }

  function getPlay(button) {
    return button.querySelector(".play");
  }

  function getPause(button) {
    return button.querySelector(".pause");
  }

  function getSpeaker(button) {
    return button.querySelector(".speaker");
  }
  
  
  function showPlayButton(button) {
    const play = getPlay(button);
    hideAllButtons(button);
    show(play);
    
  }

  function isPlaying(button) {
    const play = getPlay(button);
    return play.classList.contains("hide");
  }

  function pauseAllButtons() {
    let buttons = document.querySelectorAll(".playButton");
    for (let i = 0; i < buttons.length; i++) {
      if (isPlaying(buttons[i])) {
        showPlayButton(buttons[i]);
      }
    }
  }

  function showPauseButton(button) {
    const pause = getPause(button);
    pauseAllButtons();
    hideAllButtons(button);
    show(pause);
    button.classList.add("activated");
  }

  function showSpeakerButton(button) {
    const speaker = getSpeaker(button);
    hideAllButtons(button);
    show(speaker);
  }

  function getAudio() {
    return document.querySelector("audio");
  }

  function playAudio(player, src) {
    player.volume = 1.0;
    if (player.getAttribute("src") !== src) {
      player.setAttribute("src", src);
    }
    player.play();
  }

  function showButton(button, opts) {
    if (opts.playing) {
      showPlayButton(button);
    } else {
      showPauseButton(button);
    }
  }

  function pauseAudio(player) {
    player.pause();
  }

  function manageAudio(player, opts) {
    if (opts.playing) {
      pauseAudio(player);
    } else {
      playAudio(player, opts.src);
    }
  }

  function playButton(button) {
    const player = getAudio();
    const playing = isPlaying(button);
    showButton(button, {
      playing
    });
    manageAudio(player, {
      src: button.getAttribute("data-audio"),
      playing
    });
  }


  function showPause(button) {
    if (isPlaying(button)) {
      showPauseButton(button);
    }
  }

  function showSpeaker(button) {
    if (isPlaying(button)) {
      showSpeakerButton(button);
    }
  }

  function playButtonClickHandler(evt) {
    const button = getButtonContainer(evt.target);
    playButton(button);
  }
  
     function playButtonMouseoverHandler(evt) {
      const button = getButtonContainer(evt.target);
      showPause(button);
   }

  function playButtonMouseoutHandler(evt) {
    const button = getButtonContainer(evt.target);
    showSpeaker(button);
  }

  function initButton(selector) {
    const wrapper = document.querySelector(selector);
    wrapper.addEventListener("click", playButtonClickHandler);
    wrapper.addEventListener("mouseover", playButtonMouseoverHandler);
    wrapper.addEventListener("mouseout", playButtonMouseoutHandler);
  }
  initButton(".wrapc");
}());

The issue is with the following part of the code:

    while (el.classList.contains("playButton") === false) {
      el = el.parentNode;
    }

When the element doesn’t have a classList, that’s when the error occurs. You can deal with that by first checking if the element has made its way up to the document, before checking the classList.

    while (el !== document && el.classList.contains("playButton") === false) {
      el = el.parentNode;
    }

We can improve that further by moving the playButton check out to a separate function too.

    function notPlayButton(el) {
        return el.classList.contains("playButton") === false;
    }
    while (el !== document && notPlayButton(el)) {
      el = el.parentNode;
    }

And it’s usually better if the function name is positive, rather than negative, so let’s check for isPlayButton instead.

    function isPlayButton(el) {
        return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }

What that code does in words is: when climbing parents in the DOM, if we haven’t gone all the way up to the document, and we haven’t yet found the play button, check the next parent.

That is the preferred way of doing things.

1 Like

I delete this:

  function getButtonContainer(el) {
    while (el.classList.contains("playButton") === false) {
      el = el.parentNode;
    }
    return el;
  }

And add this:

https://jsfiddle.net/7qcv5pr9/3/

  function isPlayButton(el) {
        return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }

What did I do wrong?

Then these would get changed to:

  function playButtonClickHandler(evt) {
    const button = getButtonContainer(evt.target);
    playButton(button);
  }

  function playButtonMouseoverHandler(evt) {
    const button = getButtonContainer(evt.target);
    showPause(button);
  }

  function playButtonMouseoutHandler(evt) {
    const button = getButtonContainer(evt.target);
    showSpeaker(button);
  }

from
getButtonContainer

to:

isPlayButton

I did that here:

203:12 ReferenceError: el is not defined”

  function playButtonClickHandler(evt) {
    const button = isPlayButton(evt.target);
    playButton(button);
  }

  function playButtonMouseoverHandler(evt) {
    const button = isPlayButton(evt.target);
    showPause(button);
  }

  function playButtonMouseoutHandler(evt) {
    const button = isPlayButton(evt.target);
    showSpeaker(button);
  }

Original working code before it stopped working.
https://jsfiddle.net/8gajsp3v/7/

Deleting the whole getButtonContainer function is not what you should have done.

I don’t have the time or the ability to help you further with this today either.

From the original code that you posted, the only code that you should delete is the while loop, and replace that while-loop code with the code that I posted, which is:

    function isPlayButton(el) {
        return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }

This was the original code:

I’m not just replacing this.

  function getButtonContainer(el) {
    while (el.classList.contains("playButton") === false) {
      el = el.parentNode;
    }
    return el;
  }

With this?

    function isPlayButton(el) {
        return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }

I’m confused.

You are correct, that is not what you should be doing.

To repeat what I told you before, replace the while loop with all of the code that I posted, that being:

    function isPlayButton(el) {
        return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }

You will end up with a function that has another function inside of it, and a while loop below it, and below that a return statement. That is all correct.

I will not post the exact code that you need, for that is amazingly lazy. I will instead force you to understand what you are being told.

2 Likes

This can’t be what you meant?

  function getButtonContainer(el) {
    function isPlayButton(el) {
      return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }
  }

or even this

Am I on the right track here?

Still an error

TypeError: button.classList is undefined"

But this one works.

“a function that has another function inside of it,”

I did that.

  function getButtonContainer(el) {
    function isPlayButton(el) {
      return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }
    return el;
  }

Well done, for that is what you were instructed to do.

What else do I have to do?

I am still receiving an error message.

TypeError: button.classList is undefined"

  function getButtonContainer(el) {
    function isPlayButton(el) {
      return el.classList.contains("playButton") === true;
    }
    while (el !== document && !isPlayButton(el)) {
      el = el.parentNode;
    }
    return el;
  }

How odd, for it all works perfectly well for me.

This is when we start to investigate how it behaves in different browsers.

Web browsers work differently from each other. We know that because if they all worked the same then there would be no need for any other web browser.

What is the web browser that you experience the problem with? A version number can help too.

1 Like

Throws an error for me when i click the left button. The line in question (button.classList.add("activated") inside showPauseButton) gets executed at least 3 times; the third time through, button is document, and document.classList is not a valid construction.

1 Like

That’s the magical thing about different web browsers. I refuse though to do anything about it until I receive instructions about which web browser the problem is occurring in. I will not be running the code in 10 different web browsers either to try and figure out which one has the problem.

That way I can test the code in the affected web browser and be sure that the issue is fixed.

Who’s willing to share first?

Chrome. Latest.

Thanks. Which code is that with? Is it the code from https://jsfiddle.net/8gajsp3v/7/ or the code from https://jsfiddle.net/tg056zv4/ or both?

Also Edge. Latest.

(Did you only test in FF, i assume?)

It’s nice that the getButtonContainer problem seems to be all fixed.

Thank you @m_hutley for helping us to understand that a problem still exists with the left-most button. My checks on that consisted of clicking the left button, and clicking it again to turn it off.

That was not enough to exhibit the problem.

The problem is experienced by clicking on only the left play button, then while the play button is active and showing pause, moving the mouse off of the pause button.

This is the kind of trouble that occurs when there are no reliable tests for the code. Thanks again.

Sounds like there’s too many events tied to a single button, and they’re interfering with one another.

It’s only the mouseout event that’s happening. It triggers though when passing over every element, such as polygon or svg.

A simple solution to this is to check if the button is document. That occurs when the button container can’t be found. We can just ignore things when that occurs.

  function playButtonMouseoverHandler(evt) {
    const button = getButtonContainer(evt.target);
    if (button === document) {
      return;
    }
    showPause(button);
  }

  function playButtonMouseoutHandler(evt) {
    const button = getButtonContainer(evt.target);
    if (button === document) {
      return;
    }
    showSpeaker(button);
  }

Is there a better solution where we can get the mouseover or mouseout event to only occur when crossing the boundary of the `.wrapc’ element?