如何将类添加到动态创建的元素中

时间:2017-10-13 06:37:36

标签: javascript html

我想在JavaScript中为动态创建的元素添加侦听器,但它似乎不起作用。它没有抛出任何错误,所以我不知道我必须从什么开始。你有什么想法吗?

{
  const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
  const container = document.getElementById("container");
  const flippers = document.getElementsByClassName("header__flipper");
  const cityTemplate = () => {
    const template = `<section class="weather">
      <button class="header__flipper"><span aria-hidden="true">&rarr;</span></button>
      <header class="header">
        <h1 class="header__heading">dfgdfgd
        </h1>
      </header>
    </section>`;
    return template;
  };
  const addListeners = (collection, ev, fn) => {
    for (let i = 0; i < collection.length; i++) {
      collection[i].addEventListener(ev, fn, false);
    }
  }
  const req = (id, key) => {
    const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
    fetch(url).then((res) => {
      res.json().then((data) => {
        container.innerHTML += cityTemplate(data);
      });
    })
  }
  req("6695624", API_KEY);
  req("6695624", API_KEY);
  req("6695624", API_KEY);
  addListeners(flippers, "click", () => {
    alert("test");
  })
}


      
<div id="container"></div>

3 个答案:

答案 0 :(得分:1)

核心问题是由于req()函数是异步的 - 意味着调用了req()函数,但它在将来的某个未知点结束。虽然每个req()正在等待完成脚本继续并且使用addListeners()选择器调用.header__flipper函数 - 但由于异步行为,.header__flipper元素不是已创建,因此不添加事件侦听器。

作为演示,我在addListeners()函数中添加了一个超时,因此它在被调用之前等待1秒。这使req()函数有时间完成,并允许事件侦听器正确附加。

但是 - setTimeout()不是解决方案 - 下面的代码段仅用于演示问题,请向下滚动以找到正确的解决方案。

{
  const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
  const container = document.getElementById("container");

  const cityTemplate = () => {
    const template = `<section class="weather">
      <button class="header__flipper"><span aria-hidden="true">&rarr;</span></button>
      <header class="header">
        <h1 class="header__heading">dfgdfgd
        </h1>
      </header>
    </section>`;
    return template;
  };
  const addListeners = (collection, ev, fn) => {
    for (let i = 0; i < Array.from(collection).length; i++) {

      collection[i].addEventListener(ev, fn, false);
    }
  }
  const req = (id, key) => {
    const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
    fetch(url).then((res) => {
      res.json().then((data) => {
        container.innerHTML += cityTemplate(data);
      });
    })
  }
  req("6695624", API_KEY);
  req("6695624", API_KEY);
  req("6695624", API_KEY);

  // For Demo Only
  // The req() function is asynchronous so the addListeners() function was attempting to attach to the elements before they were created
  window.setTimeout(function() {
    addListeners(document.getElementsByClassName("header__flipper"), "click", () => {
      alert("test");
    })
  }, 1000)

}
<div id="container"></div>

解决方案

