### 用 XML 实现弹出式菜单

XML已经成为了新一代网络计算的世界语,这一点已经在各大软件公司的产品中逐步体现出来。XML本身并不是一种计算语言,而更多表现为一种数据描述的格式。相对于HTML,它更多具有数据定义的特性,而相对于数据库,它又更加灵活和适于在网络上传输。围绕XML的相关技术也层出不穷,如XSL, XSLT等,它们的配合让我们能够真正在XML上开始完成一些有趣的任务。

本文就是以一个XML初学者的角度在网页上实现一个动态弹出式菜单。对于同样希望了解XML与表现无关的特性的读者应当有所帮助。

>应用情景

我们经常在一些网站看到形形色色的菜单形式,大部分的菜单都借用了DHTML的一些特性实现,但是想好好地利用这些已有的菜单实现一些自己的菜单并不容易。首先是必须从它们的整个页面中把一些代码“分析”,其次要分析其中的显示方式与显示内容的关系,如果自己的菜单结构和显示方式不同于其它网站的设计,就得花更多的功夫去研究新的显示方法。

而我们希望实现一个可以动态调整的菜单结构,这种菜单应当可以适用于我们所要完成的整个一个网站。一旦我们的网页页面设计发生了变化,我们可以灵活地把菜单显示方式改变,适应新的网页整体风格。

我们所希望实现的菜单首先可以表现为以下样子:

图1:我们想实现的弹出式菜单的一种外观

实现方法

在大量接触HTML后,今天的网页程序设计中我们更多地愿意直接把一个页面的样子用所见即所得的编辑工具(如Frontpage, Dreamweaver等)“画”出来,而不再习惯于利用数据结构的方式设计能够通用的模块。为了能够达到我们的目的,我们又一次地开始重新考虑如何定义一个通用的菜单数据结构来描述一个菜单的信息,然后再使用一个显示模块表现这些数据信息,如果改变显示模块的一些参数,就可以实现相同菜单内容的不同外观和行为。
在了解了XML的基本定义方法之后,我们定义了如下的示范数据:
列表1: 一个示范性菜单数据结构XML
<?xml version="1.0" encoding="gb2312" ?>
<?xml:stylesheet type="text/xsl" href="menus.xsl"?>
<TOPICLIST TYPE="动态菜单">
<TOPICS TYPE="网络好去处">
<TOPIC>
<TITLE>中国XML联盟</TITLE>
<URL>http://www.xml.org.cn</URL>
</TOPIC>
<TOPIC>
<TITLE>易方软件公司</TITLE>
<URL>http://www.tangram.com.cn</URL>
</TOPIC>
<TOPIC>
<TITLE>微软MSDN</TITLE>
<URL>http://msdn.microsoft.com</URL>
</TOPIC>
</TOPICS>
<TOPICS TYPE="XML的应用实例">
<TOPIC>
<TITLE>biztalk</TITLE>
<URL>http://www.biztalk.org</URL>
</TOPIC>
</TOPICS>
<TOPICS TYPE="XML特性">
<TOPIC>
<TITLE>精简</TITLE>
<URL>express.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>自解释</TITLE>
<URL>description.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>可交换</TITLE>
<URL>exchange.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>与显示无关</TITLE>
<URL>nopresentation.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>精简</TITLE>
<URL>express.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>自解释</TITLE>
<URL>description.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>可交换</TITLE>
<URL>exchange.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>与显示无关</TITLE>
<URL>nopresentation.htm</URL>
</TOPIC>
</TOPICS>
<TOPICS TYPE="联系作者">
<TOPIC>
<TITLE>Isaac M.</TITLE>
<URL>mailto: isaac@tangram.com.cn</URL>
</TOPIC>
</TOPICS>
<TOPICS TYPE="小试牛刀">
<TOPIC>
<TITLE>红色牛刀</TITLE>
<URL>red.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>白色牛刀</TITLE>
<URL>white.htm</URL>
</TOPIC>
<TOPIC>
<TITLE>褐色牛刀</TITLE>
<URL>brown.htm</URL>
</TOPIC>
</TOPICS>
</TOPICLIST>
在这段数据中,菜单的主要项目和子项分别使用<TOPICS>和<TOPIC>元素表示,一目了然。同时你也可以看到,我们已经为这段数据指定了一段外部的转化为DHTML的XSL代码:
<?xml:stylesheet type="text/xsl" href="menus.xsl"?>
这行信息能够让IE自动把菜单的数据结构与一个特定的XSL结合在一起并转化为DHTML进行解释。另外,为了在IE中正确解释中文内容,在这段数据结构的第一个部分加入了encoding="gb2312" 的描述。

