动态生成菜单的Click事件部分失效,求解决方案
动态导航菜单点击事件部分失效的解决方法
问题原因
核心问题在于使用innerHTML +=拼接DOM时,会重新渲染容器内的所有元素,导致之前绑定的点击事件丢失。例如添加第二个父菜单时,menuContainer.innerHTML += html会将已存在的菜单HTML重新解析生成新DOM节点,之前给第一个菜单绑定的事件会因原节点被替换而失效。同时,这种拼接方式也会导致部分动态生成的子菜单事件绑定不可靠。
方案一:事件委托(推荐)
利用事件冒泡机制,仅在父容器上绑定一次事件,即可处理所有动态生成的菜单元素点击,完全避免逐个绑定事件的问题,代码更简洁高效。
修改后的完整代码
let menuData = [ { "id": 257, "nombreinterno": "TituloMenu02", "ruta": "", "titulo": "Segundo Titulo 257", "padre": "-1", "orden": "1" }, { "id": 256, "nombreinterno": "TituloMenu01", "ruta": "", "titulo": "Primer Titulo 256", "padre": "-1", "orden": "2" }, { "id": 258, "nombreinterno": "TituloMenu03", "ruta": "", "titulo": "Tercer Titulo 258", "padre": "-1", "orden": "3" }, { "id": 260, "nombreinterno": "TituloMenu05", "ruta": "", "titulo": "Quinto Titulo 260", "padre": "-1", "orden": "4" }, { "id": 259, "nombreinterno": "TituloMenu04", "ruta": "", "titulo": "Cuarto Titulo 259", "padre": "-1", "orden": "5" }, { "id": 262, "nombreinterno": "MedidaList2", "ruta": "", "titulo": "Segundo Sub-Titulo 256", "padre": "256", "orden": "25610" }, { "id": 261, "nombreinterno": "MedidaList", "ruta": "medidasListar.html", "titulo": "Primer Sub-Titulo 256", "padre": "256", "orden": "-1" }, { "id": 263, "nombreinterno": "Submenu01", "ruta": "", "titulo": "Primer Sub-Titulo 257", "padre": "257", "orden": "-1" }, { "id": 265, "nombreinterno": "Submenu01", "ruta": "", "titulo": "Primer Sub-Titulo 260", "padre": "260", "orden": "-1" }, { "id": 264, "nombreinterno": "Submenu02", "ruta": "", "titulo": "Segundo Sub-Titulo 257", "padre": "257", "orden": "-1" } ]; // 模板函数新增data-ruta属性,存储页面路由 const menuItemTemplate = (id, title, hasChildren, ruta) => ` <li class="nav-item"> <a id="${id}" class="nav-link collapsed" href="#" data-ruta="${ruta}"${hasChildren ? ` data-toggle="collapse" data-target="#collapse${id}" aria-expanded="true" aria-controls="collapse${id}"` : ''}> <i class="fas fa-fw fa-folder"></i> <span>${title}</span> </a> ${hasChildren ? `<div id="collapse${id}" class="collapse" aria-labelledby="heading${id}" data-parent="#accordionSidebar"> <div class="bg-white py-2 collapse-inner rounded"> <!-- Contenido de los submenús --> </div> </div>` : ''} </li>`; const subMenuItemTemplate = (id, title, ruta) => `<a id="${id}" class="collapse-item" href="#" data-ruta="${ruta}">${title}</a>`; function generateMenuHTML(menuData) { const menuContainer = document.getElementById('accordionSidebar'); let parentHTML = ''; // 先排序菜单数据 menuData.sort((a, b) => { if (a.orden === '-1' && b.orden === '-1') { return a.titulo.localeCompare(b.titulo); } else if (a.orden === '-1') { return 1; } else if (b.orden === '-1') { return -1; } else { return parseInt(a.orden) - parseInt(b.orden); } }); // 先构建所有父菜单HTML,一次性插入 const parentItems = menuData.filter(item => item.padre === '-1'); parentItems.forEach(parentItem => { const { nombreinterno, titulo, ruta } = parentItem; const hasChildren = menuData.some(item => item.padre == parentItem.id); parentHTML += menuItemTemplate(nombreinterno, titulo, hasChildren, ruta); }); menuContainer.innerHTML = parentHTML; // 再处理子菜单,逐个插入 const childItems = menuData.filter(item => item.padre !== '-1'); childItems.forEach(childItem => { const { nombreinterno, ruta, titulo, padre } = childItem; const parentItem = menuData.find(item => item.id == padre); const parentCollapseId = `collapse${parentItem.nombreinterno}`; const subMenuContainer = document.querySelector(`#${parentCollapseId} .collapse-inner`); subMenuContainer.innerHTML += subMenuItemTemplate(nombreinterno, titulo, ruta); }); } $(function() { generateMenuHTML(menuData); // 事件委托:父容器监听所有菜单元素的点击 $('#accordionSidebar').on('click', '.nav-link, .collapse-item', function(e) { e.preventDefault(); // 阻止默认跳转行为 const itemId = this.id; const targetPage = $(this).data('ruta'); console.log('点击菜单:', itemId); if (targetPage) { // $('#contenedor').load(targetPage); console.log('加载页面:', targetPage); } }); });
方案二:修复原有绑定逻辑
若不想使用事件委托,可通过appendChild替代innerHTML +=添加DOM元素,避免原节点被替换导致事件丢失。
核心修改点
- 将模板函数改为返回DOM元素,而非HTML字符串
- 使用
appendChild将新元素添加到容器中
// 重写模板函数,返回DOM元素 function createMenuItem(id, title, hasChildren, ruta) { const li = document.createElement('li'); li.className = 'nav-item'; const a = document.createElement('a'); a.id = id; a.className = 'nav-link collapsed'; a.href = '#'; a.dataset.ruta = ruta; if (hasChildren) { a.setAttribute('data-toggle', 'collapse'); a.setAttribute('data-target', `#collapse${id}`); } const icon = document.createElement('i'); icon.className = 'fas fa-fw fa-folder'; a.appendChild(icon); const span = document.createElement('span'); span.textContent = title; a.appendChild(span); li.appendChild(a); if (hasChildren) { const collapseDiv = document.createElement('div'); collapseDiv.id = `collapse${id}`; collapseDiv.className = 'collapse'; collapseDiv.setAttribute('data-parent', '#accordionSidebar'); const innerDiv = document.createElement('div'); innerDiv.className = 'bg-white py-2 collapse-inner rounded'; collapseDiv.appendChild(innerDiv); li.appendChild(collapseDiv); } return li; } // 生成菜单时使用appendChild添加元素 function generateMenuHTML(menuData) { const menuContainer = document.getElementById('accordionSidebar'); menuData.sort((a, b) => { // 排序逻辑不变 }); menuData.forEach(menuItem => { const { id, nombreinterno, ruta, titulo, padre } = menuItem; if (padre == '-1') { const hasChildren = menuData.some(item => item.padre == id); const menuElement = createMenuItem(nombreinterno, titulo, hasChildren, ruta); menuContainer.appendChild(menuElement); if (!hasChildren) { $(`#${nombreinterno}`).on('click', function(e) { e.preventDefault(); console.log('点击菜单:', nombreinterno); if (ruta) $('#contenedor').load(ruta); }); } } else { const parentItem = menuData.find(item => item.id == padre); const parentCollapse = document.getElementById(`collapse${parentItem.nombreinterno}`); const subMenuContainer = parentCollapse.querySelector('.collapse-inner'); const subItem = document.createElement('a'); subItem.id = nombreinterno; subItem.className = 'collapse-item'; subItem.href = '#'; subItem.dataset.ruta = ruta; subItem.textContent = titulo; subMenuContainer.appendChild(subItem); $(`#${nombreinterno}`).on('click', function(e) { e.preventDefault(); console.log('点击子菜单:', nombreinterno); if (ruta) $('#contenedor').load(ruta); }); } }); }
内容的提问来源于stack exchange,提问作者Jorge Cardenas
相关产品推荐
相关产品推荐

