使用XML/HTC/DHTML模拟标准Windows菜单

 


随着internet的发展,XML作为一种跨平台的通用结构化数据描述语言越来越得到人们的重视,并已经得到了广泛应用,如MicroMedia公司出品的Dreamweaver、Flash以及游戏抢滩登录等软件都利用了XML文件作为数据存储方式,而且Microsoft.NET也是架构在XML上面的。目前出现的取代HTML语言的下一代网页制作语言XHTML(可扩展超文本标记语言),
就是建立在XML基础上,因此掌握XML技术是未来网页制作者必备技能。
本文通过一个模拟标准Windows菜单程序来介绍有关XML技术应用。
一、 程序原理
考虑到层叠菜单复杂性,为使程序简化,只模拟到二级子菜单。下面我们来看看标准windows菜单过程:鼠标移入菜单时突出显示,移出时回复原状,点击则菜单凹陷显示并打开子菜单,鼠标点击子菜单则完成相应功能。
当子菜单打开后移动鼠标,则移入菜单凹陷显示并打开相应子菜单,移出菜单则回复原状并隐藏相应子菜单,若鼠标并不是移动到另外菜单而是在页面其它地方,则移出菜单仍凹陷显示,相应子菜单也不隐藏,只有当在非菜单处点击时才恢复原状并隐藏子菜单。标准Windows菜单一个重要特点就是子菜单打开后,
在鼠标移出菜单时并不隐藏或者在文档中非菜单处任意单击时才隐藏。目前很多网页菜单程序都做不到这点,一般都是移入菜单时打开子菜单,移出菜单时隐藏子菜单。采用IE5.0中新提供的鼠标捕获技术就可以完全解决这个问题。所谓鼠标捕获,就是与鼠标相关的所有事件都由设定鼠标捕获的对象进行处理,而无论这些事件是否由该对象触发。
由于xml文档非常适合描述结构化数据,故我们使用xml文档存储菜单及一级子菜单的有关信息。
为便于在其它页面使用模拟标准Windows菜单,我们并不使用代码功能重用性能比较差的外部脚本包含文件方法,而是采用IE 5.0新引入的DHTML行为(Behavior)技术将菜单功能代码封装在一个HTML 行为组件(HTC)内。 HTML行为组件封装了页面上特定的功能或动作,当把某个行为组件附加到页面上的标准HTML元素(对象)时,将增强该元素(对象)的默认功能,使得该元素(对象)含有行为的属性、方法或事件,这使代码重用变得十分容易,并简化了页面的HTML代码,提高了页面的可管理性。
因篇幅所限,有关xml文档及HTML行为组件的更多细节请参阅有关资料,本文不予详述。
二、程序实现
1.主页面(index.htm)
我们首先在主页面中定义充当菜单容器的Div元素,并使用CSS中的behavior属性附加行为组件(Menu.htc),这样就可以使用元素扩充的属性xmlsrc来指定存储菜单信息的数据文件(Menu.xml)以及使用onMenuClick来捕获组件触发的新事件并调用相应事件处理函数。
此外为简化程序,在主页面中定义了突出(Up_Menu)、凹陷(Dn_Menu)、原状(Menu)三种菜单外观样式,在实际应用时应在组件中定义菜单显示外观,这样更符合组件开发原则。
2.菜单组件(Menu.htc)
模拟标准Windows菜单的功能主要由菜单组件提供。菜单由以下Html元素组成:充当菜单容器的Div元素(在主页面定义)、用于一级菜单项的span元素(初始化菜单时创建)、包含二级菜单项的Div元素(单击一级菜单时建立)、二级菜单项的Div元素(单击一级菜单时建立)。在该组件中定义了用于指定菜单数据文件的xmlsrc属性、点击二级菜单时触发的onMenuClick事件。
当主页面解析完毕后触发ondocumentready事件,组件捕获这个事件并调用事件处理MenuInit()函数初始化一级菜单。当触发鼠标事件时,如onmouseover、onmouseout、onmouseclick等,组件将分别捕获这些事件并调用相应的事件处理函数MenuOver()、MenuOut()、MenuClick()。在这些处理函数中首先通过Event.srcElement属性确定触发事件的对象,然后依据对象type类型(如parentMenu、subMenu或其它)分别做相应处理,
如更改菜单显示外观、打开或隐藏子菜单、触发新事件等。
在一级菜单触发有关鼠标事件后,打开二级菜单并通过setCapture()方法对一级菜单对象设置鼠标捕获。此后所有在该菜单或者文档任意位置触发的onmouseover、onmouseout、onmouseclick鼠标事件都由该菜单对象负责处理,但event.srcElement属性仍是触发鼠标事件的对象而不是设置鼠标捕获的对象。当在二级子菜单或文档中非菜单处任意单击时,隐藏二级菜单并通过releaseCapture()方法释放鼠标捕获。
单击二级菜单时,其事件处理函数onmouseclick通过createEventObject方法创建一个event(事件)对象,再通过fire方法触发新事件,并利用event对象的有关属性传递信息(如event.menuId返回所选定的菜单ID),
然后由容器捕获这个事件,并调用相应事件处理函数HanderSubMenu ()。在主页面中定义函数HanderSubMenu ()能够使菜单组件更具通用性。
3.菜单数据文件(Menu.xml)
Item节点存储一级菜单信息,SubItem节点存储二级菜单信息,每个节点都含有两个属性:value(菜单ID)、name(菜单名称)。
===================================================

