Home > Mobile >  Random null response on dynamically created html elements
Random null response on dynamically created html elements

Time:03-30

I'm receiving a very strange response from this code. My idea was:

  1. To create 8 html elements mapping an array.
  2. Selecting the #cart-button id to attach an eventlistener (click) and passing to it the id of the of the html element (I'm trying to obtain these values from the key attribute).

Sometimes in the console I'm obtaining all the results OK (1 clicked, 2, clicked...), but sometimes the result is null clicked.

I'm trying to understand why this is happening. Thank you very much!

// Product class

class Product {
  constructor(id, title, price, img) {
    this.id = id
    this.title = title
    this.price = price
    this.img = img
  }

  productCard = () => {
    return `<div  key=${this.id}><img  src=${this.img}> <div > <p >${this.title}</p> <svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M12,4.595c-1.104-1.006-2.512-1.558-3.996-1.558c-1.578,0-3.072,0.623-4.213,1.758c-2.353,2.363-2.352,6.059,0.002,8.412 l7.332,7.332c0.17,0.299,0.498,0.492,0.875,0.492c0.322,0,0.609-0.163,0.792-0.409l7.415-7.415 c2.354-2.354,2.354-6.049-0.002-8.416c-1.137-1.131-2.631-1.754-4.209-1.754C14.513,3.037,13.104,3.589,12,4.595z M18.791,6.205 c1.563,1.571,1.564,4.025,0.002,5.588L12,18.586l-6.793-6.793C3.645,10.23,3.646,7.776,5.205,6.209 c0.76-0.756,1.754-1.172,2.799-1.172s2.035,0.416,2.789,1.17l0.5,0.5c0.391,0.391,1.023,0.391,1.414,0l0.5-0.5 C14.719,4.698,17.281,4.702,18.791,6.205z" /> </svg> </div> <p >£${this.price}</p></div>`
  };
};

// Mock data

const ProductsList = [
  new Product(1, 'Minna', '9.99', 'https://images.unsplash.com/photo-1555982105-d25af4182e4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(2, 'Palma', '109', 'https://images.unsplash.com/photo-1508423134147-addf71308178?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(3, 'Bergdis', '25.99', 'https://images.unsplash.com/photo-1449247709967-d4461a6a6103?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(4, 'Danjal', '25', 'https://images.unsplash.com/reserve/LJIZlzHgQ7WPSh5KVTCB_Typewriter.jpg?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(5, 'Kornus', '15', 'https://images.unsplash.com/photo-1467949576168-6ce8e2df4e13?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(6, 'Redin', '9.99', 'https://images.unsplash.com/photo-1544787219-7f47ccb76574?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(7, 'Jensina', '167.99', 'https://images.unsplash.com/photo-1550837368-6594235de85c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=400&h=400&q=80'),
  new Product(8, 'Steen', '13', 'https://images.unsplash.com/photo-1551431009-a802eeec77b1?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&h=400&q=80'),
];

class HtmlHandler {
  constructor() {
    this.createHTMLCards();
    this.addTheListeners();
  }

  html = '';

  createHTMLCards() {
    ProductsList.forEach(product => this.html  = product.productCard());
    // Create a new div element after the header
    const headerArea = document.querySelector('#products-area > nav');
    headerArea.insertAdjacentHTML('afterend', this.html);
  }

  // Add a product to the cart
  addTheListeners() {
    const cartButton = document.querySelectorAll('.cart-button');

    cartButton.forEach((button) => {
      button.addEventListener('click', (e) => {
        const productId = e.target.parentElement.parentElement.getAttribute('key');
        console.log(`${productId} clicked`);
      })
    })
  }

}

const htmlHandler = new HtmlHandler;
const product = new Product;
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
html {
  scroll-behavior: smooth;
}

.work-sans {
  font-family: 'Work Sans', sans-serif;
}

#menu-toggle:checked #menu {
  display: block;
}

.hover\:grow {
  transition: all 0.3s;
  transform: scale(1);
}

.hover\:grow:hover {
  transform: scale(1.02);
}

.carousel-open:checked .carousel-item {
  position: static;
  opacity: 100;
}

.carousel-item {
  -webkit-transition: opacity 0.6s ease-out;
  transition: opacity 0.6s ease-out;
}

#carousel-1:checked~.control-1,
#carousel-2:checked~.control-2,
#carousel-3:checked~.control-3 {
  display: block;
}

.carousel-indicators {
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  bottom: 2%;
  left: 0;
  right: 0;
  text-align: center;
  z-index: 10;
}

