Search code examples
mongodbtwitter-bootstrapexpresshandlebars.js

Bootstrap modals displaying only first record in MongoDB collection


I'm using NodeJS, Handlebars & Bootstrap to build a simple webapp. It should loop through a MongoDB collection of mock products and display it's fields.

I'm displaying the data in "product cards" (refer to image). When looping through the collection to create the cards it's reading each Product fine however, I have a button which opens a modal on each card - which should display the info related to that product, this doesn't work.

The issue is that all the modals display info related to only the first index in the MongoDB collection.

Here is my HTML code to display the products:

<div class="products">
  <h1>Featured Products</h1>
  <section class="product-list">
    <div class="product-container">
      {{#each Product}}
        <div class="card">
          <div class="title">{{this.name}}</div>
          <div class="image">
            <img src="https://source.unsplash.com/random/50×50/?fruit" />
          </div>
          <div class="cost">{{this.cost}}</div>
          <div>
            <button class="buy-button">Add to cart</button>
            <button
              class="buy-button"
              data-toggle="modal"
              data-target=".bd-example-modal-sm"
            >More Detail</button>
          </div>
        </div>

        <!-- modal -->
        <div
          class="modal fade bd-example-modal-sm"
          data-toggle="modal"
          aria-hidden="true"
        >
          <div class="modal-dialog modal-md">
            <div class="modal-content">
              <div class="carousel" data-ride="carousel">
                <div class="parentSlider">
                  <div class="parentSlider-cell">
                    <img
                      class="img"
                      src="https://source.unsplash.com/random/50×50/?fruit"
                    />
                  </div>
                  <div class="product-detail-text">
                    <p>Name: {{this.name}}</p>
                    <p>Price: {{this.cost}}</p>
                    <p>Description: {{this.description}}</p>
                  </div>
                </div>
              </div>
              
              <div class="modal-footer">
                <button class="buy-button" data-dismiss="modal">Close</button>
              </div>
            </div>
          </div>
        </div>
      {{/each}}
    </div>
  </section>
</div>

Solution

  • You need unique IDs for your modals. I'm assuming you are using Bootstrap for the functional part of opening the modals. The data-target attribute should be a unique selector for the modal you'd like to show.

    Currently, you have this set to .bd-example-modal-sm, which will open the first element that matches that selector.

    If you don't have a unique ID with your Product array, you can use the the special variable name @index to get the index of the current item you are looping over. If Product is an object, then you can use @key.

    Here is a simple example with an array of Products:

    const template_source = document.getElementById('template-source').innerHTML;
    const template = Handlebars.compile(template_source);
    
    const compiled_html = template({
      Product: [
        {
          name: 'Apples',
          cost: '1.00',
          description: 'Apples can be red or green.',
        },
        {
          name: 'Bananas',
          cost: '2.00',
          description: 'Bananas are usually yellow, but sometimes green or brown.',
        },
        {
          name: 'Coconuts',
          cost: '3.00',
          description: 'Coconuts when pealed are usually tan or brown.',
        },
      ]
    });
    
    const app = document.getElementById('app');
    app.innerHTML = compiled_html;
    @import url('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.1/css/bootstrap.min.css');
    
    
    /* misc */
    .product-container {
      display: flex;
      flex-wrap: wrap;
    }
    
    .card,
    .modal {
      padding: 0.5em;
      margin: 0.5em;
    }
    
    .card {
      background: gainsboro;
    }
    
    .modal {
      background: beige;
      max-width: 300px;
      max-height: 300px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.1/js/bootstrap.min.js"></script>
    
    <script id="template-source" type="text/x-handlebars-template">
       <div class="product-container">
         {{#each Product}}
           <div class="card">
             <p>Name: {{this.name}}</p>
             <p>Cost: {{this.cost}}</p>
             <button
               data-toggle="modal"
               data-target="#product-modal-{{@index}}"
             >
               More Detail
             </button>
           </div>
           
           <!-- modal -->
           <div
             id="product-modal-{{@index}}"
             class="modal"
             data-toggle="modal"
             aria-hidden="true"
           >
             <p>Name: {{this.name}}</p>
             <p>Cost: {{this.cost}}</p>
             <p>Description: {{this.description}}</p>
             <button data-bs-dismiss="modal" data-dismiss="modal">Close</button>
           </div>
         {{/each}}
       </div>
    </script>
    
    <div id="app"></div>