如何在HTA中正确遍历li标签构建JSON对象
问题
我正在开发一款可动态构建与编辑JSON文件的HTA工具,通过<li>和<ul>标签搭建UI界面,每个数据类型对应独立模板(从模板文件库调用),基础结构如下:
<li type='list'> <span>Name</span> <div> bunch of html that has hidden edit pop up windows and other controls </div> <li><ul> sub entries </ul></li> </li>
但遍历UI生成JSON时,尽管<li><ul></ul></li>嵌套在父<li>内,DOM遍历却将所有<li>视为同级元素。尝试过jQuery与DOM选择器组合,但总是同时选中父、子<li>。曾尝试添加条目时同步构建对象,但代码逻辑混乱,不利于后续维护。现咨询:
- 是列表元素的构建方式有误,还是DOM遍历方法不正确?
- 是否应该使用
<li>和<ul>以外的元素?
以下是简化示例代码:
<html> <head> <meta charset="UTF-8" http-equiv="x-ua-compatible" content="ie=edge"> <title>JSON Builder</title> </head> <body> <button style="width: 100%" onclick="getChildren()">Get</button> <div style="height: 100%; width: 100%;" > <ul id="options" style="height: 100%; border: 2px ridge; border-radius: 10px;"> <li id="entry_X" type="string" > <div style="width: 15em;" > <span id="entry_name_X" >Root 0</span> </div> <li> <ul> <li id="entry_1" type="string" > <div style="width: 15em;" > <span id="entry_name_1" >1</span> </div> <!-- This should be nested inside the li by my understanding but isnt so i was hiding them to get the look i wanted. --> <li style='display: none;'> <ul> </ul> </li> </li> <li id="entry_2" type="string" > <div style="width: 15em;" > <span id="entry_name_2" >2</span> </div> <li style='display: none;'> <ul> </ul> </li> </li> </ul> </li> </li> <li id="entry_6" type="string" > <div style="width: 15em;" > <span id="entry_name_X" >Root 1</span> </div> <li> <ul> <li id="entry_7" type="string" > <div style="width: 15em;" > <span id="entry_name_7" >7</span> </div> <li style='display: none;'> <ul> </ul> </li> </li> <li id="entry_8" type="string" > <div style="width: 15em;" > <span id="entry_name_8" >8</span> </div> <li style='display: none;'> <ul> </ul> </li> </li> </ul> </li> </li> </ul> </div> </body> <script> function getChildren() { // This returns a length of 6 items var l1 = document.querySelectorAll( 'ul>li' ); alert( "[ QALL ul>li length ] " + l1.length ); /* This returns a length of 2 items. the first item is the div in the first li and the 2nd is the li inside the first li. I expected this to return just the 1 li as there is only 1 li in the options ul but that is not how its working. */ var l1 = document.querySelector( '#options' ).children; alert( "[ QSEL options.children length ] " + l1.length ); //alert( l1[0].innerHTML ); //alert( l1[1].innerHTML ); var l1 = document.querySelectorAll( '#options ul>li' ); alert( "[ QSEL2 options.children length ] " + l1.length ); } </script> <script> resizeTo( 690,390 ); moveTo(screen.width/2-210,screen.height/2-250); </script> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> </html>
解答
核心问题:HTML结构不符合规范
你的列表嵌套写法存在错误:<li>的直接子节点不能是另一个<li>,必须先嵌套<ul>或<ol>这类列表容器,再在容器内放置子<li>。浏览器遇到不符合规范的嵌套时,会自动修正DOM结构,把错误嵌套的<li>提升为同级元素,这就是遍历所有<li>都被视为同级的根本原因。
比如你原本的错误结构:
<li type='list'> <span>Name</span> <div>...</div> <li><ul> sub entries </ul></li> <!-- 错误:li直接嵌套li --> </li>
浏览器会自动修正为:
<li type='list'> <span>Name</span> <div>...</div> </li> <li><ul> sub entries </ul></li> <!-- 被提升为同级元素 -->
正确的列表嵌套结构
要实现层级嵌套,需在父<li>内部先放置内容,再添加<ul>容器,然后在<ul>中放置子<li>:
<li type='list'> <span>Name</span> <div> bunch of html that has hidden edit pop up windows and other controls </div> <!-- 正确写法:父li内嵌套ul,再在ul里放子li --> <ul> <li>sub entries</li> </ul> </li>
对应你的示例代码,修正后的父条目结构应为:
<li id="entry_X" type="string" > <div style="width: 15em;" > <span id="entry_name_X" >Root 0</span> </div> <!-- 直接用ul作为父li的子元素,而非先套li --> <ul> <li id="entry_1" type="string" > <div style="width: 15em;" > <span id="entry_name_1" >1</span> </div> <!-- 子条目自己的子列表,直接嵌套ul --> <ul style='display: none;'></ul> </li> <li id="entry_2" type="string" > <div style="width: 15em;" > <span id="entry_name_2" >2</span> </div> <ul style='display: none;'></ul> </li> </ul> </li>
遍历方法修正
结构规范后,可通过精准选择器遍历层级:
- 选中顶级条目:
document.querySelectorAll('#options > li'),仅匹配#options的直接子<li> - 选中某父条目下的子条目:
parentLiElement.querySelectorAll('> ul > li'),或parentLiElement.querySelector('ul').children
是否需要替换<ul>/<li>?
不需要。<ul>/<li>本身就是为层级列表设计的语义化标签,只要结构规范,完全能满足需求。替换为div等元素反而会失去语义化优势,增加样式和逻辑复杂度。
额外建议
动态增删条目时,给每个条目对应的子列表添加明确类名(比如entry-children),能进一步降低遍历出错概率:
<li type="string" class="json-entry"> <div>...</div> <ul class="entry-children" style="display:none;"></ul> </li>
之后用document.querySelectorAll('.json-entry > .entry-children > li')即可精准选中子条目。
内容的提问来源于stack exchange,提问作者tokageKitayama

