基于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 相关文章推荐
兼容ie和firefox js关闭代码
Dec 11 Javascript
利用CSS、JavaScript及Ajax实现高效的图片预加载
Oct 16 Javascript
javascript实现控制div颜色
Jul 07 Javascript
jQuery点击输入框显示验证码图片
May 19 Javascript
JavaScript中style.left与offsetLeft的使用及区别详解
Jun 08 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
Jul 04 Javascript
js获取浏览器和屏幕的各种宽度高度
Feb 22 Javascript
vue自定义全局组件(自定义插件)的用法
Jan 30 Javascript
使用express+multer实现node中的图片上传功能
Feb 02 Javascript
es6数值的扩展方法
Mar 11 Javascript
js函数和this用法实例分析
Mar 13 Javascript
Vue与React的区别和优势对比
Dec 18 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
Linux下 php5 MySQL5 Apache2 phpMyAdmin ZendOptimizer安装与配置[图文]
2008/11/18 PHP
PHP strncasecmp字符串比较的小技巧
2011/01/04 PHP
php之CodeIgniter学习笔记
2013/06/17 PHP
php实现文件下载(支持中文文名)
2013/12/04 PHP
php显示指定目录下子目录的方法
2015/03/20 PHP
深入浅析php中sprintf与printf函数的用法及区别
2016/01/08 PHP
laravel 5.1下php artisan migrate的使用注意事项总结
2017/06/07 PHP
PhpStorm2020.1 安装 debug - Postman 调用的详细教程
2020/08/17 PHP
学习YUI.Ext 第六天--关于树TreePanel(Part 1)
2007/03/10 Javascript
设置下载不需要倒计时cookie(倒计时代码)
2008/11/19 Javascript
响应鼠标变换表格背景或者颜色的代码
2009/03/30 Javascript
jQuery之选项卡的简单实现
2014/02/28 Javascript
推荐 21 款优秀的高性能 Node.js 开发框架
2014/08/18 Javascript
JavaScript编程中window的location与history对象详解
2015/10/26 Javascript
JavaScript中创建对象的模式汇总
2016/04/19 Javascript
只要1K 纯JS脚本送你一朵3D红色玫瑰
2016/08/09 Javascript
完美解决js传递参数中加号和&amp;号自动改变的方法
2016/10/11 Javascript
JS实现将二维数组转为json格式字符串操作示例
2018/07/12 Javascript
实例介绍JavaScript中多种组合继承
2019/01/20 Javascript
如何使用pm2快速将项目部署到远程服务器
2019/03/12 Javascript
Python求解平方根的方法
2015/03/11 Python
Face++ API实现手势识别系统设计
2018/11/21 Python
python实现图像检索的三种(直方图/OpenCV/哈希法)
2019/08/08 Python
深入浅析python变量加逗号,的含义
2020/02/22 Python
pyinstaller打包单文件时--uac-admin选项不起作用怎么办
2020/04/15 Python
使用CSS3制作一个简单的Chrome模拟器
2015/07/15 HTML / CSS
预订全球最佳旅行体验:Viator
2018/03/30 全球购物
西班牙床垫网上商店:Colchones.es
2018/05/06 全球购物
可持续未来的时尚基础:Alternative Apparel
2019/05/06 全球购物
荷兰音乐会和音乐剧门票订购网站:Topticketshop
2019/08/27 全球购物
车间班组长岗位职责
2013/11/13 职场文书
两则小学生的自我评价分享
2013/11/14 职场文书
安全协议书范本
2014/04/21 职场文书
解除财产保全担保书
2014/05/20 职场文书
2015年安全生产月活动总结
2015/03/26 职场文书
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
2022/02/12 Redis