J0nny
J0nny

Reputation: 11

Current iteration of for loop returns undefined and return of a function is undefined

I'm currently hardly focusing on vanilla JavaScript without frameworks like jQuery and co. Unfortunately, I got to know two errors that have been mentally disturbing me the last days. I already searched the internet, but I think that the errors are pretty much too individual than for finding a general solution or work aroung on the net.

Here is my index.htlml:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Songs</title>
  </head>
  <body>
    <div id="body-inner">
      <div class="playlist"></div> <br>
      <div class="song">
        <div class="current"></div> <br>
        <span class="time"></span>
        <button class="play">Play</button>
        <button class="pause">Pause</button>
      </div>
    </div>

    <script src="main.js"></script>
  </body>
</html>

And here is my main.js:

const [playlistOut, currentOut, timeOut] = [
  document.querySelector(".playlist"),
  document.querySelector(".current"),
  document.querySelector(".time")
];

// Create the App Object
let app = {
  playlists: {
    playlistSongs: [
      "Backseat",
      "Unforgettable",
      "BlueSkies",
      "Thunder",
      "Overflow",
      "Up&Down",
      "DoMyThing",
      "Heroes",
      "Miracles",
      "CakeByTheOcean",
      "InTheEnd",
      "Feel"
    ]
  },
  songDurations: {
    Backseat: 195,
    Unforgettable: 252,
    BlueSkies: 323,
    Thunder: 182,
    Overflow: 249,
    UpDown: 154,
    DoMyThing: 534,
    Heroes: 420,
    Miracles: 236,
    CakeByTheOcean: 379,
    InTheEnd: 231,
    Feel: 432
  },
  start: () => {
    showPlaylist(app.playlists.playlistSongs);
    clickSong();
  }
};

window.onload = app.start;

let test = {
  minutes: false,
  seconds: false
};

// Configure Timer
function startTimer(duration, display) {
  var timer = duration,
    minutes,
    seconds;
  if (duration != isNaN) {
    var timer = duration,
      minutes,
      seconds;
    interval = setInterval(() => {
      minutes = parseInt(timer / 60, 10);
      seconds = parseInt(timer % 60, 10);

      minutes = minutes < 10 ? "0" + minutes : minutes;
      seconds = seconds < 10 ? "0" + seconds : seconds;
      display.textContent = minutes + ":" + seconds;
      test.minutes = minutes;
      test.seconds = seconds;
      console.log("Minutes Func: " + minutes);
      console.log("Seconds Func: " + seconds);

      if (--timer < 0) {
        timer = 0;
      }
    }, 1000);
  } else {
    var error = "The given duration is not a number.";
    console.log(error);
    alert(error);
  }
  console.log("Inside Func: " + minutes);
  return { success: true, min: minutes, secs: seconds };
}

console.log("test.minutes: " + test.minutes);
console.log("test.seconds: " + test.seconds);

// Init Timer
let timerfunc = startTimer(60, timeOut);
console.log(timerfunc.min);

// Manage Play and Pause Buttons
document.querySelector(".pause").addEventListener("click", () => {
  clearInterval(interval);
});
document.querySelector(".play").addEventListener("click", () => {
  let currentDuration = timerfunc.min * 60 + timerfunc.secs;
  console.log(currentDuration);
  startTimer(currentDuration, timeOut);
});

// Show Playlist
function showPlaylist(playlist) {
  if (playlist.length > 0) {
    var constructor = {
      content: new Array()
    };
    for (var i = 0; i < playlist.length; i++) {
      constructor.content.push(
        '<a href="#" id="' +
          playlist[i] +
          '" class="song">' +
          playlist[i] +
          "</a><br>"
      );
    }
    playlistOut.innerHTML = constructor.content.join("");
    return true;
  } else {
    var error = "There is no playlist existing with this name.";
    console.log(error);
    return false;
  }
}

// Get the Time of the Element Clicked and Start the Timer

/* FIXME FIXME FIXME
songElements[i] returns undefined in l. 109 ff.
FIXME FIXME FIXME */

function clickSong() {
  var songElements = document.querySelectorAll(".song");
  for (var i = 0; i < songElements.length; i++) {
    songElements[i].addEventListener("click", function(e) {
      e.preventDefault;
      console.log(songElements[i].id);
      var songDuration = app.songDurations.songElements[i].id;
      startTimer(songDuration, timeOut);
      currentOut.innerHTML = "Currently Playing: " + songElements[i].id;
    });
  }
}

As you can see, the app is about a song playlist where you should be able to dynamically select a song from the playlist and be able to start and stop the song manually. Unfortunately, the function startTimer() on line 53 doesn't return the wanted values so the script can get the current time in seconds to start the timer after a manual stop by the user again. I've already sticked the code out with a bunch of console.log for better understanding of the scoping of the variables, but I still couldn't manage to solve the problem.

