用Javascript和XML创建可自由定义的菜单

用Javascript和XML创建可自由定义的菜单
by www.nowans.com

这个是我在看了网上很多菜单程序时,发现符合我的要求的很少,大部分都要经过很多的修改,而很多朋友对Javascript程序不是很熟悉,那么改写的时候会遇到很多困难,就萌发了编写一个不需要大量修改程序的HTML菜单。

我才用了XML来定义菜单数据,用Javascrip操作XML数据并且构造HTML菜单,使用时只需要输出一定格式的XML文件,再也不需要痛苦地修改程序了。

你可能会说:那你还写这个详细说明文件干什么?呵呵,这个文件首先是菜单程序的说明,另外也可以作为Javascript操作XML文件、Javascript控制DHTML文档对象(DOM)的基础学习资料。希望对大家有用!如果你仅仅是使用这个程序,那么你看完第一部分就可以了。

好了先介绍一下文档,这个文档分成三个部分,也就是针对了三个文件,menu.xml、TMenuXML.js、menu.js,在看这个文件的时候最好对照程序来看,便于理解。

第一部分 定义菜单的XML数据格式 对应程序文件menu.xml

大家知道XML能够灵活的定义数据格式,并且大部分的程序设计语言以及脚本语言都能支持XML格式数据,所以这里我们使用XML来定义菜单的结构。注意在实际应用中该XML文档可以由服务器端程序生成,客户端程序或者脚本程序读取。

这样定义XML菜单数据结构可以达到不改变程序,或者尽量少的改动程序,而是通过修改或者自动生成新的XML菜单数据,来达到更新菜单的目的。

结构中主要使用了一下几个节点名称:
Menu  在菜单条上以文本显示,鼠标滑过时会出现菜单面板
SubMenu  在菜单面板上以文本+图片的形式显示,同样的鼠标滑过时会出现菜单面板
MenuItem 在菜单面板上以文本+图片的形式显示的菜单,点击后按照预先设定完成相关任务

所有节点均包括以下属性:
text  要显示的文本内容
icon  图标
href  链接,同样可以用来调用javascript函数
targ  链接的目标

XML文件范例:
<?xml version='1.0' encoding='gb2312'?>
<Data text="root">
 <Menu text="文件" icon="images/right.gif" href="www.sina.com.cn" targ="_blank">
  <SubMenu text="新建" icon="images/leaf_f.gif" href="www.163.com" targ="_self">
   <MenuItem text="项目..." icon="images/home.gif" href="#" targ="_self"/>
   <MenuItem text="文件..." icon="images/note.gif" href="" targ="_self"/>
   <MenuItem text="空白解决方案..." icon="" href="" targ="_self"/>
  </SubMenu>
  <SubMenu text="打开" icon="images/folder.gif" href="" targ="_self">
   <MenuItem text="项目..." icon="" href="" targ="_self"/>
   <MenuItem text="Web上的项目..." icon="" href="" targ="_self"/>
   <MenuItem text="文件..." icon="" href="" targ="_self"/>
   <MenuItem text="来自Web的文件..." icon="" href="" targ="_self"/>
   <MenuItem text="转换..." icon="" href="" targ="_self"/>
  </SubMenu>
  <MenuItem text="关闭" icon="" href="" targ="_self"/>
  <MenuItem text="-" icon="" href="" targ="_self"/>
  <SubMenu text="添加项目" icon="" href="" targ="_self">
   <MenuItem text="新建项目..." icon="" href="" targ="_self"/>
   <MenuItem text="现有项目..." icon="" href="" targ="_self"/>
   <MenuItem text="Web上的现有项目..." icon="" href="" targ="_self"/>
  </SubMenu>
  <MenuItem text="-" icon="" href="" targ="_self"/>
  <MenuItem text="退出" icon="" href="" targ="_self"/>
 </Menu>
 <Menu text="编辑" icon="" href="" targ="_self">
  <MenuItem text="撤销" icon="" href="" targ="_self"/>
  <MenuItem text="重复" icon="" href="" targ="_self"/>
  <MenuItem text="-" icon="" href="" targ="_self"/>
  <MenuItem text="剪切" icon="" href="" targ="_self"/>
  <MenuItem text="复制" icon="" href="" targ="_self"/>
  <MenuItem text="粘贴" icon="" href="" targ="_self"/>
  <MenuItem text="-" icon="" href="" targ="_self"/>
  <SubMenu text="文本格式" icon="" href="" targ="_self">
   <SubMenu text="艺术格式" icon="" href="" targ="_self">
    <MenuItem text="艺术1" icon="" href="" targ="_self"/>
    <MenuItem text="艺术2" icon="" href="" targ="_self"/>
   </SubMenu>
  </SubMenu>
  <MenuItem text="选项..." icon="" href="" targ="_self"/>
 </Menu>
