基于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设计 TreeItem
TreeItem
public methods
TreeItem()
addItem()
public attributes
text
action
children
tree
type
id
Description
An instance of this class is corresponding to an menu item on a tree menu.
public methods
TreeItem(text,action)
functions
CONSTRUCTOR
arguments
text
type:
string
description:
to be shown
action
type:
string
description:
The action will be performed when the item is clicked. It will be the onclick property of a html element. CAUTION:DON'T PUT CHAR ' TO IT.
addItem()
functions
Let the menuitems specified by the arguments be the subitems of this menuitem. Their orders are determined by the orders of the correponding arguments.
arguments
type:
TreeItem
description:
There is no limit on the number of arguments, but each argument should be an instance of Class MenuItem.
public attributes
1
text
string
set by the first argument of CONSTRUCTOR. Only for Class Tree.
2
action
string
set by the second argument of CONSTRUCTOR. Only for Class Tree.
3
children
Array
all subitems, set by addChild() method. Only for Class Tree.
4
tree
Tree
the tree to which the item belongs. Only for Class Tree.
5
type
string
should be one of Tree.FILE_TYPE and Tree.FOLDER_TYPE. Only for Class Tree.
6
id
string
id property of the element corresponding to this item. Only for Class Tree.
4.4 Tree设计 Tree
Tree
CONSTANTS
VERSION
AUTHOR
FILE_IMAGE
FOLDER_IMAGE
UNFOLD_IMAGE
VERSIONS
REFERENCE
FILE_TYPE
FOLDER_TYPE
public methods
Tree()
getHtml()
public attributes
indent
withSelfItem
itemColor
itemBackground
selectedItemBackground
root
selfItem
versionsItem
referenceItem
private attributes
name
html
elementClicked
private methods
findItem
findUnderlines
click
Description
An instance of this class is corresponding to a tree menu.
CONSTANTS
1
VERSION
string
"1.08"
version number
2
AUTHOR
string
"liaomingxue"
author name
3
FILE_IMAGE
string
"images/file.gif"
relative path of an image file for menu item with no subitems
4
FOLDER_IMAGE
string
"images/fold.gif"
relative path of an image file for menu item having visible subitems
5
UNFOLD_IMAGE
string
"images/unfold.gif"
relative path of an image file for menu item with its invisible subitems
6
VERSIONS
string
"aboutTree/versions.xml"
relative path of file used to show what progresses happened in all versions of this program.
7
REFERENCE
string
"aboutTree/class.xml"
relative path of file used to introduce the design of the classes of this program.
8
FILE_TYPE
string
"FILE"
an option for type atrribute of Class TreeItem
9
FOLDER_TYPE
string
"FOLDER"
an option for type atrribute of Class TreeItem
public methods
Tree(instanceName,text,action)
functions
CONSTRUCTOR
arguments
instanceName
type:
string
description:
the name of the instance 'this'
text
type:
string
description:
the text property of root item of Tree
action
type:
string
description:
the action property of root item of Tree
getHtml()
functions
to get html property of Tree
public attributes
1
indent
integer
for indenting subitem, greater than 0
2
withSelfItem
boolean
if true, show selfItem
3
itemColor
string
text color of item, eg. "#888888"
4
itemBackground
string
background color of item
5
selectedItemBackground
string
background color of item clicked
6
root
TreeItem
root item
7
selfItem
TreeItem
for demonstrating information about this program
8
versionsItem
TreeItem
selfItem's subitem to tell information about all versions
9
referenceItem
TreeItem
selfItem's subitem to show Classes designing
private attributes
1
name
string
set by argument 1
2
html
string
the content(in html format) to be shown
3
elementClicked
string
hold the id property of which item clicked
private methods
findItem(id)
arguments
id
type:
string
description:
the id of what item you want to find
findUnderlines(str)
arguments
str
type:
string
description:
return
type:
integer
description:
return how many underlines '_' occur in the argument
click(itemID)
functions
respond to item click event
arguments
itemID
type:
string
description:
id property of the clicked item
4.5 一些补充
4.5.1对于TreeItem的public attributes,显然是不能都作为public的,这里这样处理的原因是我认为程序的功能尚未达到需要严格定义的程度。
4.5.2 根菜单项。树形菜单只有一个根菜单项,其他所有菜单项都是它的子菜单项。
4.5.3 菜单项的id。根菜单项的id仅含一个下划线,其余类推之。
4.5.4 用到的图标资源为
,类似于csdn,是我自己画的。
5 性能
在菜单项达到48项时候,显示时间基本达到要求,响应时间好于要求。
6 总结
自己并不是专业做这个的,所以公布这个源代码,希望能够让大家做好这个东西。目前最新版本是1.12. 任何进一步改进该程序的程序员,我将在versions.xml中注明该程序员。原则上每次改进,版本号将增加0.01.限于个人能力,所有改进只能通过e-mail,且,所有改进都将在这篇文章中公布。我的email是mxliao@mails.gscas.ac.cn。暂时由我管理所有改进。如有朋友提供空间,我将感激不尽,并将适时采取其他程序管理方法。
由于本程序的设计者和实现者都是我一人,以及时间有限,所以这里设计和实现的衔接可能有不完备或者不一致的地方,请见谅。
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
this.name=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='"+this.name+".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) Versions of TreeMenuBy liaomingxue Version Number Time Description 1.05 2004.7.20 Eliminated some unnecessary attributes of Class Tree. Now I use xml to present version information and programer reference. The files are within dir aboutTree. The action property of the slefItem of Tree has changed to open a new window to show versions.xml which dwells in dir aboutTree. But Classes' UML presentations are unavailabe at present. Maybe the next version. 1.06 2004.7.20 Eliminated the attribute parent from Class TreeItem. UML diagram of class TreeItem, available. 1.07 2004.7.21 UML diagram beautified. File versions.xsl modified. 1.08 2004.7.21 Class TreeItem changed. No change on TreeItem's functionality.UML diagram of class TreeItem changed. 1.09 2004.7.21 simplified the Class Tree. The former vesions let an "IMG" html element be within an "A" element, now "A" removed. This results in an about 1/8 decrease in length of html property of Tree. 1.10 2004.7.21 Now there is no id property for Tree 1.11 2004.7.22 UML representation of Class Tree, available 1.12 2004.7.23 Redundant information removed to suit the release in CSDN. Some code polished. 10 测试程序演示图