這裡所謂的複雜表單,是指表單包含多種不同的輸入類型,例如下拉列錶框、單行文字、多行文字、數值等。在經常需要更換這類表單的場合,需要有表單的動態產生程式。本文介紹的正是這樣一個系統,它以資料庫保存表單定義數據,並利用ASP腳本動態產生表單HTML程式碼以及驗證表單輸入的腳本。
一、定義資料庫表結構
在Web上經常可以看到「每週調查」之類的表單,這就是一種需要經常更新的表單。如果有一個動態產生表單及其驗證腳本的程序,可以大幅減少製作這些表單的工作。
在本文的動態表單產生與驗證範例中,我們使用一個Access資料庫來儲存有關表單的定義訊息,同時為簡單計,使用者在表單中輸入的資料也儲存到同一資料庫。定義表單需要兩個表:第一個表(Definitons)用於表單輸入域的定義,第二個表(Lists)保存各個輸入域的附加資訊,例如選擇列表的選擇項目。
表Definitons包含以下欄位:
FieldName —— 賦予表單輸入域的變數名字
Label —— 即文字標籤,顯示在輸入域前面的提示性文字
Type —— 單一字符,該字符表示表單輸入域的形式和輸入值的類型具體如下:
(t) 文字輸入框,即< INPUT TYPE="TEXT" >。
(n) 文字輸入框,但要求輸入數字值。
(m) 備註型內容,用於註釋或其他大量文字的輸入,它是一個多行文字編輯框。
(b) 要求輸入「是」或「否」。本實作中將以複選框來取得這種輸入,複選框的文字標籤為「是」。如果使用者選取它,則傳回值是「on」。
(r) 單選按鈕。
(l) 下拉列錶框。
Min —— 僅對數字型輸入值有效,在這裡給出最小值。在本例中有一個「Age」(年齡)數字型輸入框,它的最小值設定為1。
Max —— 此欄位的值與輸入域形式有關。對於數位型輸入框,它表示的是允許的最大值。例如“Age”的Max值為100。對於文字輸入框,Max表示允許的最多字元數。對於多行文字編輯框,Max表示可見區域的文字行數。
Required —— 表示是否必須輸入。這種類型的值如果沒有輸入,則輸入驗證程序將報告錯誤。在表單中,必須輸入的值以星號標記,並以腳註的形式提示使用者該類別值必須輸入。
本文的範例表單是一個ASP程式設計師調查表,在Definitons表中該表單的定義主要如下:
FieldName Label Type Min Max Required
Name 姓名文字(t) - 50 否
Age 年齡數字(n) 1 100 否
Sex 性別單選按鈕(r) - - 是
E-mail 郵件地址文字(t) - - 是
Language 程式語言下拉列錶框(l) - - 否
表Lists用於保存輸入域定義的一些附加信息,本例有「Sex 」和「Languages」兩個輸入值要用到它。表Lists非常簡單,只包含以下三個欄位:
FieldName —— 目前記錄屬於哪個表單輸入域
Value —— 選擇項目的值
Label —— 使用者所看到的選擇項目的提示文字
輸入域「Sex」只能從兩個值選取:「男」或「女」。 「Language」列出了幾種可應用於ASP環境的程式語言,包括:VBScript,JavaScript,C,Perl和「其他」。
第三個表「Records」保存使用者提交的內容,它也包含三個字段,每個記錄對應用戶的一次提交:
Record —— 備註類型,以查詢字符串形式保存的用戶輸入。
Created —— 使用者提交該表單的日期和時間。 RemoteIP —— 表單提交者的IP位址。
在實際應用中可能要收集更多有關使用者的信息,為簡單計,本例只記錄提交時間和使用者IP位址這兩個附加資訊。
二、準備工作
在完成上述資料結構和表單的定義之後,接下來就可以編寫腳本。腳本的任務是產生表單以及處理使用者提交的表單。
無論是表單的產生或處理,以下三個過程(任務)都是必不可少的:第一個是確定驗證類型,在產生表單時驗證類型值透過查詢字串取得,在處理表單時從表單隱藏域讀取。程式支援的表單驗證方式共有以下四種類型:不進行驗證,客戶端JavaScript驗證,伺服器端ASP腳本驗證,客戶端和伺服器端都進行驗證(代號分別為0到3)。如果沒有在查詢字串中指定合法的驗證方式,則預設第四種驗證方式。這種驗證處理方式讓我們可以靈活地應用這個表單產生、處理系統,而當客戶端禁止使用JavaScript驗證時就可以只在伺服器端執行驗證過程。以下是確定驗證類型的程式碼:
檢查驗證類型
以下是引用片段:
iValType = Request.QueryString("val")
If IsNumeric(iValType) = False Then iValType = 3
If iValType > 3 Or iValType < 0 Then iValType =3
第二個任務是開啟資料庫連接,建立兩個記錄集對象:RS對象,這是本程式中的主要記錄集對象,用來操作Definitions表;RSList對象,主要用於從Lists表讀取資料。範例程式提供兩種資料庫連線方法:使用ODBC DSN或不使用ODBC DSN(使用DSN時需要先建立名為Dynamic的DSN,使用DSN連接資料庫的程式碼已經註解掉)。
第三個任務是在產生(或處理)表單腳本的前面(和後面)輸出一些靜態的HTML程式碼,例如< HEAD >< /HEAD >,以及在腳本運行結束的時候釋放RS、RSList等物件所佔用的資源。
除了完成上述任務的程式碼外,範例應用程式中其餘ASP腳本可能產生的頁面有兩種類型:提問表單(見上圖)以及表單提交後出現的結果頁面(後者同時也負責使用者提交結果的記錄) 。要確定究竟要執行哪一部分腳本,最簡單的方法就是檢查是否已經提交表單:如是,則處理表單;否則產生表單。
是產生表單還是處理表單?
以下是引用片段:
If Len(Request.Form) = 0 Then
'產生表單...略...
Else
'處理表單...略...
End If
三、動態產生表單
產生表單時,程式依照Definitons表中的各個輸入域定義記錄,依序產生對應的表單HTML程式碼和JavaScript程式碼。 HTML程式碼中首先要產生的是文字標籤:
以下是引用片段:
sHTML = sHTML & vbTab & "< TR >" & vbCrLf & vbTab & vbTab
sHTML = sHTML & "< TD VALIGN=" & Chr(34) & "TOP" & Chr(34)
sHTML = sHTML & " >" & vbCrLf & vbTab & vbTab & vbTab
sHTML = sHTML & "< B >" & RS.Fields("Label")
然後程式檢查目前輸入域是否必須輸入。如果必須,則在標籤文字之後加一個星號(表示該值必須輸入),同時對於必須輸入的值,也要產生對應的JavaScript程式碼來驗證它。對於單選按鈕或選擇列表,需進一步檢查用戶確實選擇了某個選項;對於所有其他輸入類型,只要檢查輸入值不為空即可。
緊接著文字標籤的是表單的輸入元素,這些元素的HTML程式碼根據Definitions表中指定的類型和屬性產生。再接下來就是根據輸入值要求產生執行客戶端驗證任務的JavaScript程式碼。對於本例,只有數字型的值需要進一步檢查以確保使用者的輸入確實是數字,而且數字值在許可證的最大值和最小值之間。產生上述程式碼之後,就可以結束一個表格行(也就是一個輸入域)繼續處理Definitions表的下一個記錄。一旦所有的資料庫記錄處理完畢,下一步就可以加入「提交」按鈕和「清除」按鈕的HTML程式碼。如果換個角度來看,程式在這裡的任務就是根據資料庫記錄產生各個輸入域,每個輸入域佔用一個表格行,每個表格行二個單元:第一個單元用來顯示文字標籤,第二個單元顯示輸入元素本身(程式碼見dForm.asp)。
上述過程結束之後,表單的HTML程式碼和驗證用JavaScript函數分別儲存到了變數sHTML和sJavaScript中。在把這些內容寫入頁面之前,程式檢查客戶端是否要求執行JavaScript驗證,如果不要求執行這類驗證,則清除sJavaScript變數:
If iValType = 0 Or iValType = 2 Then sJavaScript = ""
在輸出BODY標記之後,程式輸出如下JavaScript函數:
以下是引用片段:
< SCRIPT LANGUAGE="JavaScript" >
< !--
function validate(TheForm){
//客戶端表單驗證
< %=sJavaScript% >
return true;
}
function CheckRadio(objRadio){
//單選按鈕中是否有某個值被選中
for(var n = 0; n < objRadio.length; n++){
if(objRadio[n].checked){
return true;
}
}
return false;
}
function CheckList(objList){
//是否已經在選擇清單中選擇了某個值
for(var n = 1; n < objList.length; n++){
if(objList.options[n].selected){
return true;
}
}
return false;
}
//-- >
< /Script >
如果用戶端不需要JavaScript驗證,則validate函數只剩下一個「return true」語句。上面程式碼中的後面兩個靜態JavaScript函數(CheckRadio和CheckList)用於驗證單選按鈕和下拉列錶框,當這兩種輸入域需要驗證時validate函數將調用它們。
現在可以開始將表單寫入頁面:
< FORM ACTION="./dform.asp" METHOD="POST" NAME="MyForm" onSubmit="return validate(this)" >
在這裡,只有當validate函數傳回true時才執行表單提交操作。因此當客戶端JavaScript驗證功能關閉時,validate函數將自動傳回true。
接下來要加入的是名為val的隱藏域。如前所述,該值指示表單的驗證模式。
< INPUT TYPE="HIDDEN" NAME="val" VALUE="< %=iValType% >" >
當使用者提交表單時,處理腳本將根據該值決定是否執行伺服器端驗證。
然後輸出的是表格標記以及表格標題。標題保存在變數sTitleLabel中,該值在腳本開始執行時初始化:
以下是引用片段:
< TABLE BORDER="0" >
< TR >
< TD COLSPAN="2" ALIGN="CENTER" >
< H2 >< %=sTitleLable% >< /H2 >
< /TD >
< /TR >
作為改進措施,可以在表Definitions、Lists和Records中增加一個欄位FormID。 FormID唯一識別一個表單,這樣程式就可以同時定義多個表單、儲存多個表單的使用者回應結果。至於上面的sTitleLabel,我們可以用另外一個表格(例如Forms)來保存。
緊接著表格標記和表格標題,程式輸出的是HTML表單以及「提交」、「清除」按鈕的程式碼。在此之後,程式檢查sHTML字串中是否包含“*”,如包含的話說明表單中存在必須輸入的內容,此時就輸出一個腳註以說明該星號的含義。
以下是引用片段:
< %=sHTML% >
< TR >
< TD COLSPAN="2" ALIGN="CENTER" >
< INPUT TYPE="SUBMIT" VALUE="提交表單" > < INPUT TYPE="reset" VALUE="清除" >
< /TD >
< %
'是否存在必需輸入的表單域,如存在,則輸出表單腳註解釋'*'的含義
If InStr(sHTML,"*") Then
% >
< /TR >
< TD COLSPAN="2" ALIGN="CENTER" >
< FONT SIZE="2" >注意:有星號標記的值必需輸入。 < /FONT >
< /TD >
< /TR >
< %
End If
% >
< /TABLE >
< /FORM >
至此為止,表單的產生任務已經完成。
四、處理提交結果
ASP腳本剩下的任務是伺服器端的表單處理,包括驗證、將結果儲存到資料庫以及「提交成功/失敗」頁面的顯示。在這部分錶單驗證程式碼中用到了一個字串變數sBadForm,程式用它來保存錯誤訊息。如果在驗證過程結束時sBadForm為空,表示使用者提交的表單是合法的;否則,拒絕該表單的提交並把sBadForm傳回瀏覽器。
不管表單採用哪一種驗證模式,檢查HTTP_REFERER都是一種好的習慣。這種檢查可以防止腳本被盜用。要檢查某個POST是否來自本網站的頁面或腳本,只需比較兩個伺服器變數即可:
以下是引用片段:
If InStr(Request.ServerVariables("HTTP_REFERER"), _
Request.ServerVariables("HTTP_HOST")) = 0 Then
sBadForm = "< LI >表單提交自不正確的位置。" & vbCrlf
End If
如果表單的隱藏網域指示必須進行伺服器端驗證,則程式遍歷表單定義資料庫記錄作對應的檢查,其流程與表單的產生非常相似,只不過此時程式是驗證表單,且將輸入值非法資訊加入到sBadForm中去而已。具體程式碼請見dForm.asp。
程式最後檢查sBadForm是否為空。如不為空,則拒絕表單提交並將sBadForm寫入瀏覽器。如果sBadForm為空,則在Records表增加一個記錄保存表單資料。在儲存表單內容之前需要刪除隱藏域val,這個隱藏域總是表單的第一個輸入域:
以下是引用片段:
If Len(sBadForm) = 0 Then
RS.Open "Records", DB, 3, 2, &H0002
RS.AddNew
RS.Fields("Record") = Mid(Request.Form, InStr(Request.Form, "&") + 1)
RS.Fields("Created") = Now()
RS.Fields("RemoteIP") = Request.ServerVariables("REMOTE_ADDR")
RS.Update
Response.Write("< H1 >謝謝.< /H1 >")
RS.Close
Else
Response.Write("< H1 >表單提交失敗。< /H1 >")
Response.Write(vbCrLf & sBadForm)
End If
End If
這就是伺服器端表單處理的全部過程。根據是否存在已經提交的表單,我們可以把前面產生表單的程式碼和這裡表單處理的程式碼用If語句封裝,使得這兩部分腳本共用部分公用程式碼,例如HTML文件的頭、資料庫物件的建立和資源釋放等。
總地看來,dForm.asp只具備了動態表單產生、驗證所需的核心功能,忽略了許多細節問題的處理。例如前面已經提到的多表單問題:增加一個表格管理多個表單,使得腳本具備管理、產生、處理指定表單的能力。另外一個明顯的缺乏是表單定義資料的增加、刪除和更新功能,以及用戶提交結果資料的訪問,這類功能可以在一個獨立的程式中實現,而且在大多數情況下可以做成傳統的應用程式(非B/S結構的應用)。最後,dForm.asp支援的輸入域類型也有限,在實務上可能會有其他的表單輸入要求,例如專用的e-mail位址輸入框等。然而,對於那些經常想要更新表單的網站來說,本文所討論的表單動態產生、動態驗證功能確實是非常有用的。