KB-MasterPage ClientID Issue
12 |
[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版的解決方案,說明在此。
Comments
# by SNP
GOOOD POINT !
# by eric
請教一下Jeffrey , 為什麼你不喜歡 document.getElementById('<% =TextBox1.ClientID%>').value 這樣的寫法呢? 難道是有什麼地雷嗎? 已經用很多的人
# by Jeffrey
to eric, 好問題,值得寫一篇Post來說明,所以看這裡: http://blog.darkthread.net/blogs/darkthreadtw/archive/2007/12/24/why-not-inline-aspx.aspx
# by Maxi
真是個好方法 RegisterMPGet這種東西要去那裡學? 其實我一本ASP.NET的書都沒看過,書本裡會有嗎? 我都用笨方法 寫接clientID做參數的Javascript function 然後在code behind為control加入OnClick attribute 存入control.ClientId做參數 改天用黑暗大的方法試看看
# by 庫洛洛
請問大大, 這個可不可改成VB.Net版的? 用線上C#轉VB.Net http://www.developerfusion.com/tools/convert/csharp-to-vb/ 下面都不能用, Dim lstCph As New List(Of String)() Public Shared Sub searchContentPlaceHolder(ByVal ctrl As Control, ByVal lst As List(Of String)) 這要改成什麼才能用
# by Jeffrey
to 庫洛各, 轉換後的程式看起來沒啥問題, 莫非是忘了宣告Imports System.Collections.Generic?
# by 庫洛洛
謝謝 Jeffrey 真的是沒加 Imports System.Collections.Generic 真的很謝謝
# by Amanda
請問,我是巢狀式的masterpage,我在第一個masterpage加入 只建出 <script type="text/javascript"> function afa_mpget(objId) { var inp = document.getElementById(objId); if (!inp) inp = document.getElementById("ctl00_ctl00" + objId); script> 請問該注意那部份呢?
# by gary
您好: 我試了你的方法,部份程式碼如下 function CloseDialog(ModalPopupExtenderId) { ModalPopupExtenderId = afa_mpget(ModalPopupExtenderId); var modalPopupExtender =ModalPopupExtenderId; $find(modalPopupExtender).hide(); } 但不知為什麼就是afa_mpget (ModalPopupExtenderId); 取出來是null,去看了原始檔,字串串的沒有錯,但就是找不到,可以請問我是什麼地方錯了嗎,謝謝您
# by Jeffrey
to gary, 我建議你看一下網頁的HTML原始碼,確定<script type="text/javascript">function afa_mpget(objId) { ... }</script>有被加進去。接著檢視其中列出的幾條ctl00_* 加上ModalPopupExtenderId是否有吻合你想要的Element ID。
# by vincent
無法修改 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)) 執行之後產生上面的錯誤,是我那裡弄錯了嗎
# by Jeffrey
to vincent, 是<head>...</head>中間有參雜<% ... %>的寫法嗎?