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版的解決方案,說明在此

歡迎推文分享:
Published 21 December 2007 07:42 PM 由 Jeffrey
Filed under: ,
Views: 26,035



意見

# SNP said on 22 December, 2007 06:26 AM

GOOOD POINT !

# eric said on 22 December, 2007 10:02 AM

請教一下Jeffrey ,

為什麼你不喜歡

document.getElementById('<% =TextBox1.ClientID%>').value

這樣的寫法呢? 難道是有什麼地雷嗎?

已經用很多的人

# Jeffrey said on 23 December, 2007 10:04 AM

to eric, 好問題,值得寫一篇Post來說明,所以看這裡:

blog2.darkthread.net/.../why-not-inline-aspx.aspx

# Maxi said on 13 January, 2009 02:19 AM

真是個好方法

RegisterMPGet這種東西要去那裡學?

其實我一本ASP.NET的書都沒看過,書本裡會有嗎?

我都用笨方法

寫接clientID做參數的Javascript function

然後在code behind為control加入OnClick attribute

存入control.ClientId做參數

改天用黑暗大的方法試看看

# 庫洛洛 said on 23 January, 2009 05:33 AM

請問大大, 這個可不可改成VB.Net版的?

用線上C#轉VB.Net

www.developerfusion.com/.../csharp-to-vb

下面都不能用,

Dim lstCph As New List(Of String)()

 Public Shared Sub searchContentPlaceHolder(ByVal ctrl As Control, ByVal lst As List(Of String))

這要改成什麼才能用

# Jeffrey said on 23 January, 2009 07:57 AM

to 庫洛各, 轉換後的程式看起來沒啥問題, 莫非是忘了宣告Imports System.Collections.Generic?

# 庫洛洛 said on 23 January, 2009 09:31 AM

謝謝 Jeffrey

真的是沒加 Imports System.Collections.Generic

真的很謝謝

# Amanda said on 10 December, 2009 11:37 PM

請問,我是巢狀式的masterpage,我在第一個masterpage加入

只建出

<script type="text/javascript">

function afa_mpget(objId) {    

var inp =   document.getElementById(objId);

  if (!inp) inp = document.getElementById("ctl00_ctl00" + objId);  

script>

請問該注意那部份呢?

# gary said on 09 March, 2010 10:09 PM

您好:

我試了你的方法,部份程式碼如下

function CloseDialog(ModalPopupExtenderId)

       {

         ModalPopupExtenderId = afa_mpget(ModalPopupExtenderId);        

         var modalPopupExtender =ModalPopupExtenderId;

         $find(modalPopupExtender).hide();

       }

但不知為什麼就是afa_mpget (ModalPopupExtenderId);

取出來是null,去看了原始檔,字串串的沒有錯,但就是找不到,可以請問我是什麼地方錯了嗎,謝謝您

# Jeffrey said on 10 March, 2010 03:00 PM

to gary, 我建議你看一下網頁的HTML原始碼,確定<script type="text/javascript">function afa_mpget(objId) { ... }</script>有被加進去。接著檢視其中列出的幾條ctl00_* 加上ModalPopupExtenderId是否有吻合你想要的Element ID。

# vincent said on 12 January, 2011 02:43 AM

無法修改 Controls 集合,因為控制項包含程式碼區塊 (例如 <% ... %>)。

描述: 在執行目前 Web 要求的過程中發生未處理的例外情形。請檢閱堆疊追蹤以取得錯誤的詳細資訊,以及在程式碼中產生的位置。

例外詳細資訊: System.Web.HttpException: 無法修改 Controls 集合,因為控制項包含程式碼區塊 (例如 <% ... %>)。

原始程式錯誤:

行 17:         Dim js As New Literal()

行 18:         js.Text = sb.ToString()

行 19:         mp.Page.Header.Controls.AddAt(0, js)

行 20:     End Sub

行 21:     Public Shared Sub searchContentPlaceHolder(ByVal ctrl As Control, ByVal lst As List(Of String))

執行之後產生上面的錯誤,是我那裡弄錯了嗎

# Jeffrey said on 12 January, 2011 05:26 AM

to vincent, 是<head>...</head>中間有參雜<% ... %>的寫法嗎?

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<December 2007>
SunMonTueWedThuFriSat
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

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

文章典藏
其他功能

這個部落格


Syndication