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 width="5%">${songs[i].nmbr}</td>
<td width="80%">
<div>
<img src=${songs[i].coverPath} >
</div>
<div >
<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 src="/assets/asset 72.svg" alt="">
</td>
<td width="12%" >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
});
};
CodePudding user response:
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.
CodePudding user response:
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");
CodePudding user response:
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`);
Above selector will try to find .audio_duration
inside tr
which has tr-index
value as specified by i
.
References :