0

How can I run an event on dynamically added elements, because my click event doesn't work on new elements.

I found some answer in here but all about jQuery so I'm coding with vanilla javascript. So do you have any advice ?

  document.querySelectorAll('.galeri-cart').forEach(function (cart) {
      cart.addEventListener('click', function () {
       // something awesome happening in here    
    })
  });

creating element codes;

success: function () {
let imageData = JSON.parse(this.files[i].xhr.response);
let img = document.createElement('img');
 img.setAttribute('src', imageData.url);
 img.setAttribute('data-id',imageData.id);
 img.setAttribute('alt', imageData.alt);
 let subDiv = document.createElement('div');
 subDiv.className = "galeri-cart";
 subDiv.appendChild(img);
 let midDiv = document.createElement('div');
 midDiv.className = "col-md-4";
 midDiv.appendChild(subDiv);
 let div = document.querySelector('.row');
 div.insertBefore(midDiv, div.childNodes[0]);
 i++
}
  • please post the code where your dynamic elements are created – messerbill Feb 22 '18 at 16:53
  • You just add the handlers to the new elements as you add them to the DOM. –  Feb 22 '18 at 16:53
  • 3
    You need to use [event delegation](https://davidwalsh.name/event-delegate) for dynamically added elements. Or, if you are creating and adding elements to the DOM yourself, attach the listener to the element before you append it to the DOM – mhodges Feb 22 '18 at 16:53
  • [What is DOM Event delegation?](https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation) – Andreas Feb 22 '18 at 16:55
  • @AluanHaddad [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) has a built-in `.forEach()` iterator function – mhodges Feb 22 '18 at 16:55
  • @mhodges thanks. I always forget that because it doesn't have `map` or `filter`. Seriously, why do these APIs have to be so awful? – Aluan Haddad Feb 22 '18 at 16:58
  • Add the `click` handler on the `.row` element. In the handler check for the existence of the `galeri-cart` class. (event delegation) – Andreas Feb 22 '18 at 17:00
  • @AluanHaddad Great question.. haha. Although, with ES6, you can simply do `Array.from(document.querySelector("..."))` and then you have any array methods you want – mhodges Feb 22 '18 at 17:02
  • @mhodges thanks :). But it is like so anemic. They are already talking about adding it anyway. – Aluan Haddad Feb 22 '18 at 17:03
  • thanks for your all these pretty advices :) you are awesome :) –  Feb 22 '18 at 17:03

2 Answers2

2

Since you are adding the elements to the DOM manually, you can simply attach the event listener after you create the element and before you append it to the DOM, like so:

function galeriClickHandler () {
  // something awesome happening in here
}

success: function () {
let imageData = JSON.parse(this.files[i].xhr.response);
let img = document.createElement('img');
 img.setAttribute('src', imageData.url);
 img.setAttribute('data-id',imageData.id);
 img.setAttribute('alt', imageData.alt);
 let subDiv = document.createElement('div');
 subDiv.className = "galeri-cart";
 // add event listener here
 subDiv.addEventListener('click', galeriClickHandler);
 subDiv.appendChild(img);
 let midDiv = document.createElement('div');
 midDiv.className = "col-md-4";
 midDiv.appendChild(subDiv);
 let div = document.querySelector('.row');
 div.insertBefore(midDiv, div.childNodes[0]);
 i++
}

Alternatively, you can use event delegation on the static parent element and listen for clicks on specific children, like so:

function galeriClickHandler () {
  // something awesome happening in here
}

// Get the parent DIV, add click listener...
document.querySelector(".row").addEventListener("click",function(e) {
    // e.target was the clicked element
  if (e.target && e.target.matches(".galeri-cart")) {
    galeriClickHandler();
  }
});
mhodges
  • 9,573
  • 1
  • 22
  • 43
  • @Andreas Yes, event delegation is a perfectly viable solution as well. I will add that as an alternative solution in my answer. – mhodges Feb 22 '18 at 17:05
  • @Andreas: Why event delegation when you can bind directly? I agree about the single event handler though. It should be a named function that is shared. And the DOM creation is way too verbose. –  Feb 22 '18 at 17:05
  • @Andreas Updated with delegation – mhodges Feb 22 '18 at 17:11
  • I like this delegation way :) Thank for your help and advices mhodges and @Andreas –  Feb 22 '18 at 17:15
  • 1
    That's a fragile delegation. It only works if you click directly on the cart and not any sub elements. @cervantes Make sure you test thoroughly before using. –  Feb 22 '18 at 17:17
  • I know, so I changed this code a bit, not it's working like a charm :) –  Feb 22 '18 at 17:23
  • 1
    @doodlemeister Correct. It is a basic form of event delegation. Your answer using `event.target.closest` is a better implementation of the delegation. For the sake of not stealing your answer, I will leave mine alone. – mhodges Feb 22 '18 at 17:23
0

Since you didn't give any info on how you're adding the elements, the only direct code solution to give would be one that delegates the event handling to some container element.

var par = document.querySelector("#parent");
var button = document.querySelector("button");

button.addEventListener("click", function() {
  par.insertAdjacentHTML("beforeend", `<p class=galeri-cart>cart ${getCarts().length+1}</p>`)
});

par.addEventListener('click', function(event) {
    var cart = event.target.closest(".galeri-cart")
    if (cart) 
      cart.textContent += " clicked!"
});

function getCarts() {
  return par.querySelectorAll(".galeri-cart");
}
<button>Add Cart</button>

<div id=parent>
  <p class=galeri-cart>cart 1</p>
  <p class=galeri-cart>cart 2</p>
  <p class=galeri-cart>cart 3</p>
</div>

You added your code. You're already adding attributes and properties to the element, so you just do what you're already doing... bind the handler.

Here's a rewrite that makes your code much more concise and readable.

First, create the event handler.

function clickHandler() {
   // your handler code
}

Then create the element and bind the handler to it.

success: function() {
  let imageData = JSON.parse(this.files[i].xhr.response);
  let div = document.querySelector('.row');

  div.insertAdjacentHTML("afterbegin", `
<div class="col-md-4">
  <div class="galeri=cart">
    <img src="${imageData.url}" data-id="${imageData.id}" alt="${imageData.alt}">
  </div>
</div>`);

  div.firstElementChild
     .firstElementChild
     .addEventListener("click", clickHandler);

  i++;
}