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