宝玉

专注于web开发技术
随笔 - 114, 评论 - 1503 , 引用 - 581

无限菜单之 xml+popup 版(IE5.5+)

终于成功实现了用IE5.5+中的Popup结合xml来制作无限级菜单,这个菜单将是本年度网上最有价值的Web脚本之一!

可以看示例:演示 下载。(可以试试把浏览器变小一点,看看菜单可不可以超出浏览器……)

在IE5.5+中开始支持的Popup窗口有很多很特别的特性:

  • Popup窗口可以超出浏览器窗口区域;
  • 可以不用担心被下拉框、flash、Iframe等这些东西遮挡;
  • 一个Popup窗口打开后,当在它的区域以外点击或者另一个Popup窗口被打开时都会自动关闭;
  • Popup窗口是没有焦点的;
  • 用户不能改变Popup窗口大小;
  • Popup窗口中的内容是不能被选择的;
  • ......
因为这些特征,Popup窗口制作的菜单比起传统的div(层)实现的菜单有着得天独厚的优势,不仅效果会非常好,而且代码也会是非常少的,只是对于实现起来却有几个需要解决的棘手问题:多个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家族
这个方法可以解决多个Popup共存的问题,只是如果要使用这个方法来实现无限级菜单代码恐怕就比较繁琐了。这个例子,只是为我验证了无限级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,可以很方便直观的定义菜单数据。

菜单数据已经定义好了,现在问题就是如何来把这些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>
现在就是解决鼠标经过菜单项时,如果有子菜单则解析子菜单数据,并使用子Popup窗口显示子菜单数据,刚才我们存的数据岛这时候就可以派上用场了。在xsl中,需要用到一些客户端脚本来辅助完成了
// 创建当前窗体(可以是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";
	}
}
这样:使用menu.xsl解析一级xml的内容生成一级菜单,如果该级xml中某节点有子节点,当鼠标经过该节点时,创建当前窗口/Popup窗口的子Popup窗口,使用menu.xsl解析子节点中xml的内容并输出显示到子Popup中,递归,即可通过Popup显示所有子菜单。

作为一个菜单来讲,这个例子还有很多要完善的地方(当我再结合htc时它绝对是一个非常棒的菜单控件),但是这个例子已经完整地实现了一个xml结合xsl递归生成无限Popup菜单的例子,这个简洁的代码再次印证了xml+xsl+htc的理想组合。

发表于 2004年6月30日 8:59

评论

# re: 无限菜单之 xml+popup 版(IE5.5+)

这个是你昨天晚上解决的那个东东吗?
天才+奋斗,偶好崇拜你!
这样也好,偶将来就不用动脑工作了,一心出去玩去*_*
2004-6-30 16:37 | 随风铃

# re: 无限菜单之 xml+popup 版(IE5.5+)

我还没看,不知菜单上能不能加上图标?
2004-7-2 13:39 | hsb0307

# re: 无限菜单之 xml+popup 版(IE5.5+)

很需要,感谢你
2004-7-7 9:24 | exSet

# re: 无限菜单之 xml+popup 版(IE5.5+)

我很喜欢宝玉gg!
2004-7-13 15:56 | 小白

# re: 无限菜单之 xml+popup 版(IE5.5+)

老大,怎么在菜单项上加入链接,不然菜单只是摆设而已啊?能不能教教小弟啊。oinet@163.com
2004-7-20 10:08 | leo

# re: 无限菜单之 xml+popup 版(IE5.5+)

一直在寻找这样的东西,谢谢
2004-7-20 13:56 | bestcomy

# re: 无限菜单之 xml+popup 版(IE5.5+)

加上事件:
onclick="parent.location.href='http://www.webuc.net'"
2004-7-23 12:38 | 宝玉

# re: 无限菜单之 xml+popup 版(IE5.5+)

正想用popup写个menu htc,今天看你的blog看到你写的原型,真不错
回头封装成htc就可以用了:)

宝玉有空看看我做的东西给点意见,先谢谢:)

http://www.51js.com/viewthread.php?fpage=2&tid=28428

网上找不到合适的环境,所以是一段flash录像
2004-10-23 1:46 | 一片蓝枫

# re: 无限菜单之 xml+popup 版(IE5.5+)

sadasd
2004-10-25 1:37 | sad

# re: 无限菜单之 xml+popup 版(IE5.5+)

老大,怎么在其中加入链接啊?你都使用遍历,在哪个位置加入链接啊。小弟愚笨,老大赐教!!!
2004-8-25 21:31 | leo

# re: 无限菜单之 xml+popup 版(IE5.5+)

您有没有办法在菜单多时取消页面晃动问题,
两年前我一直试图解决这个问题
2004-11-25 6:28 | zhao

# re: 无限菜单之 xml+popup 版(IE5.5+)

好像很慢?什么原因呢,递归有问题?在firefox上根本就好像阻塞掉不能弹出子菜单,甚至跟踪当前选中的菜单项都不更新。。
2005-1-20 1:28 | yxd

# re: 无限菜单之 xml+popup 版(IE5.5+)

msdn上有利用popup构建菜单的htc的例子
2005-1-30 22:56 | sniper

# re: 无限菜单之 xml+popup 版(IE5.5+)

good
2005-3-29 0:34 | 11

# re: 无限菜单之 xml+popup 版(IE5.5+)

very good
2005-7-28 3:32 | tamefox

# re: 无限菜单之 xml+popup 版(IE5.5+)

老大,我把你的这个菜单装到我的一个frame中了,我怎么给菜单加上一个连接阿?用你说的这个:onclick="parent.location.href='http://www.webuc.net'" 不行的应为我没有办法知道有几个Parent,谢谢你了!
2006-2-8 9:32 | Ameng

# re: 无限菜单之 xml+popup 版(IE5.5+)

速度好慢 ,MSDN...
2006-4-5 2:14 | e

# re: 无限菜单之 xml+popup 版(IE5.5+)

怎么宝玉这里的演示我的ie不会拦截,我下载后打开自己的ie会拦截呢?
2006-8-23 10:02 | xugq

# re: 无限菜单之 xml+popup 版(IE5.5+)

很不稳定,并且容易导致IE司机,比如,弹出菜单后,点窗口的其他地方,很容易导致IE崩溃
2007-3-9 0:09 | TIGER

# re: 无限菜单之 xml+popup 版(IE5.5+)

这个菜单的功能太多了
2007-10-19 4:43 | 同传设备租赁

# 无限菜单之 xml popup 版

zt无限菜单之xml popup版(IE5.5 ) 无限菜单之xml popup版(IE5.5 ) 终于成功实现了用IE5.5 中的Popup结合xml来制作无限级菜单,这个菜单将...
2008-8-6 10:53 | 鱻鯹鯹oоО

Post Comment

主题  
姓名  
主页
校验码  
内容   
京ICP备 05050892号