在WEB項目中多維下拉式功能表的實現技巧和方法
作者:tttk
來源:CSDN
點擊次數:11382
2003-6-12 8:44:36
〓簡介〓
【摘要】對於web項目中下拉式功能表的設計,尤其涉及到複雜的多維菜單,許多web開發的入門者往往不知所措,網路中關於此類問題的討論也是屢見不鮮。文章旨在通過一個執行個體的分析,協助初學者掌握web項目中多維動態下拉式功能表的實現原理和方法,協助入門者熟悉javascript、DHTML、ASP等相互結合的技巧,從而找到一種動態下拉式功能表的通用解決方案。
〓本文〓
前 言
筆者日前涉及一個大型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>之間嵌入這樣的一段代碼:
<%
Dim rs, i
'[串連資料庫,取出資料]
response.write "var arrDept = new Array();" & vbNewLine
i = 0
while not rs.EOF
response.write "arrDept[" & i & "] = new Array('" & rs(unit_id) & "', '" & _
rs(dept_id) & "', '" & rs(dept_name) & "');" & vbNewLine
rs.MoveNext
i = i +1
wend
…
%>
以上這段代碼用來從部門表中取出資料,併產生相關的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()">
...
<TD>
<SELECT NAME="s0" ONCHANGE="SetSubmenu(this)"></SELECT>
</TD>
<TD>
<SELECT NAME="s1" ONCHANGE="SetSubmenu(this)"></SELECT>
</TD>
<TD>
<SELECT NAME="s2" ONCHANGE="SetSubmenu(this)"></SELECT>
</TD>
</BODY>
您有可能要問,這裡怎麼什麼資料都沒有?不要奇怪,等一下您自然就會明白。我們來看一下<BODY>對象的ONLOAD事件body_onload()做了些什麼工作?
function body_onload(){
var TD = GetParent(document.all("s0"), "TD");
TD.innerHTML = MakeMenu(arrUnit, 0, 0, "s0", 1);
TD = GetParent(document.all("s1"), "TD");
TD.innerHTML = MakeMenu(arrDept, GetSelectValue(document.all("s0")), 0, "s1", 1);
TD = GetParent(document.all("s2"), "TD");
TD.innerHTML = MakeMenu(arrEmp, GetSelectValue(document.all("s1")), 0, "s2", 1);
}
讓我們來研究一下。首先程式利用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定義,先看看函數定義:
function MakeMenu(arrSub, pValue, cValue, name, bulSkip){
var sHTML = "<select name='" + name + "' onchange='SetSubMenu(this)' >";
if (bulSkip) sHTML += "<option value=0><未選擇></option>";
for (var i=0; i < arrSub.length; i++){
if (arrSub[i][0]==pValue){
var tag = (arrSub[i][1]==cValue)?" selected>":">";
sHTML += "<option value='" + arrSub[i][1] + "'" + tag + arrSub[i][2] + "</option>";
}
}
sHTML += "</select>";
return sHTML;
}
來看一下參數的含義。arrSub,指的是菜單資料的來源,其實就是我們上文定義的數組;pValue,指定父結點的編號,根據這個編號,我們可以找出所有的子結點資料;cValue,指定菜單的預設顯示項目;name,指定產生的<SELECT>菜單的名稱;bulSkip,指定菜單的預設顯示項目是"<未選擇>"還是具體資料。
GetSelectValue()函數的目的,就是取得<SELECT>對象當前顯示的值。如果沒有顯示任何值,函數返回0。
function GetSelectValue(oSelect){
if (oSelect.selectedIndex < 0) return 0;
return oSelect.options(oSelect.selectedIndex).value;
}
那麼,當使用者載入頁面之後,首先啟動並執行就是body_onload()函數,該函數根據已經產生的JavaScript多維陣列,利用MakeMenu()函數動態產生菜單的HTML代碼,並根據DHTML的原理載入到頁面中。OK,運行一下頁面,看看菜單是否正常顯示?如果有什麼問題,抓緊時間好好調試一下,例如資料庫的串連是否正常,javascript代碼的大小寫是否正確,數組的定義是否有什麼問題…
下一步,選擇菜單…等一下,好像還有什麼遺漏,對了,我們還必須為<SELECT>對象的onchange事件添加程式碼:
function SetSubMenu(pSelect){
var oOption, sValue;
if (pSelect.selectedIndex < 0) return;
switch (pSelect.name){
case "s0":
var TD = GetParent(document.all("s1"), "TD");
TD.innerHTML = MakeMenu(arrDept, GetSelectValue("s0"), "0", "s1", 0);
TD = GetParent(document.all("s2"), "TD");
TD.innerHTML = MakeMenu(arrEmp, GetSelectValue("s1"), "0", "s2", 0);
break;
case "s1":
var TD = GetParent(document.all("s2"), "TD");
TD.innerHTML = MakeMenu(arrEmp, GetSelectValue("s1"), "0", "s2", 0);
break;
default:
}
}
好了,我們再檢查一下,還有沒有什麼遺漏。從第一個下拉式功能表中選擇單位,立即,第二個下拉式功能表和第三個下拉式功能表都發生了變化,看看是不是想要的。(不是?呵呵,回頭好好檢查);再在第二個下拉式功能表中選擇部門,看看員工的下拉式功能表是否跟著改變?
恭喜你,你已經成功的實現了三維下拉式功能表。其實,對於二維菜單,實現的方法完全一致。讀者完全可以利用本文的方法實現WEB項目菜單的全攻略。以後再遇到類似的問題,我想這回你一定可以毫不猶豫的說,讓我來搞定它。
文章到此,也應該結束,但我還想補充兩句。本文所提供的只是一種解題思路,或者說是一種方法。我不喜歡硬搬照抄的做法,相反,我希望讀者能夠舉一反三,衍生出更多的新思路、新功能,那將是我願意看到的。如果你有什麼想法,歡迎您同我交流。 tttk2000@hotmail.com
< html>
< head>
< /head>
< body>
< script language="JavaScript">
< !--
var subcat = new Array();
subcat[0] = new Array('10','1','=1')
subcat[1] = new Array('10','2','=2')
subcat[2] = new Array('10','3','=3')
subcat[3] = new Array('10','4','=4')
subcat[4] = new Array('10','5','=5')
subcat[5] = new Array('10','6','=6')
subcat[6] = new Array('10','7','=7')
subcat[7] = new Array('10','8','=8')
subcat[8] = new Array('10','9','=9')
subcat[9] = new Array('10','10','=10')
subcat[10] = new Array('20','11','=11')
subcat[11] = new Array('20','12','=12')
subcat[12] = new Array('20','13','=13')
subcat[13] = new Array('20','14','=14')
subcat[14] = new Array('20','15','=15')
subcat[15] = new Array('20','16','=16')
subcat[16] = new Array('20','17','=17')
subcat[17] = new Array('20','18','=18')
subcat[18] = new Array('20','19','=19')
subcat[19] = new Array('20','20','=20')
function changeselect1(locationid)
{
document.form1.s2.length = 0; //初始化下拉式清單 清空下拉資料
document.form1.s2.options[0] = new Option('==請選擇==',''); //給第一個值
for (i=0; i<subcat.length; i++) //legth=20
{
if (subcat[i][0] == locationid) //[0] [1] 第一列 第二列
{document.form1.s2.options[document.form1.s2.length] = new Option(subcat[i][1], subcat[i][2]);} //建立option
//第一次 length=1 因為有==請選擇==
//i=9時 length= 10 值有11個 因為從0數起 subcat[i][0] == locationid屏蔽了再寫
}
}
//-->
< /script>
< form name="form1" method="post" runat="server">
二級聯動:
< select name="s1" onChange="changeselect1(this.value)">
< option>==請選擇==</option>
< option value="10">1-10</option>
< option value="20">11-20</option>
< /select>
< select name="s2" onChange="alert(this.value)">
< option>==請選擇==</option>
< /select>
< /form>
<?php
/*******************************************
**********功能:php二級聯動菜單*************
**********作者:沖星*************************
**********Email:njj@nuc.edu.cn**************
**********日期:2004/10/02******************
**********請轉載時保留著作權資訊**************
*******************************************/
require_once('db.inc.php');//資料庫連接
$db=new hq_online;
$db1=new hq_online;
$db->query("select * from news_bclass order by id desc");
$fMenu="";
$fValue="";
while($db->next_record()){
$fMenu.="/"".$db->Record["bname"]."/",";
$fValue.="/"".$db->Record["id"]."/",";
}
$fMenu=substr($fMenu,0,(strlen($fMenu)-1));
$fMenu="[".$fMenu."]";//*****************************得到var fMenu
$fValue=substr($fValue,0,(strlen($fValue)-1));
$fValue="[".$fValue."]";//*****************************得到var fValue
//得到*****************************var sMenu
$db->query("select * from news_bclass order by id desc");
while($db->next_record()){
$parentid=$db->Record["id"];
$db1->query("select * from news_sclass where parentid='$parentid' order by parentid desc");
while($db1->next_record()){
$num=$db1->num_rows();
$i++;
$sMenu.="/"".$db1->Record["sname"]."/",";
if($i==$num){
$sMenu="[".$sMenu."],[";
}
}
// $sMenu1=$sMenu."],[";
}
$sMenu.="]";
$sMenu=str_replace("/",]","/"]",$sMenu);
$sMenu="[".$sMenu."]";
//得到*****************************var sValue
$db->query("select * from news_bclass order by id desc");
while($db->next_record()){
$parentid=$db->Record["id"];
$db1->query("select * from news_sclass where parentid='$parentid' order by parentid desc");
while($db1->next_record()){
$nums=$db1->num_rows();
$j++;
$sValue.="/"".$db1->Record["id"]."/",";
if($j==$nums){
$sValue="[".$sValue."],[";
}
}
}
$sValue.="]";
$sValue=str_replace("/",]","/"]",$sValue);
$sValue="[".$sValue."]";
//echo $fMenu."<br>";
//echo $sMenu."<br>";
//echo $fValue."<br>";
//echo $sValue."<br>";exit;
?>
<div id="tar"></div>
<SCRIPT LANGUAGE="JavaScript" DEFER>
var fMenu = <?php echo $fMenu; ?>;
var fValue = <?php echo $fValue; ?>;
var sMenu = <?php echo $sMenu; ?>;
var sValue = <?php echo $sValue; ?>;
var oWhere = document.all.tar;
var ofMenu = document.createElement("<SELECT name='bigclass'>");
var osMenu = document.createElement("<SELECT name='smallclass'>");
with(oWhere)appendChild(ofMenu),appendChild(osMenu);
createMainOptions();
createSubOptions(0);
ofMenu.onchange = function() {createSubOptions(this.selectedIndex);};
function createMainOptions() {
for(var i=0;i<fMenu.length;i++)ofMenu.options[i] = new Option(fMenu[i],fValue[i]);
}
function createSubOptions(j) {
with(osMenu) {
length=0;
for(var i=0;i<sMenu[j].length;i++)osMenu.options[i] = new Option(sMenu[j][i],sValue[j][i]);
}
}
</SCRIPT>
<?php
/*
調用方法:
將此代碼儲存為檔案ld2.php
例如要在write.php頁面放一個二級聯動,則在write.php頁面
要放置二級聯動的地方加一句require_once("ld2.php");
若write.php要向save.php頁面提交資料則在save.php中使用
$bigclass=$_POST["bigclass"];//取得大類的id值
$smallclass=$_POST["smallclass"];//取得小類的id值
接下來就知道該怎麼做了吧……
附表的結構:
可能有的表的結構和我有些不同,但基本點都一樣,修改程式
中對應欄位和表名就ok
程式寫的有點亂,不過絕對可以放心的使用
歡迎與我交流探討!
---------------------------------------------------------
大類的表news_bclass結構
CREATE TABLE news_bclass (
id int(11) NOT NULL auto_increment,
bname varchar(10) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY id_2 (id),
KEY id (id)
) TYPE=MyISAM;
欄位說明:bname為大類中文名稱
------------------------------
小類的表news_sclass結構
CREATE TABLE news_sclass (
id int(11) NOT NULL auto_increment,
sname varchar(10) NOT NULL default '',
parentid int(10) NOT NULL default '0',
bname varchar(10) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY id_2 (id),
KEY id (id)
) TYPE=MyISAM;
欄位說明:sname為小類中文名稱
parendid為大類中的id值
bname為大類中文名稱
-----------------------------------------------------------
*/
?>
db.inc.php檔案的內容
<?php
class hq_online extends db_sql{
var $Host="localhost";
var $Database="hq";
var $User="root";
var $password="";
}