Home > Back-end >  addEventListener is not working on js generated elements (images)
addEventListener is not working on js generated elements (images)

Time:10-21

I'm placing a bunch of images on a grid in the center of the page and want to add a check for when each individual image is clicked. The images are created with js and added to the document, could it be an issue of them not being 'ready' yet or something?

function placePieces() {
  for (var i = 0; i < setup.length; i  ) {
    if ((setup[i] '' == "undefined")) {continue;}
    var element = document.createElement("img");
    element.src = "Images/"   pieces[Object.keys(pieces)[setup[i]]]   ".png";
    element.style.width = "10vh";
    element.style.height = "10vh";
    element.style.marginTop = (Math.floor(i/8) * 10)   "vh";
    element.style.marginLeft = "calc(((100vw - 80vh)/2)   "   (10 * (i%8) - 1)   "vh)";
    element.style.zIndex = 10;
    element.style.position = "absolute";
    element.id = i 1;
    document.body.innerHTML = "\n"   element.outerHTML   document.body.innerHTML;
    console.log(element.outerHTML)
    var nelement = document.getElementById(i 1);
    console.log(nelement)
    nelement.addEventListener("click",highlight);
  }
}
placePieces()

function highlight(n) {
  console.log(n)
  n = n.currentTarget.myParam;
  if (setup[n] == 0 || setup[n] == 6) {
    var moves = [];
    var m = n
    while (True) {
      if (!(Math.floor((m-9)/8)<=0)) {
        console.log("test")
      }
    }
  }
}

The second function is far from finished but it still does not return anything when it should.

CodePudding user response:

You don't have to grab the element again, you can add the listener directly

const element = document.createElement('div')
element.style.background = 'red'
element.style.width = '100px'
element.style.height = '100px'
element.addEventListener('click', () => console.log('click'))
document.body.appendChild(element)

This should work:

function placePieces() {
  for (var i = 0; i < setup.length; i  ) {
    if ((setup[i] '' == "undefined")) {continue;}
    var element = document.createElement("img");
    // ...
    element.addEventListener("click",highlight);
  }
}

CodePudding user response:

Event Handling

You have two choices:

Method Pros Cons
A Bind (or register) the "click" event to each <img>. Easier to write Any dynamically added <img> must be registered to "click" event.
B Bind the "click" event to an ancestor tag in which all <img>'s reside within. Write the event handler so that it only reacts when an <img> is clicked. This paradigm is called event delegation Only needs to register to the event to only one tag once and any dynamically added <img> do not need any binding Writing the event handler is harder.

When reviewing the example:

  1. Click some <img> in Area A and B. There should be a blue outline.
  2. Next, click both ADD buttons.
  3. Click some of the new <img>s. The new <img> in Area A do not work.
  4. Click the BIND button.
  5. Click any of the new <img> in Area A

Details are commented in example

// File names of all images
const images = ["Fqsw6v8/2s", "Qb6N0dG/3s", "qnGtC68/4s", "nDFmjJB/5s", "sPtNDGm/6s", "HpmggvF/7s", "dKfcwxQ/8s", "K7HbrWp/9s", "9ys8PXt/as", "HVK2zvw/bs", "7SgXHz2/cs", "StdB11X/ds", "cN9CnV5/es"];
// File names of the first 3 images which will be added to DOM at page load
const init = [images[0], images[1], images[2]];

/**
 * Generate one or more <img>s from a given array/
 * @param {Array} array - An array of file names
 * @param {String|Object} node - Either a selector string or a DOM object referenced to
 *        be the elemment to append the <img>s to.
 * @param {String} css - A className to be assigned to each <img> @default
 *        is "img"
 * @returns {array} - An array of <img>
 */
function genImg(array, node, css = "img") {
  let root = typeof node === "string" ? 
    document.querySelector(node) : node ? 
    node : document.body;
  let offset = root.childElementCount;
  const pix = array.flatMap((img, idx) => {
    if (idx >= offset) { 
      const image = new Image();
      const frame = document.createElement("figure");
      image.src = `https://i.ibb.co/${img}.png`;
      image.className = css;
      image.dataset.idx = offset   idx;
      root.append(frame.appendChild(image));
      return image;
    } 
    return [];
  });
  return pix;
}

const main = document.forms.gallery;
const io = main.elements;
const areas = Array.from(io.area);

const imgsA = genImg(init, areas[0]);
const imgsB = genImg(init, areas[1]);

imgsA.forEach(img => img.onclick = highlightA);

function highlightA(event) {
  this.classList.toggle("highlight");
}

areas[1].onclick = highlightB;

function highlightB(event) {
  const clk = event.target;
  if (clk.matches("img")) {
    clk.classList.toggle("highlight");
  }
}

const btns = Array.from(io.add);
btns.forEach(btn => btn.onclick = addImg);

function addImg(event) {
  const clk = event.target;
  if (clk.matches("button")) {
    let idx = btns.indexOf(clk);
    genImg(images, areas[idx]);
  }
}

const bind = io.bind;
bind.onclick = bindImg;

function bindImg(event) {
  Array.from(document.querySelectorAll("#A img"))
  .forEach(img => img.onclick = highlightA);
}
html {font: 300 4vmin/1.15 "Segoe UI"}
form {display: flex; flex-flow: column nowrap; justify-content: center; 
margin: 15px auto; padding: 0 10px;}
fieldset {margin: 0.5rem 0}
fieldset fieldset {display: flex; justify-content: space-evenly; align-items: center;}
legend {font-size: 1.25rem}
button {font: inherit; float: right; cursor: pointer;}
figure {display: inline-flex; justify-content: center; align-items: center; 
margin: 0.5rem 0.5rem 0; padding: 0.5rem;}
.img {display:inline-block; max-width: 5rem}
.highlight {outline: 5px groove cyan;}
<form id="gallery">
  <fieldset>
    <legend>Area A</legend>
    <fieldset id="A" name="area"></fieldset>
    <button name="add" type="button">ADD</button>
    <button name="bind" type="button">BIND</button>
  </fieldset>
  
  <hr>
  
  <fieldset>
    <legend>Area B</legend>
    <fieldset id="B" name="area"></fieldset>
    <button name="add" type="button">ADD</button>
  </fieldset>
</form>

  • Related