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

无法复现Bootstrap列表组JS示例效果,求原因及解决方法

问题概述

尝试复现Bootstrap 5.2列表组的JavaScript交互行为,做了两处修改:用JS动态生成HTML标签、采用嵌套列表结构。目前遇到两个问题:

  • 列表项的激活高亮功能失效,右侧内容无法切换
  • 需要实现父项点击时嵌套列表的折叠效果
问题原因
  1. 激活与内容切换失效

    • 属性拼写错误:data-bs-toggle被写成data-bs-toogle,Bootstrap无法识别错误属性,导致标签切换逻辑不触发
    • 嵌套结构错误:将子列表直接挂载到父<a>标签下,破坏了Bootstrap列表组的预期DOM结构;递归判断时错用了nodeTgt.children.length,应该判断当前节点node.children.length
    • 事件冲突:手动添加的click事件里调用e.stopPropagation(),阻止了Bootstrap内置事件的执行
  2. 折叠功能缺失

    • 未添加Bootstrap折叠相关的属性和DOM容器,也未实现折叠状态切换的逻辑
解决方法

一、修复激活高亮与内容切换

  • 修正属性拼写:将data-bs-toogle改为data-bs-toggle
  • 调整DOM结构:子列表不要直接放在父<a>标签内,而是在父项后添加独立的折叠容器
  • 修正递归判断:把if(nodeTgt.children.length > 0)改为if(node.children.length > 0),确保只有当前节点有子项时才生成嵌套列表
  • 移除事件干扰:删除click事件中的e.stopPropagation(),或直接简化事件逻辑

二、添加父项折叠功能

  • 给有子项的父列表项添加折叠触发属性:data-bs-toggle="collapse",并指定目标折叠容器的ID
  • 创建折叠容器:用<div>包裹子列表,添加collapse类,可加show默认展开,不加则默认折叠
  • 添加样式标识:给有子项的父项添加右侧箭头,提升交互体验
完整修改代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Nested Bootstrap List Group</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
    <style>
        /* 为有子项的父项添加箭头标识 */
        .has-children::after {
            content: " ▼";
            float: right;
            font-size: 0.8em;
        }
        .has-children.collapsed::after {
            content: " ▶";
        }
    </style>
</head>
<body>
    <div id="tree"></div>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>

    <script>
        function clearChildren(node){
            while(node.children.length > 0)
                node.removeChild(node.lastElementChild)
        }

        let idCounter = 0
        function createNode(name,meta={}){
          return {
              name,
              id: `node-${idCounter++}`, // 给ID加前缀,避免和其他元素冲突
              children:new Array(),
              meta
          }
        }

        function makeTreeTable(parentElement, rootNode){
            clearChildren(parentElement)
            parentElement.setAttribute('class', 'row')

            // 左侧树形列表容器
            const divTree = document.createElement('div')
            divTree.setAttribute('class','col-8')
            divTree.setAttribute('id', 'div-tree')
            parentElement.appendChild(divTree)

            // 右侧内容容器
            const divMeta = document.createElement('div')
            divMeta.setAttribute('class','col-4')
            divMeta.setAttribute('id', 'div-node')
            parentElement.appendChild(divMeta)

            const metaDisplay = document.createElement('div')
            metaDisplay.setAttribute('class','tab-content')
            metaDisplay.setAttribute('id','nav-tabContent')
            divMeta.appendChild(metaDisplay)

            maketree(divTree, rootNode, metaDisplay)
        }

        let activeMeta = 'tab-pane fade show active'
        function createMetaList(treeNode){
            const metaList = document.createElement('div')
            metaList.setAttribute('id', `${treeNode.id}-meta`)
            metaList.setAttribute('class',activeMeta)
            activeMeta = 'tab-pane fade'
            metaList.setAttribute('role','tabpanel')
            metaList.setAttribute('aria-labelledby',treeNode.id)

            for (const [key, value] of Object.entries(treeNode.meta)) {
                const metaItem = document.createElement('div')
                metaItem.innerHTML = `${key} : ${value}`
                metaList.appendChild(metaItem)
            }
            return metaList
        }

        let activeNode = 'list-group-item list-group-item-action active'
        function maketree(parentElement, nodeTgt, metaDisplay) {
            const ul = document.createElement("div");
            ul.setAttribute('class', 'list-group')
            ul.setAttribute('role','tablist')

            for(const node of nodeTgt.children){
                const listItem = document.createElement("a");
                listItem.innerHTML = node.name;
                listItem.setAttribute("id", node.id);
                listItem.setAttribute("class", activeNode)
                activeNode = 'list-group-item list-group-item-action'
                // 修正属性拼写
                listItem.setAttribute('data-bs-toggle','list')
                listItem.setAttribute('role','tab')
                listItem.setAttribute('href',`#${node.id}-meta`)
                listItem.setAttribute('aria-controls',`${node.id}-meta`)

                ul.appendChild(listItem);
                metaDisplay.appendChild(createMetaList(node))

                // 只有当前节点有子项时才创建嵌套列表
                if(node.children.length > 0){
                    // 添加父项标识类
                    listItem.classList.add('has-children')
                    // 创建折叠容器
                    const collapseDiv = document.createElement('div')
                    const collapseId = `collapse-${node.id}`
                    collapseDiv.setAttribute('id', collapseId)
                    collapseDiv.setAttribute('class', 'collapse show') // 默认展开,可改为collapse默认折叠
                    ul.appendChild(collapseDiv)
                    // 给父项添加折叠触发属性
                    listItem.setAttribute('data-bs-toggle','collapse')
                    listItem.setAttribute('data-bs-target',`#${collapseId}`)
                    // 递归创建子列表
                    maketree(collapseDiv, node, metaDisplay)
                }
            }
            parentElement.appendChild(ul)
        }

        function makeMeta(i){
            return{
                meta1:`this is ${i}`,
                meta2:`this is ${++i}`,
                meta3:`this is ${++i}`,
            }
        }

        // 构建树形数据
        const root = createNode('root', makeMeta(0))
        root.children.push(createNode('node1', makeMeta(1)))
        root.children.push(createNode('node2', makeMeta(2)))
        root.children.push(createNode('node3', makeMeta(3)))

        const node1 = root.children[0]
        const node2 = root.children[1]
        const node3 = root.children[2]

        node1.children.push(createNode('node11', makeMeta(11)))
        node1.children.push(createNode('node12', makeMeta(12)))
        
        node2.children.push(createNode('node21', makeMeta(21)))
        node2.children.push(createNode('node22', makeMeta(22)))

        node3.children.push(createNode('node31', makeMeta(31)))
        node3.children.push(createNode('node32', makeMeta(32)))

        const node11 = node1.children[0]
        const node12 = node1.children[1]
        const node31 = node3.children[0]
        const node32 = node3.children[1]

        node11.children.push(createNode('node111', makeMeta(111)))
        node11.children.push(createNode('node112', makeMeta(112)))
        node12.children.push(createNode('node121', makeMeta(121)))
        node12.children.push(createNode('node122', makeMeta(122)))

        node31.children.push(createNode('node311', makeMeta(111)))
        node31.children.push(createNode('node312', makeMeta(112)))
        node32.children.push(createNode('node321', makeMeta(121)))
        node32.children.push(createNode('node322', makeMeta(122)))

        makeTreeTable(document.querySelector('#tree'), root)
    </script>
</body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 14:15:37