原生js生成无限级树形菜单
设计思路:
要生成菜单的源数据往往是一个树形数据结构(若不是也可以转换成树形结构),(那我们一起写博客吧)因为源数据结构和目标菜单结构都为树形结构,所以其实我们要做的就是数据结构的转译,即将js树形数据转换为 ul, li 拼接成的树形菜单。在这里我们通过树的深度优先遍历方式来完成这次转义操作。
结构(转义)映射关系说明:
迭代树形数据时,树形结构数据的每个同层级别的每条数据转换成一个 LI标签包裹的菜单项(添加class为menu-item标识),当遇到childrens项时,当前项数据用 LI 标签包裹(添加class为menu标识),同时在该LI标签后面添加UL标签作为下一级菜单包裹项,迭代childrens作为上述UL标签的子菜单项,对childrens子元素的操作同上述描述的操作。
简单总结就是树的每条数据都转换为LI标签内容,每个childrens都转换为UL标签。
以下我们来完成对上述描述代码实现
1,js树形数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var menuList = [ 2 { 3 name: '音程比较', 4 childrens: [ 5 { 6 name: '比较纯音程', 7 }, 8 { 9 name: '比较不完全和协音程', 10 }, 11 { 12 name: '比较不协和音程', 13 childrens: [ 14 { 15 name: '大二度&小七度-上行', 16 intervalId: 112, 17 questionType: "bj", 18 }, 19 { 20 name: '大二度&小七度-下行', 21 intervalId: 113, 22 questionType: "bj", 23 }, 24 ], 25 }, 26 { 27 name: '比较跨两个八度的复合音程', 28 } 29 ] 30 }, 31 { 32 name: '音程辨认', 33 childrens: [ 34 { 35 name: '辨认小二度和大二度音程', 36 childrens: [ 37 { 38 name: '分辨小二度和大二度音程-上行', 39 intervalId: 1, 40 questionType: "br", 41 }, 42 { 43 name: '分辨小二度和大二度音程-下行', 44 intervalId: 2, 45 questionType: "br", 46 }, 47 { 48 name: '分辨小二度和大二度音程-和声', 49 intervalId: 3, 50 questionType: "br", 51 }, 52 { 53 name: '分辨小二度和大二度音程-上行下行', 54 intervalId: 4, 55 questionType: "br", 56 }, 57 { 58 name: '分辨小二度和大二度音程-上行和声', 59 intervalId: 5, 60 questionType: "br", 61 }, 62 { 63 name: '分辨小二度和大二度音程-下行和声', 64 intervalId: 6, 65 questionType: "br", 66 } 67 ] 68 }, 69 { 70 name: '辨认小三度和大三度的音程', 71 childrens: [ 72 { 73 name: "分辨小三度和大三度音程-上行", 74 intervalId: 7, 75 questionType: "br", 76 }, 77 { 78 name: "分辨小三度和大三度音程-下行", 79 intervalId: 8, 80 questionType: "br", 81 }, 82 { 83 name: "分辨小三度和大三度音程-和声", 84 intervalId: 9, 85 questionType: "br", 86 }, 87 { 88 name: "分辨小三度和大三度音程-上行下行", 89 intervalId: 10, 90 questionType: "br", 91 }, 92 { 93 name: "分辨小三度和大三度音程-上行和声", 94 intervalId: 11, 95 questionType: "br", 96 }, 97 { 98 name: "分辨小三度和大三度音程-下行和声", 99 intervalId: 12, 100 questionType: "br", 101 } 102 ] 103 }, 104 ] 105 }, 106 ]
2,迭代生成树形菜单
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // 生成dom tree 2 function generateDomTree(treeData, config = { 3 label: 'name', 4 childrens: 'childrens' 5 }) { 6 var label = config.label // 要显示的字段名 7 var childrensKey = config.childrens // 子节点字段名 8 var container = generateDomEle('ul') 9 /* 10 fragment 菜单容器 11 menuList 待遍历菜单 12 show 展开菜单 13 */ 14 function deep(fragment, menuList, show) { 15 for (const menu of menuList) { 16 var liDom = generateDomEle('li', { 17 innerText: menu[label] 18 }) 19 var childrens = menu[childrensKey] 20 // 子节点存在 21 if (childrens && childrens.length) { 22 fragment.appendChild(liDom) // 插入节点 23 var urlDom = generateDomEle('ul') // 生成下一级菜单 24 liDom.classList.add('menu') // 添加菜单标识 25 if (!show) { 26 // 关闭菜单 27 liDom.classList.add('close') 28 } 29 fragment.appendChild(urlDom) 30 31 deep(urlDom, childrens, show) // 迭代 32 } else { 33 liDom.classList.add('menu-item') // 添加菜单项标识 34 fragment.appendChild(liDom) 35 } 36 } 37 } 38 deep(container, treeData, false) 39 return container 40 }
3,给菜单添加点击展开关闭操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var container = generateDomTree(treeData) 2 container.addEventListener('click', function (e) { 3 var target = e.target 4 // 点中 li 5 if (target.tagName == 'LI') { 6 // 点中菜单 7 if (target.classList.contains('menu')) { 8 target.classList.toggle('close') // 如果关闭则展开,展开则关闭 9 } else { 10 //做你想做的 11 } 12 } 13 }, false)
4,相关代码解释说明
4-1,代码中用到的自定义方法代码片段
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // 生成dom节点 2 function generateDomEle(tagName, property) { 3 var property = property || {} 4 var ele = document.createElement(tagName) 5 for (var key in property) { 6 ele[key] = property[key] 7 } 8 return ele 9 }
4-2,效果图
4-3,生成的dom结构
@萍2樱释 ღ( ´・ᴗ・` )
打不死的小强