任务目的
- 熟练JavaScript
- 学习树这种数据结构的基本知识
任务描述
- 基于上几个任务,实现一个树形组件,样式自定义,不做限制
- 要求有以下功能:
- 节点的折叠与展开
- 允许增加节点与删除节点
- 按照内容进行节点查找,并且把找到的节点进行特殊样式呈现,如果找到的节点处于被父节点折叠隐藏的状态,则需要做对应的展开
整体思路
印象中最早遇见的树形组件是在xp系统中的文件系统,能够在窗口左侧显示文件夹的展开树。
这里,我们用一个列表来实现一个树形组件,并且增加一个查询按钮和文本输入框。
<ul id="menu">
<li><label>无可奉告</label>
<ul>
<li><label>闷声大发财</label></li>
<li><label>无可奉告</label></li>
<li><label>爱慕安格瑞</label></li>
</ul>
</li>
<li><label>听风是雨</label></li>
<li><label>天气晴朗</label>
<ul>
<li><label>跑得快</label>
<ul>
<li><label>弄个大新闻</label></li>
<li><label>硬点</label></li>
</ul>
</li>
<li><label>作为长者</label></li>
<li><label>人生经验</label></li>
</ul>
</li>
</ul>
<div id="searchContainer">
<input type="text" id="searchText">
<button id="btnSearch">搜索</button>
</div>
然后设置基本的样式
ul{
list-style: none;
}
li{
margin-top: 8px;
}
label{
margin: 0;
}
.glyphicon{
margin-right: 5px;
}
.hidden{
display: none;
}
#menu label span{
margin: 5px;
display: none;
}
#menu label:hover span{
display: inline;
}
.searched{
color: #fb3;
}
如果节点存在孩子节点,则在该节点前面添加一个箭头icon,当箭头朝下时展开该节点下的子节点。当箭头朝右时,隐藏该节点下的所有子节点。通过给箭头添加点击事件来控制这两个状态。
下面是给列表添加icon和icon点击事件的函数
//添加icon
function initIcon(){
var lists = document.getElementsByTagName("li");
for(var i=0,len=lists.length;i<len;i++){
if(lists[i].getElementsByTagName("ul").length){
var node = document.createElement("span");
node.className = "glyphicon glyphicon-chevron-down";
lists[i].insertBefore(node,lists[i].firstChild);
}
}
}
//给Icon添加点击事件
function clickEvent(){
var node = event.target;
if(node){
var nodeClass = node.classList;
if(nodeClass.contains("glyphicon-chevron-down")){
nodeClass.remove("glyphicon-chevron-down");
nodeClass.add("glyphicon-chevron-right");
node.parentNode.lastElementChild.classList.add("hidden");
}else if(nodeClass.contains("glyphicon-chevron-right")){
nodeClass.remove("glyphicon-chevron-right");
nodeClass.add("glyphicon-chevron-down");
node.parentNode.lastElementChild.classList.remove("hidden");
}
}
}
然后,我们给列表添加增加和删除按钮,当鼠标hover某个列表时,会显示这两个按钮,增加的节点会成为鼠标正在hover节点的子节点
//渲染添加和删除按钮
function render(label){
var addSpan = document.createElement("span");
addSpan.classList.add("addSpan");
addSpan.innerHTML = "+";
addHandler(addSpan,"click",addNode);
var delSpan = document.createElement("span");
delSpan.classList.add("delSpan");
delSpan.innerHTML = "x";
addHandler(delSpan,"click",delNode)
label.appendChild(addSpan);
label.appendChild(delSpan);
}
在删除节点的时候,需要进行判断,如果该节点存在兄弟节点,可以直接删除,如果该节点没有兄弟节点,则需要将父节点前面的icon一起删除
//返回指定元素的子元素节点的个数
function elementChildCount(node){
var num = 0;
var children = node.childNodes;
for(var i=0,len=children.length;i<len;i++){
if(children[i].nodeType == 1){
num ++;
}
}
return num;
}
//删除指定节点
function delNode(){
node = event.target.parentNode.parentNode;
// console.log(node);
//用了children不返回文本节点,可能有兼容问题,最好自己写一个返回
// if(node.parentNode.children.length>1){
// node.parentNode.removeChild(node);
// }
if(elementChildCount(node.parentNode)>1){
node.parentNode.removeChild(node);
}else{
node = node.parentNode;
node.parentNode.removeChild(node.parentNode.firstElementChild);
node.parentNode.removeChild(node);
}
}
添加节点也分两种情况,如果该节点下已经存在子节点,直接添加即可,如果不存在子节点,需要在添加子节点的同时,给父元素添加一个icon,不能忘记给icon添加点击事件以及新节点的添加和删除功能
//添加节点
function addNode(){
var node = event.target.parentNode.parentNode;
var childUl = node.getElementsByTagName("ul")[0];
if(childUl){
var name = prompt("请输入名称","");
if(name && name != ""){
var li = document.createElement("li");
var label = document.createElement("label");
label.innerHTML = name;
//添加删除和增加按钮
render(label);
li.appendChild(label);
childUl.appendChild(li);
}
}else{
var name = prompt("请输入名称","");
if(name && name != ""){
var ul = document.createElement("ul");
var li = document.createElement("li");
var label = document.createElement("label");
label.innerHTML = name;
render(label);
li.appendChild(label);
ul.appendChild(li)
node.appendChild(ul);
var span = document.createElement("span");
span.className = "glyphicon glyphicon-chevron-down"; //setAttribute
addHandler(span,"click",clickEvent);
node.insertBefore(span,node.firstChild);
}
}
}
最后,还需要给整个树添加查询功能。如果查到该节点,并且该节点的长辈节点有折叠的状态,则需要将其展开。
//查询node函数
function searchNode(){
var text = document.getElementById('searchText').value;
var menu = document.getElementById('menu');
if(text){
var label = document.getElementsByTagName('label');
for(var i=0,len=label.length;i<len;i++){
if(text == label[i].childNodes[0].nodeValue){
//设置特殊样式
label[i].setAttribute("class","searched");
//如果父节点折叠则展开
var ul = label[i].parentNode.parentNode;
while(ul != menu){
// console.log(ul);
if(ul.classList.contains("hidden")){
ul.parentNode.firstElementChild.classList.remove("glyphicon-chevron-right");
ul.parentNode.firstElementChild.classList.add("glyphicon-chevron-down");
ul.classList.remove("hidden");
}
ul = ul.parentNode.parentNode;
}
}
}
}
}