zt无限菜单之 xml+popup 版(IE5.5+)
无限菜单之 xml+popup 版(IE5.5+)
终于成功实现了用IE5.5+中的Popup结合xml来制作无限级菜单,这个菜单将是本年度网上最有价值的Web脚本之一!
可以看示例:演示 下载。(可以试试把浏览器变小一点,看看菜单可不可以超出浏览器……)
在IE5.5+中开始支持的Popup窗口有很多很特别的特性:
- Popup窗口可以超出浏览器窗口区域;
- 可以不用担心被下拉框、flash、Iframe等这些东西遮挡;
- 一个Popup窗口打开后,当在它的区域以外点击或者另一个Popup窗口被打开时都会自动关闭;
- Popup窗口是没有焦点的;
- 用户不能改变Popup窗口大小;
- Popup窗口中的内容是不能被选择的;
- ......
要用Popup制作菜单一个最重要的问题就是要解决多个Popup共存的问题,Msdn上的描述是:“一个Popup窗口打开后,当另一个Popup窗口被打开时就会自动关闭”。我本来一直以为Popup窗口是不可以多个共存的,不过偶然从51js上知道:父Popup窗口可以创建子Popup窗口,子Popup窗口又可以创建子Popup窗口,这样就可以同时存在一个Popup窗口家族,当父Popup窗口关闭,所有的子孙窗口都会关闭。这点恰好可以运用在菜单中——父菜单关闭子菜单一起关闭,省去很多繁琐的判断。
最开始,我写了一个简单的两层的Popup右键菜单,为此专门写了一个根据级数生成Popup窗口家族的递归方法:
var pops = new Array(); // 用来存储Popup窗口家族的数组
function CreatePopup(degree)
{
if (degree < 0) // 层数不能小于0
return null;
if (pops[degree] != null) //如果已经存在则不需创建
return pops[degree];
if (degree == 0)
pops[0] = window.createPopup(); //创建最顶层Popup窗口
else{
if (pops[degree - 1] == null)
pops[degree - 1] = CreatePopup(degree - 1) //递归回溯一层一层开始创建
pops[degree] = pops[degree - 1].document.parentWindow.createPopup();
//从父Popup窗口创建子Popup窗口
}
pops[degree].document.body.setAttribute("degree", degree);
return pops[degree];
}
CreatePopup(1); //创建一个2层的Popup家族
Xml真是好东西,在Web控件中,可以得到灵活的运用(在我的上篇随笔《xml+xsl+htc,web控件开发的理想组合》中,已经简单提到了xml+xsl+htc的理想组合),在这里也不例外,先用Xml来定义好菜单的数据menu.xml:
<?xml version="1.0" encoding="GB2312"?> <Menu> <MenuItem Text="菜单1"> <MenuItem Text="菜单1子菜单"/> </MenuItem> <MenuItem Text="菜单2"/> </Menu>
菜单数据已经定义好了,现在问题就是如何来把这些xml数据变成Popup菜单?!在传统的用div(层)来实现的菜单,一般都是一次性将所有级菜单数据生成HTML,放在各个层中,然后动态在制定位置显示隐藏这些层来实现模拟菜单的效果,在这里当然也可以这么做。回想一下菜单的特征:每次显示一级菜单,如果该级菜单中某菜单项中有子菜单,当鼠标经过或者点击该菜单项时弹出下级子菜单,这是一个递归的过程。如果我们可以:每次显示一级xml的内容,如果该级xml中某节点有子节点,当鼠标经过该节点时读取下级xml的内容,这也是一个递归的过程,而且恰好和菜单的显示过程是一一对应的。
既然大胆假设了一把,那么就来小心求证一下:首先,用xsl来实现解析一级xml很容易搞定,使用xsl:for-each遍历生成子菜单,并且,如果子菜单中还有子xml数据,将这些子xml数据存在子菜单对应的数据岛中,以备后面处理鼠标经过或点击菜单项时用到。下面是Menu.xsl用来生成子菜单的部分:
<!-- 遍历子菜单 --> <xsl:for-each select="MenuItem"> <tr height="18" onmouseover="ItemOver(this)" onmouseout="ItemOut(this);" onclick="ItemClick(this)"> <td width="17" align="center"> <IMG SRC="images/dot1.gif" WIDTH="6" HEIGHT="6" BORDER="0" ALT=""/> </td> <td> <xsl:value-of select="@Text" /> <xsl:if test="count(MenuItem) > 0"> <!-- 这里用来存储子菜单的xml数据 --> <xml> <xsl:copy-of select="."/> </xml> </xsl:if> </td> <td width="20" align="right" valign="top" style="padding-right: 6px; padding-top:4px;"> <!-- 如果有子菜单则显示箭头 --> <xsl:if test="count(MenuItem) > 0"> <img src="images/arrowR.gif"/> </xsl:if> </td> </tr> </xsl:for-each>
// 创建当前窗体(可以是IE窗体也可以是Popup窗体)的Popup对象
// 这个Popup对象就是用来存储子菜单数据的
var oPopup = document.parentWindow.createPopup();
// 装载xsl
var stylesheet = new ActiveXObject("Microsoft.XMLDOM");
stylesheet.async = false;
stylesheet.load( "menu.xsl" );
// 鼠标经过菜单项
function ItemOver(obj)
{
// 隐藏已经打开的菜单项
if (preObj != null)
{
if (preObj == obj)
return;
oPopup.hide();
// 要清空原Popup中的数据——document.write()方法是接着原来的内容往里面写,所以如果不清空会出现重复数据
oPopup = document.parentWindow.createPopup();
// 恢复前一个菜单项的状态
ItemNormal(preObj);
preObj = null;
}
obj.className='PopMenuItemOver';
if (obj.cells(2).children.length > 0) //有子菜单
{
obj.cells(2).children(0).src = "images/ArrowRHighlight.gif";
// 获取子菜单xml数据
var subMenuData = obj.all.tags("xml")(0).XMLDocument;
// 根据子菜单xml数据和当前xsl文档生成HTML
var sHtml = subMenuData.transformNode(stylesheet);
// 将解析出来的HTML全部输出到Popup中,在Popup中,又可以利用这些脚本再Popup……
oPopup.document.write(sHtml);
// 计算popup内容的实际宽度高度
oPopup.show(0, 0, 1, 1, obj);
var width = oPopup.document.body.scrollWidth;
var height = oPopup.document.body.scrollHeight;
oPopup.hide();
// 显示菜单
oPopup.show(obj.offsetWidth, 0, width, height, obj);
preObj = obj;
}
}
// 鼠标移出菜单项
function ItemOut(obj)
{
if (oPopup.isOpen && preObj == obj) // 如果子菜单被打开则跳过
return;
ItemNormal(obj);
}
// 恢复到菜单项的默认状态
function ItemNormal(obj)
{
obj.className='PopMenuItem';
if (obj.cells(2).children.length > 0)
{
obj.cells(2).children(0).src = "images/ArrowR.gif";
}
}
作为一个菜单来讲,这个例子还有很多要完善的地方(当我再结合htc时它绝对是一个非常棒的菜单控件),但是这个例子已经完整地实现了一个xml结合xsl递归生成无限Popup菜单的例子,这个简洁的代码再次印证了xml+xsl+htc的理想组合。
浙公网安备 33010602011771号