近年來,在協助多家企業進行ASP.NET專案期間,我撰寫ASP.NET應用程式的風格有了相當大的轉變,以往,我總是以元件為出發點,
提供客戶簡單、易用的元件來解決他們的問題,效果雖然不錯,但對於技術能力較為不足的程式設計師來說,使用元件固然不成問題,
但了解元件的內部、進而掌握她們就有些困難了。
因此,我開始使用一種由來已久的技術,那就是HTML Custom Attributes。
活用HTML Custom Attributes
文/黃忠成
起
近年來,在協助多家企業進行ASP.NET專案期間,我撰寫ASP.NET應用程式的風格有了相當大的轉變,以往,我總是以元件為出發點,
提供客戶簡單、易用的元件來解決他們的問題,效果雖然不錯,但對於技術能力較為不足的程式設計師來說,使用元件固然不成問題,
但了解元件的內部、進而掌握她們就有些困難了。
因此,我開始使用一種由來已久的技術,那就是HTML Custom Attributes。
什麼是HTML Custom Attributes
如你所知,在HTML規格中,每個Element都擁有一些Attributes,例如下方的文字輸入框:
<inputname="TextBox1" type="text" id="TextBox1" /> |
其中的type、id、name就是Attributes,HTML解譯器透過解譯這些Attributes,來決定該如何呈現這個HTML Element。很幸運的,
這些Attributes並非固定不變,設計師們可以透過自定Attributes,將一些資訊一併放到HTML Element中,爾後透過Java Script來取得這些值,
進行相對的處理,下面就是這樣一個例子。
HTML |
<inputname="TextBox1" type="text" id="TextBox1" msg="test" onblur="Test(this)" /> |
.js |
function Test(sender) { alert(sender.getAttribute('msg')); } |
那這有什麼用呢?不就是將一個特定資訊綁在一個HTML Element上而已嗎?是的,乍看之下的確如此,但是若善用此手法,
我們可以簡化網頁設計工作,將常用的功能撰寫成JavaScript Library,然後透過辨識Attribute的手法,自動的幫設計師完成該做的工作。
接下來我們就以驗證使用者輸入的編號是否重複為例,明確的指出HTML Custom Attributes如何簡化及讓我們的程式碼更加易於閱讀。
驗證編號
驗證使用者輸入的編號是否重複,是多數網頁都需要的步驟,這常用於驗證帳號、驗證客戶編號等功能上。使用者所輸入的資料必須
送回伺服器端,然後由資料庫查詢輸入資料是否重複,在以往,我們多半會選擇以下幾種方法來實作;
- 使用TextBox的TextChanged事件搭配AutoPostBack。
- 使用UpdatePanel搭配TextChanged事件及AutoPostBack。
- 使用AJAX Service Methods,於TextBox的onblur事件回呼伺服器端的Service Methods或是PageMethods。
這三種方法都可以達到預期的效果,第一種是最簡單、但也是最沒有效率的做法,因為TextChanged加上AutoPostBack後,
會引發網頁的刷新動作,在現在這個AJAX盛行的年代,我想已經不會有人再選用這種方法了。
第二種稍微好些,不會引起明顯的網頁刷新動作,但我想你也明白,刷新動作依舊存在,只要有PostBack,就會有一定量的封包送往後端,
唯一不同的是使用者沒感覺到罷了,所以這種方式雖然比第一種好,但也沒好多少。
第三種是目前最建議的做法,這種方式既不會像方法一般引發明顯的網頁刷新動作,也不會像方法二般送出不必要的網路封包,
缺點是寫起來不太容易,下面是這樣的例子。
WebForm1.aspx |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication5.WebForm1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title></title> <script type="text/javascript" language="javascript"> function CheckSucceededCallback(result, context) { if (!result) { alert(context); } } </script> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True"> </asp:ScriptManager> <div> <asp:TextBox ID="TextBox1" onblur="PageMethods.CheckRecord(this.value,CheckSucceededCallback,null,'編號重複')" runat="server"> </asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" /> </div> </form> </body> </html> |
WebForm1.aspx.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace WebApplication5 { public partial class WebForm1 : System.Web.UI.Page { [System.Web.Services.WebMethod] public static bool CheckRecord(string id) { DataSet1TableAdapters.CustomersTableAdapter adapter = new WebApplication5.DataSet1TableAdapters.CustomersTableAdapter(); return adapter.RecordIsExists(id) == 0; } } } |
此例是透過ASP.NET AJAX的PageMethods搭配JavaScript來進行驗證,效果不錯,缺點是必須撰寫小量的JavaScript程式碼,
不過此法還是有不方便之處,請注意下面的程式碼片段:
onblur="PageMethods.CheckRecord(this.value,CheckSucceededCallback,null,'編號重複')" |
在呼叫CheckRecord時,我們將TextBox的值及要驗證失敗的錯誤訊息傳入,爾後驗證失敗時,由CheckSucceededCallback來秀出錯誤訊息,
此法的缺點是,我們沒有地方可以插入當驗證失敗時,把焦點移回原TextBox的程式碼,所以,改成下面這樣才是正確的。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication5.WebForm1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title></title> <script type="text/javascript" language="javascript"> function CallCheck(sender, errMsg) { PageMethods.CheckRecord(sender.value,CheckSucceededCallback, null,{element:sender,msg:errMsg}); } function CheckSucceededCallback(result, context) { if (!result) { alert(context.msg); context.element.focus(); } } </script> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True"> </asp:ScriptManager> <div> <asp:TextBox ID="TextBox1" onblur="CallCheck(this,'編號重複')" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" /> </div> </form> </body> </html> |
在這個例子中,我們用了一個Wrapper Function : CallCheck,並於內以JavaScript Object的方式,將驗證的TextBox及錯誤訊息傳入,
雖然多了一道手續,但也不失為是一個好方法。
更進階的手法,就是透過HTML Custom Attribute來描述錯誤訊息,這種手法會更簡潔,如下所示:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication5.WebForm1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head id="Head1" runat="server"> <title></title> <script type="text/javascript" language="javascript"> function CheckSucceededCallback(result, context) { if (!result) { alert(context.getAttribute("errMsg")); context.focus(); } } </script> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True"> </asp:ScriptManager> <div> <asp:TextBox ID="TextBox1" onblur="PageMethods.CheckRecord(this.value,CheckSucceededCallback,null,this)" errMsg="編號重複" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" /> </div> </form> </body> </html> |
你可以明顯的發覺,我們利用了HTML Custom Attributes來描述額外的訊息,於函式中透過getAttribute來取得該訊息,這個例子比起前面幾個來說,
更加的優美,唯一美中不足的是,由於缺少了Wrapper Function,所以PageMethods的呼叫顯得略為冗長。不過由此手法中,我們可以將
Wrapper Function提升為通用化函式,讓程式看起來更簡潔易懂。
CheckId.js |
function CallCheckFunction(sender) { if (sender.getAttribute("checkFunction") != null) { eval("PageMethods." + sender.getAttribute("checkFunction") + "('" + sender.value + "',CheckSucceededCallback,null,sender);"); } } function CheckSucceededCallback(result,context) { if (!result) { if (context.getAttribute("errorMsg") != null) alert(context.getAttribute("errorMsg")); else alert("error"); context.focus(); } } |
Default.aspx |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication5._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title></title> <script type="text/javascript" language="javascript" src="CheckID.js"></script> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True"> </asp:ScriptManager> <div> <asp:TextBox ID="TextBox1" checkFunction="CheckRecord" onblur="CallCheckFunction(this)" errorMsg="編號重覆" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" /> </div> </form> </body> </html> |
這個程式的執行結果與前例完全相同,不同的是可讀性變高了,尤其是在多個驗證區塊情況下更為明顯。
Default.aspx.cs |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.Script.Services; namespace WebApplication5 { public partial class _Default : System.Web.UI.Page { [System.Web.Services.WebMethod] public static bool CheckRecord(string id) { DataSet1TableAdapters.CustomersTableAdapter adapter = new WebApplication5.DataSet1TableAdapters.CustomersTableAdapter(); return adapter.RecordIsExists(id) == 0; } [System.Web.Services.WebMethod] public static bool CheckEmployeeRecord(string id) { DataSet1TableAdapters.EmployeesTableAdapter adapter = new WebApplication5.DataSet1TableAdapters.EmployeesTableAdapter(); return adapter.RecordExists(int.Parse(id)) == 0; } protected void Page_Load(object sender, EventArgs e) { } } } |
Default.aspx |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication5._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title></title> <script type="text/javascript" language="javascript" src="CheckID.js"></script> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True"> </asp:ScriptManager> <div> <asp:TextBox ID="TextBox1" checkFunction="CheckRecord" onblur="CallCheckFunction(this)" errorMsg="編號重覆" runat="server"></asp:TextBox> <asp:TextBox ID="TextBox2" checkFunction="CheckEmployeeRecord" onblur="CallCheckFunction(this)" errorMsg="員工編號重覆" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" /> </div> </form> </body> </html> |
此例尚不完美,因為員工編號是數字,若使用者輸入空白,將會引發例外訊息,改善的方式很簡單,我們再次透過
HTML Custom Attributes,告知CallCheckFunction先幫我們判斷值。
CheckID.js |
var supsendCheck = false; function CallCheckFunction(sender) { if (supsendCheck) { return; } if (sender.getAttribute("valueType") != null && sender.getAttribute("valueType") == "int") { var val = parseInt(sender.value); if(isNaN(val)) { alert('格式錯誤!'); sender.focus(); return; } sender.value = val; } if (sender.getAttribute("checkFunction") != null) { eval("PageMethods." + sender.getAttribute("checkFunction") + "('" + sender.value + "',CheckSucceededCallback,null,sender);"); } } function CheckSucceededCallback(result,context) { if (!result) { supsendCheck = true; if (context.getAttribute("errorMsg") != null) alert(context.getAttribute("errorMsg")); else alert("error"); context.focus(); } setTimeout("ResetSupsendCheck()", 0); } function ResetSupsendCheck() { supsendCheck = false; } |
Default.aspx |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication5._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title></title> <script type="text/javascript" language="javascript" src="CheckID.js"></script> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True"> </asp:ScriptManager> <div> <asp:TextBox ID="TextBox1" checkFunction="CheckRecord" onblur="CallCheckFunction(this)" errorMsg="編號重覆" runat="server"></asp:TextBox> <asp:TextBox ID="TextBox2" checkFunction="CheckEmployeeRecord" onblur="CallCheckFunction(this)" errorMsg="員工編號重覆" valueType="int" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button" /> </div> </form> </body> </html> |
當使用者輸入重複的客戶編號時的畫面如下:
當輸入錯誤格式的員工編號畫面如下:
當輸入重複員工編號畫面如下:
後記
當然,這些例子仍有許多改善空間,本文的目的僅是告知各位讀者,HTML Custom Attrbiutes有許多可利用的空間,透過她們再搭配上掃描整個HTML Tree,
可以讓設計師設幾個Attribute後,就達到意想不到的效果。唯一需注意的是,取Attribute名稱時要特別小心,別與原來的名稱相同,也得避免未來的HTML規格
與你的Attribute重複,當然!HTML已經停在5.0草案很久就是了。
範例下載