#carousel-1:checked~.control-1~.carousel-indicators li:nth-child(1) .carousel-bullet,
#carousel-2:checked~.control-2~.carousel-indicators li:nth-child(2) .carousel-bullet,
#carousel-3:checked~.control-3~.carousel-indicators li:nth-child(3) .carousel-bullet {
  color: #000;
  /*Set to match the Tailwind colour you want the active one to be */
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <link rel="icon" type="image/svg xml" href="favicon.svg" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/tailwind.min.css" />
  <link href="https://fonts.googleapis.com/css?family=Work Sans:200,400&display=swap" rel="stylesheet">
  <title>Pure JS Shopping Cart</title>
</head>

<body>

  <body >

    <!--Nav-->
    <nav id="header" >
      <div >

        <label for="menu-toggle" >
          <svg  xmlns="http://www.w3.org/2000/svg" width="20" height="20"
            viewBox="0 0 20 20">
            <title>menu</title>
            <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
          </svg>
        </label>
        <input  type="checkbox" id="menu-toggle" />

        <div  id="menu">
          <nav>
            <ul >
              <li><a  href="#store">Shop</a>
              </li>
              <li><a  href="#about">About</a>
              </li>
            </ul>
          </nav>
        </div>

        <div >
          <a  href="#">
            <svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
              <path
                d="M5,22h14c1.103,0,2-0.897,2-2V9c0-0.553-0.447-1-1-1h-3V7c0-2.757-2.243-5-5-5S7,4.243,7,7v1H4C3.447,8,3,8.447,3,9v11 C3,21.103,3.897,22,5,22z M9,7c0-1.654,1.346-3,3-3s3,1.346,3,3v1H9V7z M5,10h2v2h2v-2h6v2h2v-2h2l0.002,10H5V10z" />
            </svg> PJSSC
          </a>
        </div>

        <div  id="nav-content">

          <a  href="#">
            <svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
              <circle fill="none" cx="12" cy="7" r="3" />
              <path
                d="M12 2C9.243 2 7 4.243 7 7s2.243 5 5 5 5-2.243 5-5S14.757 2 12 2zM12 10c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3S13.654 10 12 10zM21 21v-1c0-3.859-3.141-7-7-7h-4c-3.86 0-7 3.141-7 7v1h2v-1c0-2.757 2.243-5 5-5h4c2.757 0 5 2.243 5 5v1H21z" />
            </svg>
          </a>

          <button  id="cart">
            <svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24"
              viewBox="0 0 24 24">
              <path
                d="M21,7H7.462L5.91,3.586C5.748,3.229,5.392,3,5,3H2v2h2.356L9.09,15.414C9.252,15.771,9.608,16,10,16h8 c0.4,0,0.762-0.238,0.919-0.606l3-7c0.133-0.309,0.101-0.663-0.084-0.944C21.649,7.169,21.336,7,21,7z M17.341,14h-6.697L8.371,9 h11.112L17.341,14z" />
              <circle cx="10.5" cy="18.5" r="1.5" />
              <circle cx="17.5" cy="18.5" r="1.5" />
            </svg>
            <div id="cart-number-badge"
              ></div>
          </button>
        </div>
      </div>
    </nav>

    <div  style="max-width:1600px;">
      <div >
        <!--Slide 1-->
        <input  type="radio" id="carousel-1" name="carousel" aria-hidden="true" hidden="" checked="checked">
        <div  style="height:50vh;">
          <div  style="background-image: url('https://images.unsplash.com/photo-1422190441165-ec2956dc9ecc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80');">

            <div >
              <div >
                <p >Stripy Zig Zag Jigsaw Pillow and Duvet Set</p>
                <a  href="#">view product</a>
              </div>
            </div>

          </div>
        </div>
        <label for="carousel-3" >‹</label>
        <label for="carousel-2" >›</label>

        <!--Slide 2-->
        <input  type="radio" id="carousel-2" name="carousel" aria-hidden="true" hidden="">
        <div  style="height:50vh;">
          <div  style="background-image: url('https://images.unsplash.com/photo-1533090161767-e6ffed986c88?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjM0MTM2fQ&auto=format&fit=crop&w=1600&q=80');">

            <div >
              <div >
                <p >Real Bamboo Wall Clock</p>
                <a  href="#">view product</a>
              </div>
            </div>

          </div>
        </div>
        <label for="carousel-1" >‹</label>
        <label for="carousel-3" >›</label>

        <!--Slide 3-->
        <input  type="radio" id="carousel-3" name="carousel" aria-hidden="true" hidden="">
        <div  style="height:50vh;">
          <div  style="background-image: url('https://images.unsplash.com/photo-1519327232521-1ea2c736d34d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80');">

            <div >
              <div >
                <p >Brown and blue hardbound book</p>
                <a  href="#">view product</a>
              </div>
            </div>

          </div>
        </div>
        <label for="carousel-2" >‹</label>
        <label for="carousel-1" >›</label>

        <!-- Add additional indicators for each slide-->
        <ol >
          <li >
            <label for="carousel-1" >•</label>
          </li>
          <li >
            <label for="carousel-2" >•</label>
          </li>
          <li >
            <label for="carousel-3" >•</label>
          </li>
        </ol>

      </div>
    </div>

    <!--     
  Alternatively if you want to just have a single hero
  <section  style="max-width:1600px; height: 32rem; background-image: url('https://images.unsplash.com/photo-1422190441165-ec2956dc9ecc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1600&q=80');">
    <div >
      <div >
        <h1 >Stripy Zig Zag Jigsaw Pillow and Duvet Set</h1>
        <a  href="#">products</a>
      </div>
      </div>
  </section>
  -->

    <section id="store" >

      <div id="products-area" >

        <nav >
          <div >
            <a  href="#">
              Store
            </a>
            <div  id="store-nav-content">
              <a  href="#">
                <svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
                  <path d="M7 11H17V13H7zM4 7H20V9H4zM10 15H14V17H10z" />
                </svg>
              </a>
              <a  href="#">
                <svg  xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
                  <path
                    d="M10,18c1.846,0,3.543-0.635,4.897-1.688l4.396,4.396l1.414-1.414l-4.396-4.396C17.365,13.543,18,11.846,18,10 c0-4.411-3.589-8-8-8s-8,3.589-8,8S5.589,18,10,18z M10,4c3.309,0,6,2.691,6,6s-2.691,6-6,6s-6-2.691-6-6S6.691,4,10,4z" />
                </svg>
              </a>
            </div>
          </div>
        </nav>
      </div>

    </section>

    <section id="about" >

      <div >

        <a  href="#">
          About
        </a>

        <p >This template is inspired by the stunning nordic minamalist design - in particular:
          <br>
          <a  href="http://savoy.nordicmade.com/" target="_blank">Savoy Theme</a> created by <a  href="https://nordicmade.com/">https://nordicmade.com/</a>          and <a  href="https://www.metricdesign.no/" target="_blank">https://www.metricdesign.no/</a></p>

        <p >Lorem ipsum dolor sit amet, consectetur <a href="#">random link</a> adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vel risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Vitae aliquet nec
          ullamcorper sit. Nullam eget felis eget nunc lobortis mattis aliquam. In est ante in nibh mauris. Egestas congue quisque egestas diam in. Facilisi nullam vehicula ipsum a arcu. Nec nam aliquam sem et tortor consequat. Eget mi proin sed libero
          enim sed faucibus turpis in. Hac habitasse platea dictumst quisque. In aliquam sem fringilla ut. Gravida rutrum quisque non tellus orci ac auctor augue mauris. Accumsan lacus vel facilisis volutpat est velit egestas dui id. At tempor commodo
          ullamcorper a. Volutpat commodo sed egestas egestas fringilla. Vitae congue eu consequat ac.</p>

      </div>

    </section>

    <footer >
      <div >
        <div >
          <div >
            <div >
              <h3 >About</h3>
              <p >
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel mi ut felis tempus commodo nec id erat. Suspendisse consectetur dapibus velit ut lacinia.
              </p>
            </div>
          </div>
          <div >
            <div >
              <h3 >Social</h3>
              <ul >
                <li>
                  <a  href="#">Add social
                    links</a>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </div>
    </footer>

    // Cart Modal
    <div id="cart-modal" >
    </div>

  </body>
  <script type="module" src="./js/main.js"></script>
</body>

</html>

CodePudding user response:

This is a problem of WHERE the mouse click actually happens.

In your code, the addTheListeners() function attaches the event listener to the <svg> HTML elements, but each <svg> element has also a child <path> element.

When you click the button, the actual e.target of the click event will be either the <svg> or the <path>, depending on the exact position of the mouse cursor at the moment of the click.

Actually, the <path> line is very thin and difficult to click on, but it can definitely happen.

When the click hits the <path> rather than the <svg>, the e.target.parentElement.parentElement will NOT find the right <div> element with the key attribute but another HTML element (hence getAttribute('key') will be null).

To have a practical feedback, you can try to add a second console.log(e.target) to your listener. You will see that null clicked will be logged togheter with the <path> element; when the e.target is <svg> you will see the correct log (1 clicked, 2 clicked...).

In cases like this, you should definitely use event delegation to catch the click correctly (regardless if it's on the <svg> or on the <path>).

  • Related