基于Web标准的UI组件 — 树状菜单(2)


Posted in Javascript onSeptember 18, 2006

从这篇开始,你需要拥有一些Javascript和DOM相关的知识才能顺利地学习下去。由于Javascript和DOM都不是三言两语可以说完的东西,如果你对它们还不熟悉,请先到这里学习一下再继续:Javascript在线教程(英文)、DOM在线教程(英文)。

getElementsByClassName()

为了从一大堆HTML代码中找出我们的树状菜单(也许有多个),我们先来实现一个通过className找DOM节点的方法:getElementsByClassName。这是对浏览器自有DOM方法的一个简单但实用的扩充。

此方法有两个参数:ele指出以哪个DOM节点为根节点寻找(也就是说只找ele的子节点),className指出符合条件的节点的class属性中必须包含怎样的className。它的返回值是一个数组,存放了所有符合条件的节点。

function getElementsByClassName(ele,className) {
 //获取所有子节点
 if(document.all){
  var children = ele.all;
 }else{
  var children = ele.getElementsByTagName('*');
 }
 //遍历子节点并检查className属性
 var elements = new Array();
 for (var i = 0; i < children.length; i++) {
  var child = children[i];
  var classNames = child.className.split(' ');
  for (var j = 0; j < classNames.length; j++) {
   if (classNames[j] == className) {
    elements[elements.length] = child;
    break;
   }
  }
 }
 return elements;
}

最前面的一个if-else语是为了兼容IE5(IE5不能运行document.getElementsByTagName('*'))。需要注意的是千万不要用浏览器检测的方法来写脚本,而应该直接使用将要用到的语句来测试是否可以执行,如果返回值为nullundefined,那再换一种方法。这样的脚本可以有更好的兼容性,也更健壮。

elements[elements.length] = child;,这句同样是为了兼容IE5才没有使用数组的push方法。如果你一定要使用push方法,那么可以在执行getElementsByClassName()之前先重载一遍push方法。代码如下:

Array.prototype.push = function(value){
 this[this.length] = value;
}

注:原本我希望getElementsByClassName也能像push方法一样写,比如HTMLElement.prototype.getElementsByClassName = ...。不过实际操作的时候发现在运行时HTMLElement这个对象并不是固定的,每种tag似乎都不一样,只能作罢。如果你有解决方案请告诉我,谢谢。

现在我们就可以方便地找出页面上所有的树状菜单了:

var trees	= getElementsByClassName(document,'TreeView');
for(var i=0;i<trees.length;i++){
 alert('我是第' + i + '个树状菜单');
 //在这里可以更细致地处理每一个树状菜单
}

最后把上面这几句加到window.onload事件中运行,以便文档一加载完就对树状菜单进行初始化。完整的代码请查看下面例子的源代码。

查看效果(例1)

区分树枝与树叶

在上一篇中我们说到树枝和树叶的区别就是这个节点有没有子节点,所以判断树枝和树叶的方法也可以从这个角度来考虑。一个比较直观的方法就是遍历整个树状菜单的DOM树(注意这里两个“树”的区别),看看每个节点是不是拥有子节点,如果有的话我们就给这个节点一个专门的class以示区分。我们这里用一种比较取巧的方法,就是判断各个节点的innerHTML中有没有出现字符串'<ul>'。如果有的话,那么很显然它拥有一个或多个子节点。

var trees	= getElementsByClassName(document,'TreeView');
for(var i=0;i<trees.length;i++){
 //先把所有的节点找出来
 var nodes = trees[i].getElementsByTagName('LI');
 //判断各个节点是否有子节点
 for(var j=0;j<nodes.length;j++){
  if(nodes[j].innerHTML.toLowerCase().indexOf('<ul>') > -1)
   nodes[j].className += 'Open';
 }
}

这里给每个树枝加了一个className:Open,因为我们现在还不能打开关闭树枝,所以只要是树枝那就是open的。当然后面我们会用到Close的:)。相应的修改一下CSS,给树枝一个带减号的图标,表示它是打开的:

.TreeView li.Open{
 background:transparent url(opened.gif) 12px 2px no-repeat;
}

查看效果(例2)

高亮选中项

接下来实现把当前选中的树枝(或树叶)高亮的功能。有两个时候需要高亮:菜单初始化的时候和点击某个菜单项的时候。

初始化的时候比较容易处理,直接给需要高亮的节点一个特殊的Class即可,比如“Selected”:

.TreeView li.Selected a:link,
.TreeView li.Selected a:visited,
.TreeView li.Selected a:hover,
.TreeView li.Selected a:active{
 background-color:#05F;
 color:#FFF;
 text-decoration:none;/*去除下划线*/
 cursor:default;/*让光标变为普通箭头,假装是不能点的^_^*/
 padding:0 2px;/*为了美观考虑,也可以不要这句*/
}

查看效果(例3)