解决方案是将事件侦听器附加到父选择器(如@Nishad建议的那样)。我们的想法是将click事件侦听器附加到父元素(如#container),并在侦听器回调函数中检查事件目标是否是新动态元素之一。

在您的情况下,在按钮中添加<span class="header__flipper__aria" aria-hidden="true">&rarr;</span>会使事情变得复杂,因为事件目标可能是<button><span>。这要求我们检查事件目标是否是这些元素中的任何一个。

{
  const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
  const container = document.getElementById("container");

  const cityTemplate = () => {
    const template = `<section class="weather">
      <button class="header__flipper"><span class="header__flipper__aria" aria-hidden="true">&rarr;</span></button>
      <header class="header">
        <h1 class="header__heading">dfgdfgd
        </h1>
      </header>
    </section>`;
    return template;
  };
  const addListeners = (collection, ev, fn) => {
    collection.addEventListener(ev, fn, false); 
  }
  const req = (id, key) => {
    const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
    fetch(url).then((res) => {
      res.json().then((data) => {
        container.innerHTML += cityTemplate(data);
      });
    })
  }
  req("6695624", API_KEY);
  req("6695624", API_KEY);
  req("6695624", API_KEY);

  addListeners(document.getElementById("container"), "click", (event) => {
    var classes = event.target.classList;
    if (classes.contains("header__flipper") || classes.contains("header__flipper__aria")) {
      alert("test");
    }
  })

}
<div id="container"></div>

替代解决方案

另一种方法是在创建动态元素时将事件侦听器附加到回调中每个动态元素内的按钮,如下所示:

{
  const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
  const container = document.getElementById("container");

  const cityTemplate = () => {
    const newEl = document.createElement("section");
    newEl.classList.add("weather");
    
    const template = `<button class="header__flipper"><span class="header__flipper__aria" aria-hidden="true">&rarr;</span></button>
      <header class="header">
        <h1 class="header__heading">dfgdfgd
        </h1>
      </header>`;
    newEl.innerHTML = template;
    return newEl;
  };
  
  const req = (id, key) => {
    const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
    fetch(url).then((res) => {
      res.json().then((data) => {
        const city = cityTemplate(data);
        city.querySelector("button").addEventListener("click", function(){
          alert("test");
        }, false);
        container.appendChild(city); 
      });
    })
  }
  req("6695624", API_KEY);
  req("6695624", API_KEY);
  req("6695624", API_KEY);
}
<div id="container"></div>

答案 1 :(得分:1)

问题是您在fetch请求完成之前添加了事件侦听器。在您致电addListeners时,触发器尚未在DOM中。

我修改了req方法以返回fetch的承诺。使用Promise.all代码将等待所有三次提取完成。这仍然没有完全解决问题,代码知道何时完成提取,但这与将req添加到DOM的cityTemplate方法不同。

我想到了两个解决方案:

  1. setTimeout处理程序中使用Promise.all。这很可能会延迟添加事件侦听器足够长的时间,以便在DOM中添加模板。我添加了一些console.log语句,这些语句将显示Promise.all日志行显示在最后一个渲染日志行之前。
  2. req方法返回您自己创建的承诺,而不是fetch承诺。将cityTemplate添加到DOM后,解决自己创建的承诺。通过这种方式,您可以确定Promise.all只有在DOM中的所有内容都无法实现。
  3. 解决方案1不是一个非常强大的解决方案,应该避免。解决方案2提供了您想要的控制。我的答案显示了解决方案2的基本设置,它没有进行任何类型的错误处理。

    {
      const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
      const container = document.getElementById("container");
      const flippers = document.getElementsByClassName("header__flipper");
      const cityTemplate = () => {
        const template = `<section class="weather">
          <button class="header__flipper"><span aria-hidden="true">&rarr;</span></button>
          <header class="header">
            <h1 class="header__heading">dfgdfgd
            </h1>
          </header>
        </section>`;
        return template;
      };
      const addListeners = (collection, ev, fn) => {
        for (let i = 0; i < collection.length; i++) {
          collection[i].addEventListener(ev, fn, false);
        }
      }
      const req = (id, key) => {
        console.log(`getting ${id}`);
        // Return a new promise, this promise will be fulfilled once the template
        // has been added with the retrieved data.
        return new Promise(resolve => {
          const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
          // Get the data
          fetch(url).then((res) => {
            res.json().then((data) => {
              // Add the template to the DOM
              container.innerHTML += cityTemplate(data);
              console.log(`rendered ${id}`);
              // Relove that promise that was returned by the method.
              resolve();
            });
          })    
        });
      }
      
      // Wait for all three promises to be done. These promises will be fulfilled after
      // the DOM has been updated.
      Promise.all([req("6695624", API_KEY), req("6695624", API_KEY), req("6695624", API_KEY)])
        .then(() => {
          console.log(`promise all done`);
          // There is no longer a need for a timeout, due to the change to the 
          // req method.
          addListeners(flippers, "click", () => {
            alert("test");
          })          
        });
    }
    <div id="container"></div>

答案 2 :(得分:0)

您需要使用以下代码段来触发动态创建元素的点击事件

$(document).on("click", ".className", function(){
    alert('this works')
});

对于JavaScript解决方案,您可以参考:adding onclick event to dynamically added button?