从这篇开始,你需要拥有一些Javascript和DOM相关的知识才能顺利地学习下去。由于Javascript和DOM都不是三言两语可以说完的东西,如果你对它们还不熟悉,请先到这里学习一下再继续:Javascript在线教程(英文)、DOM在线教程(英文)。
为了从一大堆HTML代码中找出我们的树状菜单(也许有多个),我们先来实现一个通过className
找DOM节点的方法:getElementsByClassName
。这是对浏览器自有DOM方法的一个简单但实用的扩充。
此方法有两个参数:ele
指出以哪个DOM节点为根节点寻找(也就是说只找ele
的子节点),className
指出符合条件的节点的class属性中必须包含怎样的className
。它的返回值是一个数组,存放了所有符合条件的节点。
<code>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; }</CODE>
最前面的一个if-else语是为了兼容IE5(IE5不能运行document.getElementsByTagName('*')
)。需要注意的是千万不要用浏览器检测的方法来写脚本,而应该直接使用将要用到的语句来测试是否可以执行,如果返回值为null
或undefined
,那再换一种方法。这样的脚本可以有更好的兼容性,也更健壮。
elements[elements.length] = child;
,这句同样是为了兼容IE5才没有使用数组的push
方法。如果你一定要使用push
方法,那么可以在执行getElementsByClassName()
之前先重载一遍push
方法。代码如下:
<CODE>Array.prototype.push = function(value){ this[this.length] = value; }</CODE>
注:原本我希望getElementsByClassName
也能像push
方法一样写,比如HTMLElement.prototype.getElementsByClassName = ...
。不过实际操作的时候发现在运行时HTMLElement
这个对象并不是固定的,每种tag似乎都不一样,只能作罢。如果你有解决方案请告诉我,谢谢。
现在我们就可以方便地找出页面上所有的树状菜单了:
<CODE>var trees = getElementsByClassName(document,'TreeView'); for(var i=0;i<trees.length;i++){ alert('我是第' + i + '个树状菜单'); //在这里可以更细致地处理每一个树状菜单 }</CODE>
最后把上面这几句加到window.onload
事件中运行,以便文档一加载完就对树状菜单进行初始化。完整的代码请查看下面例子的源代码。
在上一篇中我们说到树枝和树叶的区别就是这个节点有没有子节点,所以判断树枝和树叶的方法也可以从这个角度来考虑。一个比较直观的方法就是遍历整个树状菜单的DOM树(注意这里两个“树”的区别),看看每个节点是不是拥有子节点,如果有的话我们就给这个节点一个专门的class以示区分。我们这里用一种比较取巧的方法,就是判断各个节点的innerHTML中有没有出现字符串'<ul>'
。如果有的话,那么很显然它拥有一个或多个子节点。
<code>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'; } } </code>
这里给每个树枝加了一个className:Open
,因为我们现在还不能打开关闭树枝,所以只要是树枝那就是open的。当然后面我们会用到Close的:)。相应的修改一下CSS,给树枝一个带减号的图标,表示它是打开的:
<code>.TreeView li.Open{ background:transparent url(opened.gif) 12px 2px no-repeat; }</code>
接下来实现把当前选中的树枝(或树叶)高亮的功能。有两个时候需要高亮:菜单初始化的时候和点击某个菜单项的时候。
初始化的时候比较容易处理,直接给需要高亮的节点一个特殊的Class即可,比如“Selected
”:
<code>.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;/*为了美观考虑,也可以不要这句*/ }</code>
这里有几点可能还需要补充说明一下:
.TreeView
is indeed redundant code in this example, but in actual projects, there may be various components on a page, such as a menu, and the selected menu item is also represented by .Selected
Why is the selector written in four lines? a
(there are other ways to improve the priority, about the priority algorithm , described in detail in the book "a
Website Reconstruction").
Why should
Selected
instead of directly on li
? a
This is another place that is not easy to explain, because to a large extent it is a personal habit, but I personally feel that this is more appropriate. In fact, it’s okay to write on li
, at least it won’t look much different (from the perspective of the presentation layer), but if you jump out of the “presentation layer” and stand on From the perspective of the "structural layer", regardless of the tree structure or DOM structure of this menu, a node is expressed by a a
, and li
is just a more detailed part of this node. Although I ultimately hope to assign a special style to a
(why not assign it to a
? You can try it yourself), in terms of XHTML structure, it is more appropriate to write this li
on class="Selected"
. (God forbid I made it clear...) li