You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用template literals渲染JSON数据后,如何访问动态元素并实现交互

问题

通过fetch获取JSON数据,用模板字符串渲染到页面后数据正常显示,但用querySelectorAll访问动态生成的元素时返回空NodeList。最终需求是:点击对应区域的按钮时,仅展示该区域的对应数据,并支持数据更新。

原代码如下:

// to fetch data from backend.
// const link = 'https://jsonplaceholder.typicode.com/users';
const link = './data copy.json';

// const container = document.getElementById('.item-container');
fetch(link)
    .then(res => {
        return res.json();
    })
    .then(data => {
console.log(data);

        data.forEach( gun => {
            const template = `
    <div class="ticket">
    <div class="id card"><p>${gun.gunId}</p>
  </div>
    <div class="location card">
      <p class="gun-id">${gun.location}</p> 
    </div>
    <div class="description card">
      <button class="view" id="${gun.gunId}" onclick="modalForm(),updateForm(this.id)">Details</button>
      <div  class="modal">
      <form class="modal-content" id ="form-data"onsubmit="event.preventDefault(); validateForm();">
        <div class="form-header">
          <h1 id="" class="gun-form-name"></h1>
          <span id="" class="close" onclick="closeModal()">×</span>
          <span id="" class="back-arrow fa-lg" type="button" onclick="goBack()" role="button" aria-label="Go Back Button"><i class="fa-solid fa-arrow-left"></i>
           </span></div>
        <section class="main-content">
          <div class="container">
            <div class="card">
                <div class="wrapper">
                  <h3 id="title-id">${gun.gunId}</h3>
                    <div class="img">
                        <img class="gun-image"src="gun image.PNG" alt="test">
                        <button class="overlay-btn btn-1" type="button">${gun.partId.lightPipeId}</button>
                        <button class="overlay-btn btn-2" type="button">${gun.partId.lightbugId}</button>
                        <button class="overlay-btn btn-3" type="button">${gun.partId.batteryId}</button>
                        <button class="overlay-btn btn-4" type="button">${gun.partId.triggerId}</button>
                        <button class="overlay-btn btn-5" type="button">${gun.partId.triggerPcb}</button>
                        <button class="overlay-btn btn-6" type="button">${gun.partId.triggerSpring}</button>
                        <button class="overlay-btn btn-7" type="button">${gun.partId.grayCable}</button>
                        <button class="overlay-btn btn-8" type="button">${gun.partId.powerCableid}</button>
                        <button class="overlay-btn btn-9" type="button">${gun.partId.stockPcb}</button>
        
                    </div>
                    
                    <div class="details">
                           <p class="info">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi ut iure molestiae dolores commodi amet reiciendis recusandae voluptatem porro cumque fuga excepturi eum necessitatibus, vel, assumenda alias voluptas debitis veniam.</p>
                           <p class="info">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi ut iure molestiae dolores commodi amet reiciendis recusandae voluptatem porro cumque fuga excepturi eum necessitatibus, vel, assumenda alias voluptas debitis veniam.</p> 
                           <p class="info">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi ut iure molestiae dolores commodi amet reiciendis recusandae voluptatem porro cumque fuga excepturi eum necessitatibus, vel, assumenda alias voluptas debitis veniam.</p> 
                           <p class="info">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi ut iure molestiae dolores commodi amet reiciendis recusandae voluptatem porro cumque fuga excepturi eum necessitatibus, vel, assumenda alias voluptas debitis veniam.</p> 
                           <p class="info">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi ut iure molestiae dolores commodi amet reiciendis recusandae voluptatem porro cumque fuga excepturi eum necessitatibus, vel, assumenda alias voluptas debitis veniam.</p>  
                    </div>
                    <div class="btn">
                        <button class="close-ticket"type="button">Close Ticket</button>
                    </div>
                </div>
            </div>
        </div>
        </section>
        
      </form>
    </div>
  
    </div>
  <div class="close-bt card">
    <button class="delete-button"id="${gun.gunId}">Close</button>
    </div>
    </div>
  </div>`  
            document.getElementById('item-container').insertAdjacentHTML('beforeend', template);
        });
        document.getElementById('open-tickets').innerHTML=data.length;
      })
    .catch(error=> console.log(error));

const modal = document.querySelectorAll(".modal");
console.log(modal);

最优实现方案

1. 解决动态元素获取为空的问题

fetch是异步操作,你把querySelectorAll(".modal")写在fetch外部时,DOM还未完成渲染,所以返回空NodeList。必须将获取动态元素的代码移到fetch的then回调内部,确保DOM渲染完成后执行:

fetch(link)
  .then(res => res.json())
  .then(data => {
    // 渲染DOM的代码...
    
    // 这里才能正确获取到动态生成的.modal元素
    const modal = document.querySelectorAll(".modal");
    console.log(modal);
  })

2. 用事件委托处理动态元素点击

