活用HTML Custom Attributes

近年來,在協助多家企業進行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" />
 
其中的typeidname就是AttributesHTML解譯器透過解譯這些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如何簡化及讓我們的程式碼更加易於閱讀。
 
 
 
驗證編號
 
 
     驗證使用者輸入的編號是否重複,是多數網頁都需要的步驟,這常用於驗證帳號、驗證客戶編號等功能上。使用者所輸入的資料必須
送回伺服器端,然後由資料庫查詢輸入資料是否重複,在以往,我們多半會選擇以下幾種方法來實作;
 
  •  使用TextBoxTextChanged事件搭配AutoPostBack
  •  使用UpdatePanel搭配TextChanged事件及AutoPostBack
  •  使用AJAX Service Methods,於TextBoxonblur事件回呼伺服器端的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 AJAXPageMethods搭配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草案很久就是了。
 
 
範例下載