Like Count button + Card layout not working properly


I am supposed to have three cards on large screens, 2 in tablets, 1 in mobile and all without hard coded html. Only through javascript. However, when I try to add three cards in the same row, it takes the same movie for the entire row and then the next one for the second row and so on… Also the button only works for the first card… There must be smth wrong in my loop…

This is my code so far:

var parsedMovies = JSON.parse(movies);

for (let i = 0; i < parsedMovies.length; i++) {
  document.getElementById("cards").innerHTML += `
    
    <div class="card-group">
     <div class="card mb-3 bg-dark text-light" style="max-width: 540px;">
       <div class="row g-0 ">
         <div class="col-md-4 ">
           <img src="${parsedMovies[i].image}" class="img-fluid rounded-start" alt="...">
         </div>
         <div class="col-md-8">
           <div class="card-body">
             <h5 class="card-title">${parsedMovies[i].title}</h5>
             <p class="card-text">${parsedMovies[i].plot}</p>
             <p class="card-text"><small class="text-muted"> Year: ${parsedMovies[i].year} <br> Director: ${parsedMovies[i].director} <br> Actors: ${parsedMovies[i].actors}</small>
           
        <div class="voting">
            <button id="likebtn">
                <i>👍</i>
            </button>
            <input type="number" id="input1" value="${parsedMovies[i].likes}">
            <button id="dislikebtn">
                <i>👎</i>
            </button>
            <input type="number" id="input2" value="${parsedMovies[i].dislikes}">
        </div>
             </p>

           </div>
         </div>
       </div>
       
     </div>
     <div class="card mb-3 bg-dark text-light" style="max-width: 540px;">
       <div class="row g-0 ">
         <div class="col-md-4 ">
           <img src="${parsedMovies[i].image}" class="img-fluid rounded-start" alt="...">
         </div>
         <div class="col-md-8">
           <div class="card-body">
             <h5 class="card-title">${parsedMovies[i].title}</h5>
             <p class="card-text">${parsedMovies[i].plot}</p>
             <p class="card-text"><small class="text-muted"> Year: ${parsedMovies[i].year} <br> Director: ${parsedMovies[i].director} <br> Actors: ${parsedMovies[i].actors}</small>
           
        <div class="voting">
            <button id="likebtn">
                <i>👍</i>
            </button>
            <input type="number" id="input1" value="${parsedMovies[i].likes}">
            <button id="dislikebtn">
                <i>👎</i>
            </button>
            <input type="number" id="input2" value="${parsedMovies[i].dislikes}">
        </div>
             </p>

           </div>
         </div>
       </div>
       
     </div>
     <div class="card mb-3 bg-dark text-light" style="max-width: 540px;">
       <div class="row g-0 ">
         <div class="col-md-4 ">
           <img src="${parsedMovies[i].image}" class="img-fluid rounded-start" alt="...">
         </div>
         <div class="col-md-8">
           <div class="card-body">
             <h5 class="card-title">${parsedMovies[i].title}</h5>
             <p class="card-text">${parsedMovies[i].plot}</p>
             <p class="card-text"><small class="text-muted"> Year: ${parsedMovies[i].year} <br> Director: ${parsedMovies[i].director} <br> Actors: ${parsedMovies[i].actors}</small>
           
        <div class="voting">
            <button id="likebtn">
                <i>👍</i>
            </button>
            <input type="number" id="input1" value="${parsedMovies[i].likes}">
            <button id="dislikebtn">
                <i>👎</i>
            </button>
            <input type="number" id="input2" value="${parsedMovies[i].dislikes}">
        </div>
             </p>

           </div>
         </div>
       </div>
       
     </div>

    </div>
    `;

  let likebtn = document.querySelector("#likebtn");
  let dislikebtn = document.querySelector("#dislikebtn");
  let input1 = document.querySelector("#input1");
  let input2 = document.querySelector("#input2");

  likebtn.addEventListener("click", () => {
    input1.value = parseInt(input1.value) + 1;
  });

  dislikebtn.addEventListener("click", () => {
    input2.value = parseInt(input2.value) + 1;
  });

Ids are unique let likebtn = document.querySelector("#likebtn") just gives you the first matching element. You would have to use querySelectorAll and a class or element selector and then loop the collection and attach listeners to each of the elements.

We really need a live example to better understand what is going on. And as for the layout, we can’t say much without the CSS.

Hi lasjorg, thank you for your reply.
How may I share a live example?

You can create a GitHub repo with the code. Or use some online environment like CodeSandbox.

Thank you

If you look at the Bootstrap docs for the card component you can see that the parent of all the cards should be the .card-group element, so that is what you want to append to in the DOM.

But I’m not so sure using the Card groups is the best option, you likely have to use the grid system instead of the card-layout. The grid-cards section has examples.

Maybe it’s just me, but I think creating your own card grid using flexbox or CSS Grid is easier. Simplified grid, but it is almost all you need (I have just reused the BS class name).

.card-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(540px, 1fr));
  gap: 2rem;
  justify-items: center;
}

Just as with the buttons your inputs are also using ids, as said, ids have to be unique.

One option is after you have looped all the buttons and attached event listeners, inside the event listener handler function you can use event.currentTarget.nextElementSibling to get to the input element. This isn’t necessarily the best approach as you are now tied to the DOM structure but it’s at least one option. I’m guessing you will also want to use localStorage to save the likes/dislikes (or a database).

I would suggest using forEach instead of a for loop and use some destructuring to get cleaner access to the properties.


I honestly didn’t give it too much thought, but here is an example anyway. It can likely be improved.

Example
// Using import/export
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

// movies.js === export const parsedMovies = [{}, {}, ...]
// movies.html === <script type="module" src="../js/script.js"></script>

import { parsedMovies } from '../json/movies.js';
const cards = document.querySelector('.card-group');

let cardHTML = '';

parsedMovies.forEach((movie) => {
  const { image, title, plot, year, director, actors, likes, dislikes } = movie;

  cardHTML += `
    
   <div class="card mb-3 bg-dark text-light" style="max-width: 540px;">
     <div class="row g-0 ">
       <div class="col-md-4 ">
         <img src="${image}" class="img-fluid rounded-start" alt="...">
       </div>
       <div class="col-md-8">
         <div class="card-body">
           <h5 class="card-title">${title}</h5>
           <p class="card-text">${plot}</p>
           <p class="card-text"><small class="text-muted"> Year: ${year} <br> Director: ${director} <br> Actors: ${actors}</small>
         
      <div class="voting">
          <button class="likeBtn" id="likebtn">
              <i>👍</i>
          </button>
          <input type="number" id="input1" value="${likes}">
          <button class="dislikeBtn" id="dislikebtn">
              <i>👎</i>
          </button>
          <input type="number" id="input2" value="${dislikes}">
      </div>
           </p>

         </div>
       </div>
     </div>
   </div>
  `;
});

cards.insertAdjacentHTML('beforeend', cardHTML);

document.querySelectorAll('.likeBtn').forEach((likeBtn) =>
  likeBtn.addEventListener('click', ({ currentTarget }) => {
    // depends on DOM structure
    const likeInput = currentTarget.nextElementSibling;
    likeInput.value = Number(likeInput.value) + 1;
  })
);

document.querySelectorAll('.dislikeBtn').forEach((dislikeBtn) =>
  dislikeBtn.addEventListener('click', ({ currentTarget }) => {
    // depends on DOM structure
    const dislikeInput = currentTarget.nextElementSibling;
    dislikeInput.value = Number(dislikeInput.value) + 1;
  })
);

Thank you for taking the time ! I really appreciate it! I will try it. :smiley:

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.