The next and more crucial error can be found at line 133 inside the function clickSong(). I'm getting all the elements with the class of .song inside there and returning them in a node list. In the next turn, I'm running a for loop through said node list to apply a click eventHandler at the excact element clicked. But there is already the problem: I cant get the item clicked, in fact the current iteration at line 136 of the for loop always returns undefined. I sadly have no idea at all how to approach a fix for this.

Do you have an idea or have you maybe experienced a similar situation? I'm glad in case of help.

Thank you in advance, J0nny

Upvotes: 1

Views: 259

Answers (2)

Pavlo
Pavlo

Reputation: 1197

I changed a lot, so just try to look through the code and see what is changed

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Songs</title>
</head>
<body>
<div id="body-inner">
    <div class="playlist"></div> <br>
    <div class="controls">
        <div class="current"></div> <br>
        <span class="time"></span>
        <button class="play">Play</button>
        <button class="pause">Pause</button>
    </div>
</div>

<script src="main.js"></script>
</body>
</html>

JAVASCRIPT

const [playlistOut, currentOut, timeOut] = [
    document.querySelector(".playlist"),
    document.querySelector(".current"),
    document.querySelector(".time")
];

// Create the App Object
let app = {
    playList: [
        "Backseat",
        "Unforgettable",
        "BlueSkies",
        "Thunder",
        "Overflow",
        "Up&Down",
        "DoMyThing",
        "Heroes",
        "Miracles",
        "CakeByTheOcean",
        "InTheEnd",
        "Feel"
    ],
    songDurations: {
        Backseat: 195,
        Unforgettable: 252,
        BlueSkies: 323,
        Thunder: 182,
        Overflow: 249,
        UpDown: 154,
        DoMyThing: 534,
        Heroes: 420,
        Miracles: 236,
        CakeByTheOcean: 379,
        InTheEnd: 231,
        Feel: 432
    },
    start: () => {
        renderPlayList(app.playList);
        clickSong();
    }
};

window.onload = app.start;

let [playListInterval, currentMinute, currentSecond] = [0, 0, 0];


// Configure Timer
function startTimer(duration, display) {
    currentMinute = parseInt(duration / 60, 10);
    currentSecond = parseInt(duration % 60, 10);
    if (!isNaN(duration)) {
        let timer = duration;
        playListInterval = setInterval(() => {
            currentMinute = parseInt(timer / 60, 10);
            currentSecond = parseInt(timer % 60, 10);

            const minutes = currentMinute < 10 ? "0" + currentMinute : currentMinute;
            const seconds = currentSecond < 10 ? "0" + currentSecond : currentSecond;
            display.textContent = minutes + ":" + seconds;
            if (--timer < 0) {
                timer = 0;
            }
        }, 1000);
    } else {
        const error = "The given duration is not a number.";
        console.log(error);
        alert(error);
    }
}


// Manage Play and Pause Buttons
document.querySelector(".pause").addEventListener("click", () => {
    if (playListInterval !== 0) {
        clearInterval(playListInterval);
        playListInterval = 0;
    }
});
document.querySelector(".play").addEventListener("click", () => {
    let currentDuration = currentMinute * 60 + currentSecond;
    startTimer(currentDuration, timeOut);
});

// Show Playlist
function renderPlayList(playlist) {
    if (playlist.length > 0) {
        const content = [];
        for (let i = 0; i < playlist.length; i++) {
            content.push(
                '<a href="#" id="' +
                playlist[i] +
                '" class="song">' +
                playlist[i] +
                "</a><br>"
            );
        }
        playlistOut.innerHTML = content.join("");
        return true;
    } else {
        const error = "There is no playlist existing with this name.";
        console.log(error);
        return false;
    }
}

// Get the Time of the Element Clicked and Start the Timer

function clickSong() {
    const songElements = document.querySelectorAll(".song");
    for (let i = 0; i < songElements.length; i++) {
        const currentElement = songElements[i];
        currentElement.addEventListener("click", function (event) {
            event.preventDefault();
            const songDuration = app.songDurations[currentElement.id];
            if (playListInterval !== 0) {
                clearInterval(playListInterval);
                playListInterval = 0;
            }
            startTimer(songDuration, timeOut);
            currentOut.innerHTML = "Currently Playing: " + currentElement.id;
        });
    }
}

Notice that in the html that i changed <div class="song"> to <div class="controls">, when i tried to play/pause, then it told me that it wasn't a duration, because it had the same eventListener as the song list

Upvotes: 1

trincot
trincot

Reputation: 350270

The thing is that your i variable will have reached the end value of the for loop before any click event triggers the callback. So whichever click event occurs, the callback will use a value for i that is out of range.

You can easily solve this by using let instead of var, as that creates a new instance of the variable for each iteration, and it will be that instance that will be referenced in the asynchronous callback.

for (let i = 0; i < songElements.length; i++) {

A second problem occurs in this expression:

 app.songDurations.songElements[i].id

Your app.songDurations object has no songElements property, so that will not work. Did you intend this?

 app.songDurations[songElements[i].id]

Upvotes: 1

Related Questions