该程序必须在IE5.0及以上版本浏览器运行,运
行结果如图。怎么样?象不象标准Windows菜单。
附:模拟标准Windows菜单源代码(在Win98+IE5.0下通过)
===================================================

<Html><Head>
<Title>菜单组件示例</Title>
<style>
<!–.Menu{
border-top:2 solid #CCCCCC;
border-bottom:1 solid #CCCCCC;
border-left:2 solid #CCCCCC;border-right:1 solid #CCCCCC;
}
.Up_Menu {
border-top:2 outset;border-left:2 outset;border-right:1 outset #FFFFFF;
border-bottom:1 outset #FFFFFF;
background-color:#CCCCCC; }
.Dn_Menu{
border-top:2 inset ;
border-left:2 inset ;
border-right:1 inset ;
border-bottom:1 inset ;
background-color:#CCCCCC;
}–></style><script>
function HanderSubMenu(){//选择子菜单后相应处理  switch(event.menuId){    case event.menuId:      alert(event.menuId);      break;  }}</script></Head><Body style="background-color:#CCCCCC;"><TABLE style="font-size:12px;">
  <TBODY><TR height="10" >   
<TD Align="center" height=25 style="border-top:2px groove;border-bottom:2px groove;">      <div id="Menu" style="behavior:url(Menu.htc)" xmlsrc="Menu.xml" onMenuClick="HanderSubMenu()"></div>  
   </TD>                     </TR>
</TBODY></TABLE></Body></Html>
================================================
Menu.htc文件:
<PUBLIC:COMPONENT>
<PUBLIC:PROPERTY Name="xmlsrc" PUT="putxmlsrc"/>
<PUBLIC:EVENT Name="onMenuClick" ID="menuClick" />
<PUBLIC:ATTACH EVENT="ondocumentready" For="element" ONEVENT="MenuInit()" />
<PUBLIC:ATTACH EVENT="onmouseover"  for="element" ONEVENT="MenuOver()" />
<PUBLIC:ATTACH EVENT="onmouseout" for="element" ONEVENT="MenuOut()" />
<PUBLIC:ATTACH EVENT="onclick"  for="element" ONEVENT="MenuClick()" />
<Script language="JavaScript">
var xmldoc=null;// xml文档对象变量
var activeMenu=null;//打开子菜单的父菜单对象变量
var menuContainer=null;//包含子菜单项容器对象变量
function putxmlsrc(str){//载入菜单数据文件
xmldoc= new ActiveXObject("Microsoft.XMLDOM");//创建xml文档对象
  xmldoc.async=false;  xmldoc.load(str);//载入菜单数据}
function MenuInit(){//初始化菜单  
var parentMenuItems = xmldoc.selectNodes("//Itemlist/Item"); file://读取一级菜单数据  
    var xmlElement = parentMenuItems.nextNode();
  var newElement;      
while (xmlElement != null){         newElement = document.createElement("span"); file://创建菜单元素
   newElement.type = "parentMenu";    
    with (newElement){      innerHTML = xmlElement.getAttribute("name");   
                id=xmlElement.getAttribute("value");     
              className="Menu";          }  
       with (newElement.style){       
                position="relative";     
                width= 60;   
               cursor="default";    }    
     element.appendChild(newElement); file://element指附加行为的元素
        xmlElement = parentMenuItems.nextNode();         }}
function MenuOver(){  var EventSource=event.srcElement;//捕获触发事件的对象
switch(activeMenu){//判断子菜单是否存在  
  case null://不存在子菜单      
if(EventSource.type=="parentMenu")
EventSource.className="Up_Menu";  
    break;    
default:      
switch(EventSource.type){//判断触发事件对象类型       
case  "parentMenu"://移入到一级菜单        
  if(activeMenu!=EventSource){//判断是否移入到新一级菜单        
        removeSubMenu();//删除原有子菜单            
         buildSubMenu(EventSource.id);//建立新子菜单    
              EventSource.setCapture();//对新菜单设置鼠标捕获     
              activeMenu.className="Menu";         
              activeMenu=EventSource; file://更新打开子菜单的父菜单对象变量  
              activeMenu.className="Dn_Menu";            }        
              break;       
case "subMenu"://移入到子菜单        
  EventSource.style.color="#FF0000";     
     EventSource.style.textDecoration="underline";
         break;      }      break;  }}
function MenuOut(){  EventSource=event.srcElement      
     switch(activeMenu){    case null:    
  if(EventSource.type=="parentMenu")EventSource.className="Menu";    
  break;    
default:      if(EventSource.type=="subMenu"){  
      EventSource.style.color="#000000";  
      EventSource.style.textDecoration="none";  
    }      break;  }}
function MenuClick(){  EventSource=event.srcElement  
switch(EventSource.type){  
  case "parentMenu":      
if(activeMenu!=EventSource){      
  removeSubMenu();       
buildSubMenu(EventSource.id);    
    EventSource.setCapture();       
EventSource.className="Dn_Menu";        
activeMenu=EventSource;      }     
break;   
case "subMenu":      
removeSubMenu();     
activeMenu.className="Menu";  
    
activeMenu.releaseCapture();//释放鼠标捕获设置      
activeMenu=null;    
  var eventObject = createEventObject();//创建事件对象   
   eventObject.menuId = EventSource.id;//通过事件对象属性传递所选子菜单ID
      menuClick.fire(eventObject);//将事件触发到容器页面      break;  
    default:      
removeSubMenu();     
if(activeMenu!=null){       
activeMenu.releaseCapture();       
activeMenu.className="Menu";      
  activeMenu=null;       }   
    break;  } }
function buildSubMenu(EventSourceid){//建立子菜单
menuContainer = document.createElement("div");
with (menuContainer.style){  
   position="absolute";    left=0;    top=15;    color="#000000";    cursor="default";  }
  eval(EventSourceid).appendChild(menuContainer);
var subMenuItems = xmldoc.selectNodes("//Item[@value=’"+EventSourceid+"’]/SubMenu");
var xmlElement = subMenuItems.nextNode();  
  var newElement;  
while (xmlElement != null){          n
ewElement = document.createElement("div");     
     newElement.type = "subMenu";  
  with (newElement){           
innerHTML ="□ "+xmlElement.getAttribute("name");
           id=xmlElement.getAttribute("value");    }    
      with (newElement.style){      width=75;      height=18;    }    
      menuContainer.appendChild(newElement);  
  menuContainer.className="Up_Menu";       
    xmlElement = subMenuItems.nextNode();    
      }}function removeSubMenu(){//删除子菜单
if(menuContainer!=null)menuContainer.removeNode(true);}

</Script></Component>
===========================
Menu.xml文件:
<?xml version="1.0" encoding="gb2312" ?>
file://xml处理指令指明为中文编码,若不指明则不支持中文
<Itemlist>  <Item value="file" name="文件"

=======================
【问题提问、论坛交流】编辑:xker.com