这里有几点可能还需要补充说明一下:

  1. 选择器(Selector)的前面为什么要加上.TreeView,这不是冗余代码吗?
    在这个例子中确实是冗余代码,但在实际项目中,一个页面上可能会有各种不同的组件,比如还有一个菜单,被选中的菜单项也用.Selected来表示。这时就需要在选择符的前面先指出是什么组件的选中项以防冲突。当然还有其他的解决办法,比如这里的类不取名为Selectd,改为TreeSelected或者其他什么的,但是这样做人为的把命名方案复杂化了,我个人不推荐这样做。
  2. 选择器为什么分作四行来写?
    因为我们之前已经设置过a的样式,为了提高优先级重载旧的样式,所以需要指定a的四种伪状态(还有其他提高优先级的办法,关于优先级算法,在《网站重构》一书中有详细说明)。
  3. Selected为什么要用在li上,而不直接用在a上?
    这又是一个不太容易说明白的地方,因为很大程度上它是一种个人习惯,只是我个人觉得这样做更合适一些。事实上,写在li上或a上都是可以的,至少看上去(表现层的视角)不会有太大的区别,但是如果你从“表现层”中跳出来,站在“结构层”的视角来看,无论这个菜单的树结构还是DOM结构,一个节点都是由一个li来表达的,a只不过是这个节点内的更细节的部分。虽然我最终是希望给a指定一个特殊的样式(为什么不指定给li?你可以自己试一下),但从XHTML结构来说,这个class="Selected"还是写在li上更合适。(上帝保佑我说清楚了……)

下一篇讲什么?

这篇文章是我第一次加入Javascript内容,不是很清楚是说浅了还是说深了,请大家在右边留言告诉我你的想法。从下一篇开始,我们开始进入部署鼠标事件和响应鼠标事件方面的内容。也许从下下篇开始再加入一些Javascript面向对象编程的内容,待定待定……hehe^_^

Javascript 相关文章推荐
JS框架之vue.js(深入三:组件1)
Sep 29 Javascript
谈谈target=_new和_blank的不同之处
Oct 25 Javascript
使用Ajax与服务器(JSON)通信实例
Nov 04 Javascript
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
Dec 15 Javascript
详解Vue打包优化之code spliting
Apr 09 Javascript
jsonp跨域及实现百度首页联想功能的方法
Aug 30 Javascript
JavaScript设计模式之代理模式实例分析
Jan 16 Javascript
JS获取本地地址及天气的方法实例小结
May 10 Javascript
使用vue中的混入mixin优化表单验证插件问题
Jul 02 Javascript
vue-cli3 取消eslint校验代码的解决办法
Jan 16 Javascript
vue3如何优雅的实现移动端登录注册模块
Mar 29 Vue.js
Vue2项目中对百度地图的封装使用详解
Jun 16 Vue.js
JavaScript中的私有成员
Sep 18 #Javascript
javascript的事件描述
Sep 08 #Javascript
由浅到深了解JavaScript类
Sep 08 #Javascript
js常用函数 不错
Sep 08 #Javascript
Javascript 不能释放内存.
Sep 07 #Javascript
一些有关检查数据的JS代码
Sep 07 #Javascript
Mozilla中显示textarea中选择的文字
Sep 07 #Javascript
You might like
关于Laravel Route重定向的一个注意点
2017/01/16 PHP
PHP工厂模式的日常使用
2019/03/20 PHP
统计出现最多的字符次数的js代码
2010/12/03 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(二)人物行走的实现
2013/01/23 Javascript
js禁止页面刷新禁止用F5键刷新禁止右键的示例代码
2013/09/23 Javascript
JS获取屏幕,浏览器窗口大小,网页高度宽度(实现代码)
2013/12/17 Javascript
浏览器窗口大小变化时使用resize事件对框架不起作用的解决方法
2014/05/11 Javascript
jQuery实现多按钮单击变色
2014/11/27 Javascript
Nodejs实现多人同时在线移动鼠标的小游戏分享
2014/12/06 NodeJs
JavaScript将一个数组插入到另一个数组的方法
2015/03/19 Javascript
JavaScript中几种排序算法的简单实现
2015/07/29 Javascript
javascript常用正则表达式汇总
2015/07/31 Javascript
WEB前端开发框架Bootstrap3 VS Foundation5
2016/05/16 Javascript
JavaScript中的数组遍历forEach()与map()方法以及兼容写法介绍
2016/05/19 Javascript
JavaScript制作颜色反转小游戏
2016/09/25 Javascript
js获取元素的偏移量offset简单方法(必看)
2017/07/05 Javascript
JavaScript递归函数解“汉诺塔”算法代码解析
2018/07/05 Javascript
详解vue移动端项目的适配(以mint-ui为例)
2018/08/17 Javascript
vue表单验证你真的会了吗?vue表单验证(form)validate
2019/04/07 Javascript
微信小程序云开发实现数据添加、查询和分页
2019/05/17 Javascript
vue组件三大核心概念图文详解
2019/05/30 Javascript
js实现表单项的全选、反选及删除操作示例
2020/06/05 Javascript
[02:16]卖萌的僵尸 DOTA2神话信使飞僵小宝来袭
2014/03/24 DOTA
[25:59]Newbee vs TNC 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
python正则表达式中的括号匹配问题
2014/12/14 Python
利用Python爬虫给孩子起个好名字
2017/02/14 Python
将pandas.dataframe的数据写入到文件中的方法
2018/12/07 Python
python使用建议技巧分享(三)
2020/08/18 Python
.TTL是什么?有什么用处,通常那些工具会用到它?(ping? traceroute? ifconfig? netstat?)
2016/05/09 面试题
医院护士的求职信范文
2013/12/26 职场文书
学校后勤人员职责
2013/12/27 职场文书
2014幼儿园教育教学工作总结
2014/12/17 职场文书
廉政承诺书
2015/01/19 职场文书
实习报告范文之电话客服岗位
2019/07/26 职场文书
大学校园餐饮创业计划书
2019/08/07 职场文书
python 自动化偷懒的四个实用操作
2021/04/11 Python