学习定义 XSL 是一个稍微麻烦的事情,因为 XSL 既像一段 DHTML 的描述,由好像有可执行程序的功能。假如你比较熟悉 DHTML,应当很容易掌握 XSL 的要领,但是自己定义的过程,应当配合 XML 一起,一边测试一边定义是一个不错的方法。我们在自己的菜单中,采用了<DIV>把菜单的项目逐个解析成为 DHTML 的元素。
列表 2:能够表现我们所定义的菜单数据的一种 XSL (Menus.xsl)
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<!-- MENUS.XSL -->
<xsl:template match="/">
<HTML>
<HEAD>
<TITLE><xsl:value-of select="TOPICLIST/@TYPE" /></TITLE>
<LINK REL="stylesheet" TYPE="text/css" HREF="menus.css" />
<SCRIPT LANGUAGE="JScript" SRC="menus.js"></SCRIPT>
</HEAD>
<BODY>
<H1><xsl:value-of select="TOPICLIST/@TYPE" /></H1>
<!-- BUILD MENUBAR -->
<DIV ID="divMenuBar">
<TABLE ID="tblMenuBar" BORDER="0">
<TR>
<xsl:for-each select="//TOPICS[TOPIC]">
<TD CLASS="clsMenuBarItem">
<xsl:attribute name="ID">tdMenuBarItem<xsl:value-of select="@TYPE" /></xsl:attribute>
<xsl:value-of select="@TYPE" />
</TD>
<xsl:if test="context()[not(end())]">
<TD>|</TD>
</xsl:if>
</xsl:for-each>
</TR>
</TABLE>
</DIV>
<!-- BUILD INDIVIDUAL MENUS -->
<xsl:for-each select="//TOPICS[TOPIC]">
<DIV CLASS="clsMenu">
<xsl:attribute name="ID">divMenu<xsl:value-of select="@TYPE" /></xsl:attribute>
<DIV CLASS="clsMenuSpacer"></DIV>
<xsl:for-each select="TOPIC">
<DIV>
<A TARGET="main">
<xsl:attribute name="HREF"><xsl:value-of select="URL" /></xsl:attribute>
<xsl:value-of select="TITLE" />
</A>
</DIV>
</xsl:for-each>
</DIV>
</xsl:for-each>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
在最早的 Menu.xsl 定义中,我们把菜单的交互方式(即如何响应鼠标的动作),以及颜色和字体等风格元素都与 XSL 结合在一起。但是我们发现还可以分离,一边保留更大的灵活性。所以我们又定义了 Menus.css 和 Menus.js 文件,单独定义了菜单主项和子项的外观等特性,以及如何响应用户的动作,这样我们又获得了进一步的灵活性。
列表 3:在 XSL 中可以调整的格式表 StyleSheet (Menus.css)
/* MENUS.CSS */
BODY { font-family:verdana; font-size:60%; background-color:ffffff; }
H1 { font-size:120%; font-style:italic; }
DIV#divMenuBar { background-color:#6699cc; }
TABLE#tblMenuBar TD { font-size:60%; color:white; padding:0px 5px 0px 5px; cursor:default; }
TABLE#tblMenuBar TD.clsMenuBarItem { font-weight:bold; cursor:hand; }
DIV.clsMenu {
font-size:100%; background-color:#000000;
position:absolute; visibility:hidden; width:130px;
padding:5px 5px 5px 8px; border-top:1 white solid;
}
DIV.clsMenu A { text-decoration:none; color:white; font-weight:bold; }
DIV.clsMenu A:hover { color:blue; }

菜单的解析代码Menus.xsl中还包含了一个外部的Javascript代码文件,这样我们的动态菜单的各个模块就可以协同工作了。
列表4:实现Menu的交互方式的代码 (Menus.js)
/* MENUS.JS */
var eOpenMenu = null;
function OpenMenu(eSrc,eMenu)
{
eMenu.style.left = eSrc.offsetLeft + divMenuBar.offsetLeft;
eMenu.style.top = divMenuBar.offsetHeight + divMenuBar.offsetTop;
eMenu.style.visibility = "visible";
eOpenMenu = eMenu;
}
function CloseMenu(eMenu)
{
eMenu.style.visibility = "hidden";
eOpenMenu = null;
}
function document.onmouseover()
{
var eSrc = window.event.srcElement;
if ("clsMenuBarItem" == eSrc.className)
{
eSrc.style.color = "moccasin";
var eMenu = document.all[eSrc.id.replace("tdMenuBarItem","divMenu")];
if (eOpenMenu && eOpenMenu != eMenu)
{
CloseMenu(eOpenMenu);
}
if (eMenu)
{
OpenMenu(eSrc,eMenu);
}
}
else if (eOpenMenu && !eOpenMenu.contains(eSrc) && !divMenuBar.contains(eSrc))
{
CloseMenu(eOpenMenu);
}
}
function document.onmouseout()
{
var eSrc = window.event.srcElement;
if ("clsMenuBarItem" == eSrc.className)
{
eSrc.style.color = "";
}
}
下面我们只要对以上与数据、交互方式和风格相关的各种文件进行一些参数修改就能够改变这个菜单模块的行为和表现了。修改Menu.xml能够动态增加菜单的内容信息,修改menu.xsl能够改变菜单的格局,修改menus.js能够改变菜单的交互方式,修改menus.css能够改变菜单的颜色、字体等外观。
例如,只是在Menu.xsl和menu.js修改很少代码,就能够让以上的菜单信息表现出完全不同的一种交互风格,成为垂直型的弹出菜单。如果你有兴趣,就在示例的代码中自己修改一下,变成自己的菜单代码吧(在我们提供的下载代码中,menus2.xsl和menus2.js等都是修改后的实例)。

图2:改变XSL文件参数后弹出式菜单的另一种外观

总结

通过这样一个简单却很有趣的编程经历,我们发现XML确实是一种非常灵活的网络信息表现语言,它的与显示无关的特性能够让我们在这个初步探索的基础上为它增加更多的特性。例如,我们的下一步目标就是把menus.xml用一段ASP程序所替代,在我们的网站数据库中动态提取当前页面所需要的菜单内容,然后再结合增强的XSL和Javascript编写更加复杂的菜单模块。