模板内写onclick属性不利于维护,且动态元素直接绑定事件容易失效,推荐用事件委托:给静态父元素(如#item-container)绑定点击事件,通过判断目标元素类名处理不同按钮逻辑。

3. 优化模态框展示逻辑

当前每个ticket都渲染一个模态框会造成DOM冗余,推荐只保留一个全局模态框,点击按钮时根据gunId匹配对应数据,再填充模态框内容,提升性能。

4. 数据更新实现

更新数据时,先同步修改数据源(如全局存储的gunData数组),再局部更新对应DOM内容,避免全量重渲染。


修改后的完整代码示例

const link = './data copy.json';
const container = document.getElementById('item-container');
// 全局模态框(提前写在HTML中,避免动态生成多个)
const modal = document.getElementById('global-modal');
const modalContent = modal.querySelector('.modal-content');

// 事件委托处理所有按钮点击
container.addEventListener('click', (e) => {
  const target = e.target;
  // 处理Details按钮点击
  if (target.classList.contains('view')) {
    const gunId = target.id;
    const gun = window.gunData.find(item => item.gunId === gunId);
    if (gun) {
      fillModal(gun);
      modal.style.display = 'block';
    }
  }
  // 处理Close按钮点击
  else if (target.classList.contains('delete-button')) {
    const gunId = target.id;
    // 删除对应ticket
    const ticket = target.closest('.ticket');
    ticket.remove();
    // 更新数据源和计数
    window.gunData = window.gunData.filter(item => item.gunId !== gunId);
    document.getElementById('open-tickets').textContent = window.gunData.length;
  }
  // 处理模态框关闭按钮
  else if (target.classList.contains('close')) {
    modal.style.display = 'none';
  }
});

// 填充模态框数据
function fillModal(gun) {
  modalContent.querySelector('#title-id').textContent = gun.gunId;
  modalContent.querySelector('.gun-form-name').textContent = `Gun ${gun.gunId}`;
  // 更新零件按钮内容
  const overlayBtns = modalContent.querySelectorAll('.overlay-btn');
  overlayBtns[0].textContent = gun.partId.lightPipeId;
  overlayBtns[1].textContent = gun.partId.lightbugId;
  overlayBtns[2].textContent = gun.partId.batteryId;
  overlayBtns[3].textContent = gun.partId.triggerId;
  overlayBtns[4].textContent = gun.partId.triggerPcb;
  overlayBtns[5].textContent = gun.partId.triggerSpring;
  overlayBtns[6].textContent = gun.partId.grayCable;
  overlayBtns[7].textContent = gun.partId.powerCableid;
  overlayBtns[8].textContent = gun.partId.stockPcb;
}

// 数据更新函数(示例:修改某个零件ID)
function updateGunPart(gunId, partKey, newValue) {
  const gun = window.gunData.find(item => item.gunId === gunId);
  if (gun) {
    gun.partId[partKey] = newValue;
    // 如果模态框正展示该gun,同步更新模态框
    if (modal.style.display === 'block' && modalContent.querySelector('#title-id').textContent === gunId) {
      fillModal(gun);
    }
  }
}

// 初始化数据
fetch(link)
  .then(res => res.json())
  .then(data => {
    window.gunData = data; // 全局存储数据,方便后续操作
    data.forEach(gun => {
      const template = `
        <div class="ticket">
          <div class="id card"><p>${gun.gunId}</p></div>
          <div class="location card"><p class="gun-id">${gun.location}</p></div>
          <div class="description card">
            <button class="view" id="${gun.gunId}">Details</button>
          </div>
          <div class="close-bt card">
            <button class="delete-button" id="${gun.gunId}">Close</button>
          </div>
        </div>
      `;
      container.insertAdjacentHTML('beforeend', template);
    });
    document.getElementById('open-tickets').textContent = data.length;
  })
  .catch(error => console.log(error));

对应的HTML需提前添加全局模态框:

<div class="modal" id="global-modal">
  <form class="modal-content" id="form-data" onsubmit="event.preventDefault(); validateForm();">
    <div class="form-header">
      <h1 class="gun-form-name"></h1>
      <span class="close">×</span>
      <span class="back-arrow fa-lg" type="button" onclick="goBack()" role="button" aria-label="Go Back Button">
        <i class="fa-solid fa-arrow-left"></i>
      </span>
    </div>
    <section class="main-content">
      <div class="container">
        <div class="card">
          <div class="wrapper">
            <h3 id="title-id"></h3>
            <div class="img">
              <img class="gun-image" src="gun image.PNG" alt="test">
              <button class="overlay-btn btn-1" type="button"></button>
              <button class="overlay-btn btn-2" type="button"></button>
              <button class="overlay-btn btn-3" type="button"></button>
              <button class="overlay-btn btn-4" type="button"></button>
              <button class="overlay-btn btn-5" type="button"></button>
              <button class="overlay-btn btn-6" type="button"></button>
              <button class="overlay-btn btn-7" type="button"></button>
              <button class="overlay-btn btn-8" type="button"></button>
              <button class="overlay-btn btn-9" type="button"></button>
            </div>
            <div class="details">
              <p class="info">Lorem ipsum dolor sit amet...</p>
              <!-- 其他详情文本 -->
            </div>
            <div class="btn">
              <button class="close-ticket" type="button">Close Ticket</button>
            </div>
          </div>
        </div>
      </div>
    </section>
  </form>
</div>

内容的提问来源于stack exchange,提问作者Jhona

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 06:38:14