</Data>

定义菜单结构相对比较容易,下一部分介绍如何通过javascript来解析XML数据。

第二部分 Javascript读取XML数据 对应程序文件TMenuXML.js

定义了良好的XML格式数据之后就可以通过Javascrip读取了,这里我们定义了一个javascrip类,用来读取:
function TMenuXML(_filename){
 this.isShowTree = false;
 //需要装载的XML文件名称
 var XMLFileName=_filename;
 //javascrip XML 对象
 var XMLDoc = new ActiveXObject("MSXML2.DOMDocument.3.0");
 //到文件左边的空格数量
 var LeftMargin=0;

 //对象初始化函数
 this.Create=function(){}

 //取得节点的数据、文本
 //也可以取得节点属性的值
 this.getNode=function(doc, xpath) {}

 //递归读取下一个需要循环的菜单
 this.getNextNode=function(_Node){}

 //构造菜单
 this.BuildMenu=function(_Menus){}

 this.getLeftMarginStr=function(){}
}

大家可以看到,解析XML是通过微软的MSXML2.DOMDocument.3.0对象实现的,这个对象也是TMenuXML类里面的核心属性,我们称其为XMLDoc,另外还有一个重要的属性叫做XMLFileName,用来保存XML文件的路径,其他一些属性的作用在注释钟都有说明。

各种属性定义之后,我们来看一些重要的方法,首先是Create方法,这是初始化方法,其作用是初始化菜单以及装载菜单数据:
//对象初始化函数
this.Create=function(){
 if(XMLDoc.load(XMLFileName)){
  var Menus = XMLDoc.selectSingleNode("/Data/Menu");
  this.BuildMenu(Menus);
 }
}
首先判断菜单数据是否加载成功,若成功就调用XML对象的selectSingleNode("/Data/Menu")方法,selectSingleNode方法返回一个由参数(XPATH,和虚拟路径类似)指定的节点。接着调用BuildMenu()方法构造菜单。

BuildMenu()方法是XML数据解析中最重要的一个方法,在这个方法中还用到了我们定义的另一个Menu对象,关于这个对象我们会在下一部分讲解,还是先来看看BuildMenu()方法的代码:
if (_Menus!=null) {}
首先是对参数_Menus的判断,如果传递过来的菜单数据是空的,就直接退出。

接着是三个判断,分别构造菜单、子菜单、菜单项
//按照情况构造Menu菜单
If(_menus.Nodename=="Menu"){
}
//按照情况构造SubMenu菜单
if(_Menus.nodeName=="SubMenu"){
}
//按照情况构造MenuItem菜单
if(_Menus.hasChildNodes){
}
在构造菜单和子菜单的时候都是调用Menu对象的方法,需要注意的就是构造菜单项的时候,首先取得子菜单,若有子菜单则判断是否是菜单项,若是则创建菜单项,否则判断是否还有子菜单,进入递归调用。当该层次上的菜单构造结束之后进入下一层的递归。
//按照情况构造MenuItem菜单
if(_Menus.hasChildNodes){
 _MenuNode = _Menus.firstChild;
 while(_MenuNode != null){
  if(_MenuNode.nodeName=="MenuItem"){
   if(this.isShowTree){
    document.write(this.getLeftMarginStr() + "·" + this.getNode(_MenuNode, "@text") +"<br>");
   }
   _m.addMenuItem(_m.CurrentMenuBoard, this.getNode(_MenuNode, "@text"), this.getNode(_MenuNode, "@icon"), this.getNode(_MenuNode, "@href"), this.getNode(_MenuNode, "@targ"), false);
  }
  
  if(_MenuNode.hasChildNodes){
   this.BuildMenu(_MenuNode);
  }
  
  _MenuNode = this.getNextNode(_MenuNode);
 }
}
完整的代码请参考范例。

在上面的代码中还使用了一些内部方法,这里逐一介绍:

首先是取得节点的数据、文本,同时也可以取得节点属性的值。
this.getNode=function(doc, xpath) {
 var retval = "";
 var value = doc.selectSingleNode(xpath);
 if (value) retval = value.text;
 return retval;
}
getNode方法返回指定XML对象的XPATH上的数据、文本或者某个属性的数据。可以像下面的范例这样使用,范例表示取得当前节点上的@text属性的数据:
this.getNode(_MenuNode, "@text");
也可以从文档的根开始写:
this.getNode(_MenuNode, "/Data/Menu/@text");

