【问题标题】:How to add a class to dynamically created element如何将类添加到动态创建的元素
【发布时间】:2017-10-13 06:37:35
【问题描述】:

我想在 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");
  })
}


      
&lt;div id="container"&gt;&lt;/div&gt;

【问题讨论】:

  • 你为什么使用反引号而不是单引号?
  • @miqezjo - 错误表明您的 API_KEY 无效。 Swellar - 反引号用于创建模板文字 - 非常方便!
  • 我想用模板字符串创建元素
  • @BrettDeWoody 不知道,谢谢提供信息
  • 反引号在javascript的NODE中使用,如果你想声明一个字符串,你必须把它放在单引号或双引号中。

标签: javascript html


【解决方案1】:

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

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

想到两个解决方案:

  1. Promise.all 处理程序中使用setTimeout。这很可能会延迟添加事件侦听器足够长的时间,以便将模板添加到 DOM 中。我添加了一些 console.log 语句,它们将显示 Promise.all 日志行出现在最后呈现的日志行之前。
  2. req 方法返回您自己创建的承诺,而不是fetch 承诺。在将 cityTemplate 添加到 DOM 后,解决自创承诺。这样您就可以确定 Promise.all 在所有内容都在 DOM 中之前不会得到满足。

解决方案 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");
      })          
    });
}
&lt;div id="container"&gt;&lt;/div&gt;

【讨论】:

  • 感谢您的帮助!我可以在没有 setTimeout 的情况下做到吗?有没有使用 Promise 的替代方案?
  • @miqezjo 我已经更新了答案,所以它使用了承诺解决方案。
  • 哦,我明白了。我对承诺不太熟悉,但我会尝试。再次感谢
【解决方案2】:

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

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

但是 - setTimeout() 不是解决方案 - 下面的 sn-p 仅用于演示问题,向下滚动以获得正确的解决方案。

{
  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)

}
&lt;div id="container"&gt;&lt;/div&gt;

解决方案

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

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

{
  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");
    }
  })

}
&lt;div id="container"&gt;&lt;/div&gt;

替代解决方案

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

{
  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);
}
&lt;div id="container"&gt;&lt;/div&gt;

【讨论】:

  • 感谢您的解释。我还有一个问题。我应该以某种方式更改此按钮还是可以?我的意思是有没有办法摆脱检查类是否包含 header__flipper__aria?
  • 一些选项 - 一种是让&lt;span&gt; 占据&lt;button&gt; 的全部大小,因此只能单击&lt;span&gt;event.target.classList 将只需要检查&lt;span&gt;是否被点击。
  • @miqezjo - 我添加了一个可能更容易理解的替代解决方案。创建每个新元素时,它会将事件侦听器附加到每个按钮。
  • @miqezjo - 不要忘记接受解决您问题的解决方案;)
【解决方案3】:

您需要使用下面的代码 sn-p 来触发动态创建的元素的点击事件

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

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

【讨论】:

  • 任何非 jQuery 解决方案?
  • 我想坚持使用模板字符串并添加EventListener
猜你喜欢
  • 2016-12-29
  • 2014-10-17
  • 2017-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
  • 2018-01-21
  • 1970-01-01
相关资源
最近更新 更多