Duration not applying to all rows

Viewed 63

I created a music list similar to spotify dynamically is javascript and I am trying to add duration in all rows but the problem is duration is applying to first row only. And console not showing any error. i tried but couldn't figure what's the problem.

// Creating Music List
const tbody = document.querySelector("tbody");
// let create tr tags according to array length for list
for (let i = 0; i < songs.length; i++) {
    //let's pass the song details from the array
    let trTag = `<tr tr-index = ${i}>
    <td class="numberlist" width="5%">${songs[i].nmbr}</td>
    <td class="title" width="80%">
        <div>
            <img class="cover1" src=${songs[i].coverPath} >
        </div>
        <div class="songdetails">
            <h3>${songs[i].trackName}</h3>
            <h4>${songs[i].trackArtist}</h4>
            
        </div>
    </td>
    <td width="30%">
        <h4>${songs[i].trackAlbum}</h4>
    </td>
    <td width="15%">${songs[i].date}</td>
    <td width="5%">
        <img class="svg1" src="/assets/asset 72.svg" alt="">
    </td>
    <td width="12%" class="audio_duration">0:00</td>
    </tr> `;


    tbody.insertAdjacentHTML("beforeend", trTag); //inserting the tr inside tbody tag

    let trAudioDuration = tbody.querySelector(".audio_duration");
    let trAudioTag = new Audio(`audio/1.mp3`);

    trAudioTag.addEventListener("loadeddata", () => {
        let aDuration = trAudioTag.duration;
        let totalMin = parseInt(aDuration / 60);
        let totalSec = parseInt(aDuration % 60);
        if (totalSec < 10) {
            totalSec = `0${totalSec}`;
        };
        trAudioDuration.innerHTML = `${totalMin}:${totalSec}`; //passing total duration of song
    });
};

enter image description here

4 Answers

Ahh, this is a classic one! The problem lies with this:

let trAudioDuration = tbody.querySelector(".audio_duration");

See, querySelector, especially when using a class selector, only returns the first matched element. We need to use querySelectorAll here instead, which returns all of the matched elements.

Since querySelectorAll returns an array of multiple elements, we also need to handle the innerHTML setting a bit differently, so we use forEach to go through all of the matched elements and set the innerHTML on each of them.

Here's a snipped demonstrating the issue and the solution:

function problem() {
  let tag = document.querySelector(".duration");
  tag.innerHTML = "1:53";
}

function solution() {
  let tags = document.querySelectorAll(".duration2");
  
  tags.forEach((tag) => {
    tag.innerHTML = "8:41";
  });
}

function secure_solution() {
  let tags = document.querySelectorAll(".duration3");
  
  tags.forEach((tag) => {
    tag.textContent = "8:41";
  });
}

problem();
solution();
secure_solution();
<p>Problematic table</p>
<table id = "tbl">
  <tr><td class = "duration">0:00</td></tr>
  <tr><td class = "duration">0:00</td></tr>
  <tr><td class = "duration">0:00</td></tr>
  <tr><td class = "duration">0:00</td></tr>
  <tr><td class = "duration">0:00</td></tr>
  <tr><td class = "duration">0:00</td></tr>
</table>

<p>Working table</p>
<table id = "tbl2">
  <tr><td class = "duration2">0:00</td></tr>
  <tr><td class = "duration2">0:00</td></tr>
  <tr><td class = "duration2">0:00</td></tr>
  <tr><td class = "duration2">0:00</td></tr>
  <tr><td class = "duration2">0:00</td></tr>
  <tr><td class = "duration2">0:00</td></tr>
</table>

<p>Secure table</p>
<table id = "tbl3">
  <tr><td class = "duration3">0:00</td></tr>
  <tr><td class = "duration3">0:00</td></tr>
  <tr><td class = "duration3">0:00</td></tr>
  <tr><td class = "duration3">0:00</td></tr>
  <tr><td class = "duration3">0:00</td></tr>
  <tr><td class = "duration3">0:00</td></tr>
</table>

On an important side note you shouldn't use innerHTML to write the duration into the elements. Your duration is just text and contains zero html, so it is a better idea to use something like textContent instead. This ensures that some malicious party has no chance of injecting their own bad HTML to your page by somehow replacing duration data with HTML like <script>alert("hax")</script>. Notice how I actually just wrote HTML there, but StackOverflow has this thought out and your browser won't actually pop an alert box for you and only shows that as a text. If you ever DO need to use innerHTML, remember to sanitize it first.

As explained by @Swiffy in his answer problem is there due to tbody.querySelector(".audio_duration"); as it will select first .audio_duration element from tbody.

You can update that query selector like below and it should work well.

let trAudioDuration = tbody.querySelector(`tr[tr-index='${i}'] .audio_duration`);
let trAudioTag = tbody.querySelector(`tr[tr-index='${i}'] audio`);

Explanation :

  • Selector tr[tr-index='${i}'] .audio_duration will find tr element inside tbody with attribute tr-index and its value specified with i and any of its child (including child of child and so on) having class audio_duration and return that element with class audio_duration.
  • Selector tr[tr-index='${i}'] audio will find tr element inside tbody with attribute tr-index and its value specified with i and any of its child (including child of child and so on) audio element and return that audio element.

Note : querySelector will return first matching value from given selector. If you want to get all matching element then use querySelectorAll.

References :

Your issue is because your selector .audio_duration is searching the tbody where all the .audio_duration elements are.

One solution would be to get the tr that you inserted and perform your selector on that and then it will look for .audio_duration in the tr instead of the tbody.

let tr = tbody.lastElementChild;
let trAudioDuration = tr.querySelector(".audio_duration");

adding {src: "audiofilename"} in song array and modifying code like below worked out.

** - modified code

const tbody = document.querySelector("tbody");
// let create tr tags according to array length for list
for (let i = 0; i < songs.length; i++) {
    //let's pass the song details from the array
    let trTag = `<tr tr-index = ${i}>
    <td class="numberlist" width="5%">${songs[i].nmbr}</td>
    <td class="title" width="80%">
        <div>
            <img class="cover1" src=${songs[i].coverPath} >
        </div>
        <div class="songdetails">
            <h3>${songs[i].trackName}</h3>
            <h4>${songs[i].trackArtist}</h4>
            
        </div>
    </td>
    <td width="30%">
        <h4>${songs[i].trackAlbum}</h4>
    </td>
    <td width="15%">${songs[i].date}</td>
    <td width="5%">
        <img class="svg1" src="/assets/asset 72.svg" alt="">
    </td>
    <td width="12%" id="${songs[i].src}" class="audio_duration">0:00</td>**
    <audio class="${songs[i].src}" src="${songs[i].filePath}"> </audio>**
    </tr > `;


    tbody.insertAdjacentHTML("beforeend", trTag); //inserting the tr inside tbody tag

    let trAudioDuration = tbody.querySelector(`#${songs[i].src}`);**
    let trAudioTag = tbody.querySelector(`.${songs[i].src}`);**

    trAudioTag.addEventListener("loadeddata", () => {
        let aDuration = trAudioTag.duration;
        let totalMin = parseInt(aDuration / 60);
        let totalSec = parseInt(aDuration % 60);
        if (totalSec < 10) {
            totalSec = `0${totalSec} `;
        };
        trAudioDuration.textContent = `${totalMin}:${totalSec} `; //passing total duration of song

    });
};```
Related