Friday, December 21, 2007 - 文章

KB-MasterPage ClientID Issue

[Abstract]

When using ASP.NET masterpage, the ClientID of webcontrol inside ContentPlaceHolder will get container's ClientID as prefix, like 'ctl00_ContentPlaceHolder1_TextBox1' and this become a big trouble while writing Javascript client-side code. 

Many people suggest using "document.getElementById('<% =TextBox1.ClientID%>')" to solve the problem, but I really don't like embed server-side code in ASPX file, so here's my solution, a flexible Javascript "afa_mpget()" to replace document.getElementById() when using masterpage.

猜謎時間又來了! 想看看,以下的Code有什麼地方有問題?

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" 
Inherits="_Default" MasterPageFile="~/MasterPage.master" %>
<asp:Content runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
    <asp:TextBox ID="TextBox1" runat="server" Text="Hello"></asp:TextBox>
    <script type="text/javascript">
    alert(document.getElementById("TextBox1").value);
    </script>
</asp:Content>

答案是Javascript alert時會找不到物件,為什麼? 在網頁上View Source,答案馬上揭曉,當網頁套用MasterPage時,TextBox1產生的<INPUT>名稱會變成"ctl00_ContentPlaceHolder1_TextBox1"... 天哪! 由Frameset走向MasterPage,簡單的事變複雜了,要為此回頭嗎?

Google一下,發現大家最常建議最的解法是改寫成
alert(document.getElementById('<% =TextBox1.ClientID%>').value);

不過,我個人實在不喜歡這種ASP時代的義大利麵式寫法,所以就自力救濟了一番。首先我寫了一個MasterPageHelper.cs:

public class MasterPageHelper
{
    public MasterPageHelper()
    {
    }
 
    public static void RegisterMPGet(MasterPage mp)
    {
        List<string> lstCph = new List<string>();
        lstCph.Add(mp.ClientID);
        searchContentPlaceHolder(mp.Page.Form, lstCph);
        StringBuilder sb = new StringBuilder();
        sb.Append(@"
<script type=""text/javascript"">
function afa_mpget(objId) {
    var inp = document.getElementById(objId);
");
        foreach (string cphId in lstCph)
        {
            sb.AppendFormat(
"   if (!inp) inp = document.getElementById(\"{0}_\" + objId);\n",
                cphId);
        }
        sb.Append("return inp;\n}\n</script>");
        Literal js = new Literal();
        js.Text = sb.ToString();
        mp.Page.Form[請看下方更新]Header.Controls.AddAt(0, js);
    }
 
    public static void searchContentPlaceHolder(Control ctrl, 
List<string> lst)
    {
        if (ctrl is ContentPlaceHolder)
            lst.Add(ctrl.ClientID);
        else if (ctrl.HasControls())
            foreach (Control c in ctrl.Controls)
                searchContentPlaceHolder(c, lst);
    }
}

在MasterPage的Page_Load事件中,呼叫MasterPageHelper.RegisterMPGet(this),就會在網頁中加入一個用元件名稱自動尋找各ConentPlaceHolder下元件的彈性函數--afa_mpget(fieldName):

<script type="text/javascript">
function afa_mpget(objId) {
    var inp = document.getElementById(objId);
   if (!inp) inp = document.getElementById("ctl00_" + objId);
   if (!inp) inp = document.getElementById("ctl00_ContentPlaceHolder1_" + objId);
   if (!inp) inp = document.getElementById("ctl00_ContentPlaceHolder2_" + objId);
return inp;
}
</script>

接著,我們用afa_mpget取代document.getElementById,就又回到以前幸福快樂的生活囉!

<asp:Content runat="server" ContentPlaceHolderID="ContentPlaceHolder1">
    <asp:TextBox ID="TextBox1" runat="server" Text="Hello"></asp:TextBox>
    <script type="text/javascript">
    </script>
</asp:Content>
<asp:Content runat="server" ContentPlaceHolderID="ContentPlaceHolder2">
    <asp:TextBox ID="TextBox2" runat="server" Text="World"></asp:TextBox>
    <script type="text/javascript">
    alert(afa_mpget("TextBox1").value);
    alert(afa_mpget("TextBox2").value);
    </script>
</asp:Content>

Update 2008-01-03
Page.Form.Controls.AddAt會破壞ViewState,故改成Header.Controls.Add,說明在此

Update 2008-01-19
強化版搜尋範圍擴及UserControl,說明在此

Update 2009-01-13
另有jQuery版的解決方案,說明在此

搜尋

Go

<December 2007>
SunMonTueWedThuFriSat
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication