基于JavaScript的树形菜单
1 前言
显然,树形菜单在网络应用中是很重要的。我也到过国外专业的菜单开发网站看过,但是感觉可维护性很差。由于工作中的一些需要,我认为自己开发一个维护性好,接口简单的树形菜单是十分有用的。本文将详细介绍作者开发的基于JavaScript的树形菜单,并附上源代码。
2 基本功能
2.1 跨平台性能好
2.2 可维护性好
2.3 接口简单
2.4 能够表达子菜单
2.5 菜单显示时间不大于1ms/菜单项,响应时间亦如此。
3 基本分析
3.1 要求可维护性好,需采用完全的面向对象方法。
3.2 树形菜单完全由“菜单项”组成,“菜单项”由表达其类型(有无子菜单项)的开关图和表达其内容的文本,以及表达其事件响应的行为构成。开关图负责响应菜单项是否展开的事件。
3.3 树形菜单以及每个菜单项都使用table元素表示
4 设计
4.1 整个程序整合为一个treedefiniton.js文件,其中包含两个类(js称之为对象)。一个类是Tree,另一个是TreeItem,二者是一对多的关系。当然测试程序是一个test.htm文件。
4.2 程序的设计文档用reference.xml表示,版本信息(包含作者)用versions.xml表示。所有xml的结构尚未固定,仅有用于显示的reference.xsl以及versions.xsl.这些程序的源代码限于篇幅,未公布于此。
4.3 TreeItem设计
4.4 Tree设计
4.5 一些补充
4.5.1对于TreeItem的public attributes,显然是不能都作为public的,这里这样处理的原因是我认为程序的功能尚未达到需要严格定义的程度。
4.5.2 根菜单项。树形菜单只有一个根菜单项,其他所有菜单项都是它的子菜单项。
4.5.3 菜单项的id。根菜单项的id仅含一个下划线,其余类推之。
,类似于csdn,是我自己画的。
5 性能
在菜单项达到48项时候,显示时间基本达到要求,响应时间好于要求。
6 总结
自己并不是专业做这个的,所以公布这个源代码,希望能够让大家做好这个东西。目前最新版本是1.12. 任何进一步改进该程序的程序员,我将在versions.xml中注明该程序员。原则上每次改进,版本号将增加0.01.限于个人能力,所有改进只能通过e-mail,且,所有改进都将在这篇文章中公布。我的email是mxliao@{域名已经过期}。暂时由我管理所有改进。如有朋友提供空间,我将感激不尽,并将适时采取其他程序管理方法。
由于本程序的设计者和实现者都是我一人,以及时间有限,所以这里设计和实现的衔接可能有不完备或者不一致的地方,请见谅。
7 测试程序源代码片断(test.htm)
var tree=new Tree("tree","Words Expert","");
tree.root.addChild((Add=new TreeItem("Add","")),
(Recite=new TreeItem("Recite","")),
(View=new TreeItem("View","")),
(Help=new TreeItem("Help","")),
(Config=new TreeItem("Config","")),
(File=new TreeItem("File","")),
(About=new TreeItem("About Words Expert","")));
Recite.addChild((seeChinese=new TreeItem("See Chinese","")),
(seeEnglish=new TreeItem("See English","")),
(lastOrder=new TreeItem("Last order","")));
seeChinese.addChild((reciteChineseAlpha=new TreeItem("Alpha","")),
(reciteChineseFamiliarity=new TreeItem("Familiarity","")),
(reciteChineseTime=new TreeItem("Time","")));
reciteChineseAlpha.addChild(
(new TreeItem("Alpha Order","")),
(new TreeItem("Reverse Alpha Order","")));
reciteChineseFamiliarity.addChild(
(new TreeItem("Familiarity","")),
(new TreeItem("Reverse Reverse Familiarity","")));
reciteChineseTime.addChild(
(new TreeItem("Time","")),
(new TreeItem("Reverse Time","")));
seeEnglish.addChild((reciteEnglishAlpha=new TreeItem("Alpha","")),
(reciteEnglishFamiliarity=new TreeItem("Familiarity","")),
(reciteEnglishTime=new TreeItem("Time","")));
reciteEnglishAlpha.addChild(
(new TreeItem("Alpha Order","")),
(new TreeItem("Reverse Alpha Order","")));
reciteEnglishFamiliarity.addChild(
(new TreeItem("Familiarity","")),
(new TreeItem("Reverse Reverse Familiarity","")));
reciteEnglishTime.addChild(
(new TreeItem("Time","")),
(new TreeItem("Reverse Time","")));
View.addChild(
(lastOrder=new TreeItem("Last Order","")),
(alpha=new TreeItem("Alpha","")),
(familiarity=new TreeItem("Familiarity","")),
(time=new TreeItem("Time","")));
alpha.addChild(
(new TreeItem("Alpha Order","")),
(new TreeItem("Reverse Alpha Order","")));
familiarity.addChild(
(new TreeItem("Familiarity Order","")),
(new TreeItem("Reverse Familiarity Order","")));
time.addChild(
(new TreeItem("Time Order","")),
(new TreeItem("Reverse Time Order","")));
File.addChild((new TreeItem("Save wordbook","")), (new TreeItem("Save config","")),(new TreeItem("Save All","")),(new TreeItem("Save All & Exit","")),
(new TreeItem("Exit Without Saving","")),
(new TreeItem("Backup wordbook","")));
tree.show();
8 TreeDefinition.js源代码(7K)
function TreeItem(text,action)
{
// public attributes
this.text=text;
this.action=action;
this.children=new Array();
this.tree=null;
this.type="";
this.id="";
// public methods
this.addChild=TreeItem_addChild;
}
function Tree(instanceName,text,action)
{
// constants
this.VERSION="1.12";
this.AUTHOR="liaomingxue";
this.FILE_IMAGE="images/file.gif";
this.FOLDER_IMAGE="images/fold.gif";
this.UNFOLD_IMAGE="images/unfold.gif";
this.VERSIONS="aboutTree/versions.xml";
this.REFERENCE="aboutTree/class.xml";
this.FILE_TYPE="FILE";
this.FOLDER_TYPE="FOLDER";
// priavate attributes
{域名已经过期}=instanceName;
this.html="";
this.elementClicked="";
// public attributes
this.indent=20;
this.withSelfItem=true;
this.itemColor="#000000";
this.itemBackground="#aaafff";
this.itemOverColor="#0000ff";
this.selectedItemBackground="#ccaacc";
this.root=new TreeItem(text,action);
this.root.id="TREEMENU"+"_0";
this.root.tree=this;
this.selfItem=new TreeItem("ABOUT TREE MENU","");
this.versionsItem=new TreeItem("versions","window.open(\""+this.VERSIONS+"\")");
this.referenceItem=new TreeItem("programmer reference","window.open(\""+this.REFERENCE+"\")");
// private methods
this.findItem=Tree_findItem;
this.findUnderlines=Tree_findUnderlines;
this.click=Tree_click;
// public methods
this.show=TREE_show;
this.getHtml=function getHtml(){return this.html;}
}
function Tree_findUnderlines(str)
{
var i,j;
for(i=0,j=0;i<str.length;i++) if(str.charAt(i)==='_') j++;
return j;
}
function TreeItem_addChild()
{
var child;
var i;
for(i=0;i<arguments.length;i++)
{
child=arguments[i];
this.children[this.children.length]=child;
this.type=this.tree.FOLDER_TYPE;
child.id=this.id+"_"+(this.children.length-1);
child.tree=this.tree;
child.type=this.tree.FILE_TYPE;
}
}
function TREE_show()
{
this.html+="<table cellspacing=0 cellpadding=0 rules='cols' style='border-width:2;' height='100%'>";
this.html+="<tr><td>";
this.html+="<table cellspacing=0 cellpadding=0>";
var n,i,top,stack;
this.root.addChild(this.selfItem);
this.selfItem.addChild(this.versionsItem,this.referenceItem);
stack=new Array(); //初始化目录栈
stack[0]=this.root; //加入目录栈的栈顶目录
while(stack.length>0)
{
/*弹出栈顶元素,并保存到top变量*/
top=stack[stack.length-1];
stack.length--;
n=this.findUnderlines(top.id);
/*将弹出的栈顶元素作为一个目录加入*/
this.html+="<tr style='";
if(n<3) this.html+=" display:;'>";
else this.html+=" display:none;'>";
this.html+="<td>";
this.html+=" <table style='table-layout:fixed;' cellspacing=0 cellpadding=0 id='"+top.id+"'";
this.html+=" <tr style='background:"+this.itemBackground+";'>";
for(i=0;i<n-1;i++)
this.html+=" <td width="+this.indent+"> </td>";
this.html+=" <td nowrap>";
this.html+=" <img style='cursor:hand;' onclick='"+{域名已经过期}+".click(this.parentNode.parentNode.parentNode.parentNode.id);' border=0 src='";
if(top.type==this.FOLDER_TYPE)
if(n==1)
this.html+=this.UNFOLD_IMAGE+"'>";
else this.html+=this.FOLDER_IMAGE+"'>";
else this.html+=this.FILE_IMAGE+"'>";
this.html+= "</img>";
this.html+=" <a onmouseover='this.style.color=\""+this.itemOverColor+"\";'";
this.html+=" onmouseout='this.style.color=\""+this.itemColor+"\";'";
this.html+=" style='color:"+this.itemColor+";cursor=hand;font-family:times new roman;font-size:medium;font-weight:bold;''"
this.html+=" onclick='"+top.action+"'>"+top.text;
this.html+=" </a>";
this.html+=" </td>";
this.html+=" </tr>";
this.html+=" </table>";
this.html+="</tr></td>";
/*假如弹出的栈顶目录有子目录,应该按逆序把所有子目录加到栈中*/
for(i=top.children.length-1;i>=0;i--) stack[stack.length]=top.children[i];
}
this.html+="</table>";
this.html+="</td></tr>";
this.html+="<tr><td height='100%' style='background:"+this.itemBackground+";'> </td></tr>";
this.html+="</table>";
document.write(this.html);
}
function Tree_click(itemID)
{
/* 首先找到被单击的html元素 */
var e=window.event.srcElement;
/* 找到该元素对应的目录对象 */
var item=this.findItem(itemID);
if(item!=null)
{
/* 如果该目录的类型为文件夹,那么,当它已经展开时,应折叠,否则相反 */
var i,s;
//如果被单击的是文件夹类型的目录
if(item.type==this.FOLDER_TYPE)
{
//如果该文件夹没有展开
if(e.src.indexOf(this.FOLDER_IMAGE)>=0)
{
//首先展开它,也就是更换它的图像
e.src=this.UNFOLD_IMAGE;
var stack=new Array(); //初始化目录栈
//先将所有一级子目录加到栈中
for(i=item.children.length-1;i>=0;i--) stack[stack.length]=item.children[i];
var top;
while(stack.length>0)
{
/*弹出一个目录*/
top=stack[stack.length-1];stack.length--;
document.all(top.id).parentNode.parentNode.style.display=""; //显示它
//如果这个被显示的目录处于展开状态,那么它的所有一级子目录应该加到栈中
if(top.type==this.FOLDER_TYPE &&
document.all(top.id).firstChild.firstChild.lastChild.firstChild.src.indexOf(this.UNFOLD_IMAGE)>=0)
for(i=top.children.length-1;i>=0;i--) stack[stack.length]=top.children[i];
}
}
/* 如果该文件夹已经展开 */
else
{
//首先折叠它
e.src=this.FOLDER_IMAGE;
//然后隐藏它的所有子目录,这里用到一个栈,来求出所有子目录
var stack=new Array(); //初始化目录栈
//先将所有一级子目录加到栈中
for(i=item.children.length-1;i>=0;i--)
stack[stack.length]=item.children[i];
var top;
while(stack.length>0)
{
/*弹出一个目录*/
top=stack[stack.length-1];stack.length--;
document.all(top.id).parentNode.parentNode.style.display="none"; //隐藏它
//再将这个被隐藏的目录的所有一级子目录加到栈中
for(i=top.children.length-1;i>=0;i--) stack[stack.length]=top.children[i];
}
}
}
/*如果以前有被单击的目录,那么以前被单击的元素要恢复到未单击的状态*/
if(this.elementClicked.length!=0)
document.all(this.elementClicked).firstChild.firstChild.lastChild.lastChild.style.background=this.itemBackground;
this.elementClicked=itemID;
document.all(this.elementClicked).firstChild.firstChild.lastChild.lastChild.style.background=this.selectedItemBackground;
}
}
function Tree_findItem(id)
{
/*从根目录开始找*/
var root=this.root;
var item=root;
if(item.id==id) return item;
var i;
for(i=0;i<root.children.length;i++)
{
item=root.children[i];
/*正好找到*/
if(item.id==id) return item;
/*
如果这个目录的层次大于或者等于要找的目录的层次,那么就不必再找了。
由于目录的层次与目录的id中的下划线个数相同,所以可以用这种方法
*/
if(this.findUnderlines(item.id)>this.findUnderlines(id)) return null;
/*如果找到它的一个直系祖先,那么就应该马上从它的直系祖先开始找下去*/
if(id.indexOf(item.id)==0 && id.charAt(item.id.length)=='_')
{
i=-1;root=item;
}
}
return null;
}
9 版本信息(自从1.05)
By liaomingxue