getNextNode是递归读取下一个需要循环的菜单的函数,函数能够自动在各层菜单之间递归循环,即如果本层没有下级菜单时会自动向上以及继续寻找下级菜单。返回找到的菜单。
this.getNextNode=function(_Node){
 if(_Node == null) return null;
 
 if(_Node.nextSibling != null){
  return _Node.nextSibling;
 }else{
  LeftMargin -= 1;
  _m.CurrentMenuBoard = _m.getParentBoard(_m.CurrentMenuBoard);
  return this.getNextNode(_Node.parentNode);
 }
}

这一部分的代码比较复杂,除了熟练使用javascript之外对微软的XML对象也要比较熟悉。下一部分介绍构造菜单的对象。

第三部分 Javascript构造菜单 对应程序文件menu.js

这一部分主要介绍用javascript在HTML文件中生成菜单,我们采用DIV、TABLE等等一些HTML脚本对象,用DHTML脚本和CSS来控制这些对象的显示和隐藏。控制还要用到Javascript事件,主要包括onmouseover、onmouseout等等。

先看看我们都定义了那些属性:说明最后跟随的是属性的对象类型。
//菜单条<TR>
this.MenuBar;
//内容面板,所有的菜单内容都在这个上面<div>
this.ContentPane;
//当前的菜单,在菜单条上显示的菜单项目<TD>
this.CurrentMenu;
//当前菜单面板,显示菜单项目列表的容器<TABLE>
this.CurrentMenuBoard;
//当前的菜单项目<TR>
this.CurrentMenuItem;
this.cellIndex=0;

主要用到的方法:
//增加菜单条
//同时设置MenuBar和ContentPane
this.addMenuBar=function(){}

//在菜单条上增加菜单
//同时设置当前菜单
//参数:菜单的文本内容
this.addMenu=function(_mStr){}

//增加菜单项目列表容器
//同时设置当前的容器
//参数:容器的所有者(或者parentElement),是否为顶级容器
this.addMenuBoard=function(_owner, _isTop){}

//增加菜单项
//同时设置当前菜单项
//参数:菜单项目列表容器,菜单项文本,图表(16 x 16),链接位置,窗口目标,是否有下级菜单
this.addMenuItem=function(_board, _mStr, _mIcon, _mHref, _mTrg, _hasSubMenu){}

//显示_owner的菜单项目列表
function showMenuBoard(_owner){}

//隐藏_owner的菜单项目列表
function hiddenMenuBoard(_owner){}

//设置菜单项目的背景颜色
//参数:菜单项,是否选中
function setMenuItemBgColor(_mItem, _isSelect){}

//递归_board的父菜单项列表容器
this.getParentBoard=function(_board){}

下面我们开始介绍这些方法中的重点代码片断:
在addMenu方法中主要是增加菜单条,我们用DIV作为菜单的容器,通过下面的代码创建DIV并添加到BODY中:
this.ContentPane = document.createElement("div");
document.body.insertAdjacentElement("beforeEnd",this.ContentPane);
类似的代码在接下来的方法中会频繁的出现,就不再说明了。

在addMenu方法中主要是在table中插入一列,并且设置列上的相关事件。
下面的代码用来插入列:
_menu = this.MenuBar.insertCell();
下面的代码设置菜单上的事件:
_menu.onmouseover = function(){
 _menu.parentElement.cells(this.cellIndex).style.cssText = "background-color:#B5BED6;border:1px solid #08246B";
 showMenuBoard(_menu.parentElement.cells(this.cellIndex));
}

addMenuBoard方法是用来增加菜单面板的方法,说采用的防范也和上面类似,下面我们直接介绍这里的核心方法:
//递归_board的父菜单项列表容器
this.getParentBoard=function(_board){
 if(_board == null) return null;
 if(_board.parentElement == null) return null;
 
 if (_board.parentElement.tagName=="TABLE"){
  return _board.parentElement;
 }else{
  return this.getParentBoard(_board.parentElement);
 }
}

getParentBoard方法是搜索合适的DHTML容器对象的方法,在插入菜单项的时候若是三级以上的菜单,则有一个单独使用的菜单面板,插入了所有的菜单项之后,需要向上一级以插入后续的菜单,使用方法可以参考TMenuXML.js文件中的代码:

//递归读取下一个需要循环的菜单
this.getNextNode=function(_Node){
 if(_Node == null) return null;
 
 if(_Node.nextSibling != null){
  return _Node.nextSibling;
 }else{
  LeftMargin -= 1;
  _m.CurrentMenuBoard = _m.getParentBoard(_m.CurrentMenuBoard);
  return this.getNextNode(_Node.parentNode);
 }
}

好了,终于写好了,希望对大家有些帮助吧,这个里面涉及的内容比较多,耐心看到这里的也是不容易啊。说声谢谢!!

wynn
2005-6-13

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值