筆者日前涉及一個大型ASP項目的開發,其中多次遇到多維下拉菜單(對於WEB項目而言,這裡專指網頁中的<SELECT>元素)的問題,菜單中的數據均需要從數據庫中取出,並動態的生成和變化。筆者以前曾發表過如何利用PHP和JavaScript制作二維下拉菜單的文章,目前這類文章在網絡上也頗為多見,思路也有很多創新,但對於本文中談到的多維下拉菜單,很少有人談及。筆者無意班門弄斧,只是想把開發中的一點經驗和技巧總結出來,希望能給廣大的讀者一點啟示。
多維下拉菜單,顧名思義,也就是根據一個下拉菜單的選擇,來控制其它一個或多個下拉菜單中顯示的數據。舉個例子來說明,在一個WEB管理系統中,用戶要求通過選擇單位名稱,進而選擇部門名稱,最後選擇員工。也就是說,需要提供三個下拉列表,每個下拉列表之間需要建立關聯。通過第一個能選擇第二個,並同時選擇第三個,第四個等等。那麼每一個下拉列表的顯示數據之間如何建立關聯,關聯起來的數據又如何通過事件驅動,這正是本文所要討論的主要內容。
熟悉VB、Delphi等RAD開發工具的朋友可能會感到疑惑。的確,在這些所謂的RAD開發工具中,我們可以利用Combo Boxes控件很容易的實現下拉菜單,進而實現他們之間的關聯。但是由於WEB項目中的下拉菜單是利用HTML中的<SELECT>元素來實現的,而由於Html的局限性,無論是對象的屬性還是事件模型,都遠沒有RAD工具那麼強大。所以,開發WEB項目,這種問題只能有一個解決途徑,那就是利用Javascript(也可能有人說可以利用Java來實現,那我也只好說,您是高手)。
關於JavaScript的使用,不是本文的重點,所以不了解或不熟悉Javascript的讀者請先參考JavaScript的相關資料以獲取相關信息。
好,言歸正傳,下面我們就一起來探討多維下拉菜單的設計問題。為了討論的方便,我們就以上文提到的三維下拉菜單為例,向大家一步一步的講述設計的思路。
一、分析菜單的運作流程
首先,用戶會選擇單位列表,並從中選出一個單位名稱,我們假定為單位A。這時,另外兩個下拉列表應該做些什麼?對,我們希望第二個下拉菜單能立即反映第一個下拉菜單的選擇,顯示並僅顯示單位A中的所有部門,我們再假定菜單中第一項為部門A(默認的顯示項)。那麼可能有讀者會問,第三個下拉菜單不是應該同時選擇與部門A對應的員工數據嗎?這是個很好的想法,是的,我們也應該立即改變第三個下拉菜單中的數據為部門A中的員工列表。同樣,當用戶選擇部門時,又會改變員工列表。依次類推。
以上是一種思路,可能有一些特殊的情況,例如,在改變部門列表時,並不希望立即就選擇一個部門,而是顯示一個"請選擇"字樣的提示條目。
思路已經有了,下面就是如何實現的問題了。
二、菜單數據的容器
根據常規想法,當用戶從第一個下拉菜單中選擇單位名稱,我們可以從數據庫中選出與之相關的部門數據,並顯示出來,這似乎也不無可行。但有經驗的開發者就會發現,由於Web頁面的無狀態性,當你再次連接數據庫時,Web頁面必須得再次刷新。這是一個頭疼的問題,一方面我們想連接數據庫,可另一方面我們必須得保持用戶已輸入數據不被破壞。即使如此,估計用戶也並不希望看到一個每次選擇都刷新一次的局面。難道就沒有更好的辦法?
有,那就是利用Javascript的多維數組。我們為什麼不可以把需要顯示的數據在第一次連接數據庫時全取出來,放到數組中去?這樣在每次改變菜單數據時,只要從數組中取得數據,不就可以大大的提高效率了嗎?這是個令人振奮的方法,這個方法中提到的JavaScript數組,我們暫且稱之為菜單數據的容器。
您的思路是不是一下子豁然開朗?可是躍躍欲試一番以後,是不是感到事情好像並不是那麼簡單?問題又來了,容器的結構該如何設計,數據之間的關聯又如何實現呢?別急,其實這正是問題之所在。
三、數據容器結構的設計
說起容器結構的設計,我們得感謝數據結構中的鏈表給我們的啟示--鏈表是通過指針聯系在一起的。雖然JavaScript中沒有指針的概念,但我們為什麼不可以模擬一下。
為了討論方便,我們假定數據庫的結構如下:
1、 單位信息表:(unit_id, unit_name, …)
2、 部門信息表:(dept_id, unit_id, dept_name, …)
3、 員工信息表:(emp_id, dept_id, emp_name, …)
利用這個數據庫結構,我們可以很容易的推導出數組的結構。您說的沒錯,這應該是一個多維數組。其定義方法應該象下面這樣(以部門為例):
var arrDept = new Array();
arrDept[0] = new Array(unit_id0, dept_id0 dept_name0);
arrDept[1] = new Array(unit_id1, dept_id1, dept_name1);
…
arrDept[n] = new Array(unit_idn, dept_idn, dept_namen);
n的大小視實際數據量而定,例如在單位下拉菜單中,n代表單位的總數。但讀者必須明白,正是由於n的不確定性,以上的代碼必須通過程序動態的產生。例如對於ASP程序,我們可以在<script></script>之間嵌入這樣的一段代碼:
<%以上這段代碼用來從部門表中取出數據,並產生相關的JavaScript多維數組。這只是筆者的一種演示,讀者完全可以使用更靈活的方法來提取數據。
說來說去,我們還是要回到JavaScript數組的結構定義上來。聰明的讀者應該已經從上述的代碼中發現了數組的定義方法,但筆者還是要不厭其煩的再補充一遍:
我們把數組的第一個元素定義為指針,用來指向其"父結點"。等等,什麼是父結點?父結點說明白了就是上一級結點,例如,部門的上一級是單位,員工的上一級是部門。那麼第二個元素是什麼?讓我們來看一下下面的一段<SELECT>定義:
<SELECT NAME="s1" onChange=" SetSubMenu(this)">
<OPTION Value="1">單位1</OPTION>
<OPTION Value="2">單位2</OPTION>
….
</SELECT>
<OPTION>元素的Value屬性從哪裡來呢?對,就是第二個元素,依此類推,第三個元素指的就是顯示在菜單中的數據喽,即上面的"單位1"、"單位2"…
讀者到這裡可能有些糊塗了,說這麼多,這個數組到底是什麼樣?別急,讓我們以部門為例,給出一段根據部門庫中的數據動態生成的數組模擬代碼:
<SCRIPT LANGUAGE="JavaSCRIPT">
<!-
…
var arrDept = new Array();
arrDept[0] = new Array('u01', 'd01', '部門1');
arrDept[1] = new Array('u01', 'd02', '部門2');
…
arrDept[8] = new Array('u06', 'd08', '部門8');
…
arrDept[15] = new Array('u08', 'd15', '部門15');
…
->
</SCRIPT>
數組終於真相大白。以"u"開頭數據的代表單位的編號,即,指向單位的指針,也就是說,我們可以通過這個編號來確定該單位所屬的部門;以"d"開頭的數據代表部門的編號,用來供下一級選單(即員工選單)的指針使用。(注:實際使用中,數據格式根據情況而定)
有一個問題,象單位這樣沒有父結點的數組該如何定義?很簡單,把數組的第一個元素全部置為0就行了。
下一步,是到我們編寫JavaScript代碼來控制菜單的顯示的時候了。我們就假定您生成的三個數組分別命名為arrUnit,arrDept,arrEmp。
四、編寫JavaScript代碼,控制菜單的顯示
其實有經驗的程序員,讀到這裡應該知道如何進行下去。但您不妨讀下去,也許,筆者的方法對您未必不是一種新的嘗試。而且,據我猜測,讀我這篇文章的大多數都是沒有經驗的程序員,呵呵,幫人幫到底吧。Come On, Let's Go.
讓菜單顯示出來,其實有好幾種思路。利用ASP等程序直接生成<SELECT>結構、利用OPTION對象的ADD和Remove方法動態添加和改變等等,都是可以使用的方法。但,經過筆者的多次實踐和摸索,有一種方法更為有效,那就是利用Script代碼動態的改寫整個<SELECT>框架。
好,就讓我們從加載頁面(document)開始,一步一步的講解JavaScript代碼到底是如何控制菜單的顯示的。
既然有三個菜單,那麼我們就得事先設計出這樣的Html代碼(其實要不要無所謂,放在那裡只是為了便於理解):
<BODY BGCOLOR="#FFFFFF" ONLOAD="body_onload()">
您有可能要問,這裡怎麼什麼數據都沒有?不要奇怪,等一下您自然就會明白。我們來看一下<BODY>對象的ONLOAD事件body_onload()做了些什麼工作?
function body_onload(){
讓我們來研究一下。首先程序利用GetParent()函數取得s0的容器TD對象句柄,然後,利用MakMenu()函數產生菜單代碼,並把代碼賦值給剛才取得的TD對象;然後是s1,接著是s2.。GetParent()函數定義如下:
function GetParent(src, tag){
if (src && src.tagName!=tag){
return(GetParent(src.parentElement, tag));
}
return src;
}
這裡的tag參數必須大寫,例如TD、TR、TABLE,函數返回的是離src指定的元素最近的由tag標簽定義的父對象。
我們要特別說明一下MakeMenu()函數,這個函數的作用不言而喻--產生菜單的Html定義,先看看函數定義:
來看一下參數的含義。arrSub,指的是菜單數據的來源,其實就是我們上文定義的數組;pValue,指定父結點的編號,根據這個編號,我們可以找出所有的子結點數據;cValue,指定菜單的默認顯示項;name,指定產生的<SELECT>菜單的名稱;bulSkip,指定菜單的默認顯示項是"<未選擇>"還是具體數據。
GetSelectValue()函數的目的,就是取得<SELECT>對象當前顯示的值。如果沒有顯示任何值,函數返回0。
function GetSelectValue(oSelect){
那麼,當用戶加載頁面之後,首先運行的就是body_onload()函數,該函數根據已經產生的Javascript多維數組,利用MakeMenu()函數動態的生成菜單的HTML代碼,並根據DHtml的原理加載到頁面中。OK,運行一下頁面,看看菜單是否正常顯示?如果有什麼問題,抓緊時間好好調試一下,例如數據庫的連接是否正常,Javascript代碼的大小寫是否正確,數組的定義是否有什麼問題…
下一步,選擇菜單…等一下,好像還有什麼遺漏,對了,我們還必須為<SELECT>對象的onchange事件添加程序代碼:
function SetSubMenu(pSelect){
好了,我們再檢查一下,還有沒有什麼遺漏。從第一個下拉菜單中選擇單位,立即,第二個下拉菜單和第三個下拉菜單都發生了變化,看看是不是想要的。(不是?呵呵,回頭好好檢查);再在第二個下拉菜單中選擇部門,看看員工的下拉菜單是否跟著改變?
恭喜你,你已經成功的實現了三維下拉菜單。其實,對於二維菜單,實現的方法完全一致。讀者完全可以利用本文的方法實現WEB項目菜單的全攻略。以後再遇到類似的問題,我想這回你一定可以毫不猶豫的說,讓我來搞定它