简体   繁体   中英

How can I addEventListener to each innerHTML += in this for loop?

I'm trying to add an event listener on some repeating innerHTML . IE for every lot of HTML added by innerHTML, I'll also need to add a click event onto it.

To complicate things I'm also importing a data set from another JS file imported under the name data. As you can see in the code I need the data inside the event listener to be specific to the for loop iteration of the innerHTML so that when I fire the event listener I can see the correct, corresponding data.

This is my attempt:

JS:

import data from './data.js';
import img from './images.js';

export const lists = () => {
  const main = document.getElementById('main');

  main.innerHTML = `
  <div class="main-container">
    <div class="flex-between row border-bottom">
      <div class="flex new-list">
        <img class="create-img img-radius" src="${img.symbols[0]}" alt="Delete Bin">
        <h3>New List</h3>  
      </div>
      <div class="flex-between sections">
        <h3 class="text-width flex-c">Items:</h3>
        <h3 class="text-width flex-c">Reminders:</h3>
        <h3 class="text-width flex-end">Created:</h3>
      </div>
    </div>
    <div id="lists"></div>
  </div>
  `;

  const lists = document.getElementById('lists');

  for (let i = 0; i < data.lists.length; i++) {
    let obj = eval(data.lists[i]);
    let totalReminders = getTotalReminders(obj);

    lists.innerHTML += `
    <div class="flex-between row list">
      <h4>${obj.name}</h4>
      <div class="flex-between sections">
        <h4 class="number-width flex-c">${obj.items.length}</h4>
        <h4 class="number-width flex-c">${totalReminders}</h4>
        <div class="text-width flex-end">
          <h4 class="date">${obj.created}</h4>
          <img class="img-radius" src="${img.symbols[3]}" alt="Delete Bin">
        </div>
      </div>
    </div>
    `;

    const list = document.querySelector('.list');

    list.addEventListener('click', () => { // click event
      listNav.listNav(obj.name);
      listSidebarL.listSidebarL();
      listSidebarR.listSidebarR();
      listMain.listMain(obj.items);
    });
  };
};

const getTotalReminders = passed => { // find total reminders
  let total = 0;

  for (let i = 0; i < passed.items.length; i++) {
    total += passed.items[i].reminders;
  };

  return total;
};

At the moment ONLY the first iteration of innerHTML += has an event listener attached and when I click on it I see the data that should be corresponding the last iteration.

What am I doing wrong here?

You need to move the code that sets up the event handlers so that it is outside of your for loop and runs after that loop is finished. Then, instead of .querySelector() , which only returns the first matching element, you need .querySelectorAll() to return all matching elements. After that, you'll loop through all those elements and set up the handler.

You'll also need to change how your obj variable is declared so that it will be in scope outside of the for loop. Do this by declaring it just before the loop, but assigning it inside the loop:

let obj = null; // Now, obj is scoped so it can be accessed outside of the loop
for (let i = 0; i < data.lists.length; i++) {
  obj = eval(data.lists[i]);

And, put the following just after the for loop finishes:

// Get all the .list elements into an Array
const list = Array.prototype.slice.call(document.querySelectorAll('.list'));

// Loop over the array and assign an event handler to each array item:
list.forEach(function(item){
  item.addEventListener('click', () => { 
    listNav.listNav(obj.name);
    listSidebarL.listSidebarL();
    listSidebarR.listSidebarR();
    listMain.listMain(obj.items);
  });
});

With all this said, your approach here is really not very good. There is almost always another option than to use eval() for anything and using .innerHTML is usually something to avoid due to its security and performance implications. Using it in a loop is almost always a bad idea. You really should be using the DOM API to create new elements, configure them and inject them into the DOM. If you must use .innerHTML , then build up a string in your loop and after the loop, inject the string into the DOM via .innerHTML , just once.

One options is to look at event delegation/bubbling . The basic principle here is you add the event handler to a parent object, in this case <div id="lists"></div> . Then when the event is fired you query the target of that event to see if it matches your element.

Using this technique you don't have to re-bind event handlers when new items are added, particularly useful if the items are added by user interaction.

In your case it would look something like:

export const lists = () => {
  const main = document.getElementById('main');

  main.innerHTML = `
  <div class="main-container">
    <div class="flex-between row border-bottom">
      <div class="flex new-list">
        <img class="create-img img-radius" src="${img.symbols[0]}" alt="Delete Bin">
        <h3>New List</h3>  
      </div>
      <div class="flex-between sections">
        <h3 class="text-width flex-c">Items:</h3>
        <h3 class="text-width flex-c">Reminders:</h3>
        <h3 class="text-width flex-end">Created:</h3>
      </div>
    </div>
    <div id="lists"></div>
  </div>
  `;

  const lists = document.getElementById('lists');

   //Now that the parent element is added to the DOM
   //Add the event handler
   lists.addEventListener("click",function(e) {
   // e.target was the clicked element
   if (e.target && e.target.matches(".list")) {
       listNav.listNav(obj.name);
       listSidebarL.listSidebarL();
       listSidebarR.listSidebarR();
       listMain.listMain(obj.items);
   }

   //Add Items etc    
});

NOTE Scots comments re eval and innerHTML apply equally to this answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM