無限菜單之 xml+popup 版(IE5.5+)

 

在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的理想組合。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章