Try this.

Create a webform applied with masterpage and put an <asp:TextBox ID="TextBox1> into ContentPlaceHolder, then write some javascript to use jQuery $("#TextBox1").val() to set its value.

<%@ Page Title="" Language="C#" MasterPageFile="~/jQueryClientId/MyMaster.master" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
    <script src="../js/jquery-1.2.6.js" type="text/javascript"></script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    <script type="text/javascript">
        $("#TextBox1").val("I did it?");
    </script>
</asp:Content>

Test the web form, do you see TextBox1's value chage? Nothing happened! Why?

You can find the answer easily by viewing the source of web page.  TextBox1 is rendered as <input name="ctl00$ContentPlaceHolder1$TextBox1" type="text" id="ctl00_ContentPlaceHolder1_TextBox1" />, the id becomes ctl00_ContentPlaceHolder1_TextBox1, but not TextBox1, so you can't use $("#TextBox1") to find it.  When an web control is put inside containers like user control, ContentPlaceHolder, the client id will be added with prefix of its container.

Some people suggest using $("#<% =TextBox1.ClientId %>") to solve this problem, but the "spaghetti" way  make my code lousy, especially being concise is the important reason why I choose jQuery.

My idea is to assign a CSS class related with the webcontrol id, then we can use class name to find it, for example: $("._TextBox1").  I use the first underscore "_" in class name to avoid collision with normal CSS class names. (There is not too many choices, only a few characters can be used as identity according to CSS specification.)

To provide developers a familiar way to select these elements, there is another syntax, $$("##TextBox1"), a wrapper which converts the selector to "._TextBox1" and return $("._TextBox1").

This concept is simple and here is the sample.

<%@ Master Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        jQueryClientIdEnhancement.RegisterExtScript(this.Page);
    }
 
    //You can move this class to App_Code to be used by all MasterPages
    public class jQueryClientIdEnhancement
    {
        public static void RegisterExtScript(Page page)
        {
            Dictionary<string, string> dct = new Dictionary<string, string>();
            //explore all containers to find all visible webcontrols
            searchContentPlaceHolder(page.Form, dct);
            StringBuilder sb = new StringBuilder();
            foreach (string key in dct.Keys)
            {
                if (sb.Length > 0) sb.Append(",");
                //key = ClientId, value = Id
                sb.AppendFormat("{0}:\"{1}\"", key, dct[key]);
            }
            //build the hashtable 
            string script = @"window.aspNetWebControls = {" + sb.ToString() + "};\n";
            //assign the assistant class name to each web control's HTML element
            script += @"
if (typeof(jQuery) == 'function' && window.aspNetWebControls) {
    var c = window.aspNetWebControls;
    for (var clientId in c) 
        $('#' + clientId).addClass('_' + c[clientId]);
    var pattern = /##(\w+)/g;
    window.$$ = function( selector, context ) {
        selector = selector.replace(pattern, '._$1');
        return jQuery(selector, context);                        
    }                        
}            
";
            //put the script at the end of form to make sure every webcontrol
            //element is declared
            page.ClientScript.RegisterStartupScript(page.GetType(),
                "jQueryClientIdEnhancement",
                script, true);
        }
        private static void searchContentPlaceHolder(Control ctrl, 
            Dictionary<string, string> dct)
        {
            if (ctrl.HasControls())
                foreach (Control c in ctrl.Controls)
                    searchContentPlaceHolder(c, dct);
            if (ctrl.Visible)
                dct.Add(ctrl.ClientID, ctrl.ID);
        }
    }        
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <asp:ContentPlaceHolder id="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
    </asp:ContentPlaceHolder>
    </form>
</body>
</html>

OK, now we can use $$("##WebControlId") or $("._WebControlId") to select the elements, for example:

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    <input type="text" id="TextBox2" />
    <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox>    
    <script type="text/javascript">
        $(function() {
            $$("##TextBox1,#TextBox2").val("I did it!");
            $("._TextBox3").val("Done");
        });
    </script>
</asp:Content>

I hope this solution can make elements selection easier in MasterPage scenario.  If you have any idea or feedback, please leave your comment below.

【中文摘要】

關於MasterPage裡,ClientId會被加料的問題,之前已經探討過。當時提出了一個新函數afa_mpget()來取代ASP.NET AJAX的$get()或document.getElementById(),大家都知道我後來迷戀jQuery到無法自拔,因此讓jQuery Selector克服ClientId加料的問題自然就變成我的某要之急。

以上是我提出的解決方案,用$("._TextBox1")或$$("##TextBox1")的方法就可以選取被埋在MasterPage子網頁或UserControl中的WebControl,希望用起來夠方便。

歡迎大家試用,有意見的話請再留言。


Comments

# by wangaguo

請問您的blog的code 用firefox3剪下來後,貼到Notepad都會相隔一行。 用IE7則會串成一行。 很不好處理。

# by wangaguo

一直知道套了master page後, id就會變不是自己取的名字, 就很討厭. 剛才試用了一下, 很不錯用喔. 以上的程式還會用到 System.Text using System.Text; 待會要繼續寫javascript, 有什麼問題再回頭來報告.

# by wangaguo

剛才測試可用後, 就實際要處理工作了, 但要處理的對像是一組兩個的radio button. id=rdoReAllocateMethod1, name=ReAllocateMethod id=rdoReAllocateMethod2, name=ReAllocateMethod 拿第一個$("._rdoReAllocateMethod1") 這樣可以沒問題. 但要同時替兩個name相同的加上click事件, 好像以上程式就沒處理name的部份了是嗎? 所以直接用$(":radio[name*='ReAllocateMethod']")好像容易些. 那麼$("*[id$='_rdoReAllocateMethod1']")取第一個, 好像也沒麻煩多少. 但不知執行效率上有沒有差呢? ps.一直對masterpage使id名字變長傷腦筋, 不過忘了以前還沒有用jQuery.

# by Maxi

smart! $$怎樣用? 自己定義嗎?

# by Jeffrey

to Maxi, $$是 ... window.$$ = function( selector, context ) { ... 這段程式做的手腳。

# by Tim

黑大..您好 近日在使用Jquery AutoComplete功能時,發現一個問題 如果web page在沒有使用MasterPage時 AutoComplete出現的內容,會有捲軸.... 但同樣一段code,放到Content Page,AutoComplete的效果仍然有,但捲軸卻不會出現 我試著加上您最上方那段code,測了一下,捲軸仍舊不會出現 呼叫如下 $(function() { $$("##txtAppWorkID").autocomplete("GetUser.aspx", { delay: 5, minChars: 5, matchSubset: 1, matchContains: 1, cacheLength: 0, onItemSelect: selectItem, onFindValue: findValue, formatItem: formatItem, matchContains: false, autoFill: true, max: 15 } ); }); 不知是什麼問題,或是您有沒有什麼建議..非常感謝

# by Jeffrey

to Tim, AutoComplete的效果仍然有,但捲軸卻不會出現<--這個看不太懂,是指一口氣列出出現全部提示項目,而非固定大小可以捲動? 感覺上跟CSS有關。

# by Tim

黑大..謝謝回覆.. 抱歉沒講的很清楚 "AutoComplete的效果仍然有,但捲軸卻不會出現"----> 這句話是指,當我在文字框輸入T,會自動帶出Tim,Tom,Teresa,Tina等15筆資料(我上限設15筆) 就像在google,yahoo等搜尋引擎輸入關鍵字,會自動以like方式帶出相關詞 但顯示這15筆資料選單會拖很長,不會出現捲軸 而同樣的程式,放在一支不含MasterPage的html網頁時 選單就不會拖很長,捲軸會出現 另外今天有多測一些情況,把程式發佈到server上後 1. A同事(IE6),單html-->有捲軸,含有MasterPage的 Content Page--->也有捲軸 2. B同事(IE7),單html-->有捲軸,含有MasterPage的 Content Page--->沒捲軸 3. C同事(IE8),單html-->有捲軸,含有MasterPage的 Content Page--->沒捲軸 所以現在有點搞不情楚到底是IE的問題,還是MasterPage 的影響...好奇怪....

# by gino.lo

最近剛玩 ASP.net ... 然後中招 以前習慣用 asp + jquery .. @@" 一樣 Master page 果然 ID 問題很大.. 因為我是用 vb 寫的. 所以... 資料真少 XD 剛用了一個很一個很笨的方法. 給你參考看看. 不同於你的方法,直接client 解決. 一支javascript 放到 js 檔或是 master page 內 <script language="javascript" type="text/javascript"> function $$(strId, strObj) { return $(strObj + "[" + "id$=_" + strId + "]"); } </script> 控制項 (page 或 master page 都可) <asp:TextBox ID="TextBox1" runat="server" Text=""></asp:TextBox> <a href="#" onclick="$$('TextBox1','input').val('測試')">測試</a> 這樣暫時能解決 jquery 基本的問題. 其他的有試出問題再跟你討論請教一下~~

# by FrankWu

你好 我用的時候有個問題 一樣也在 Page_Load 引用 jQueryClientIdEnhancement.RegisterExtScript(this.Page); 接著在畫面上拉一個GridView和Button 然後執行,一開始畫面Load完,沒問題 但當我按下按鈕做 PostBack,會發現GridView的HTML程式碼有問題 他裡面的所有標籤都被加上ID,包含<table>、<tr>、<td> 而且<table>標籤會有兩個ID,一個當初GridView的ID,另一個是被加上去的 請問,這個Bug有解嗎? 同一標籤兩個ID不合常理

# by Jeffrey

to FrankWu, RegisterExtScript並沒有從Server端去更動Control,我也挺好奇重複ID是誰加上去的。若移除 jQueryClientIdEnhancement.RegisterExtScript就恢復正常嗎? 是否能提供有問題的程式範例?

# by FrankWu

先說抱歉,昨天有多post一筆,因為我重新整理看不到我的資料 = =,麻煩你砍掉多的囉 底下是我上面說明的範例,麻煩你了,謝謝 http://db.tt/33ZiTRP

# by Jeffrey

to FrankWu, 我測試的結果如附圖: http://www.darkthread.net/photos/1076-a883-o.gif 有沒有加RegisterExtScript會差別在多出來的class宣告,但id的部分看起來也正常。不知測試環境是否還有其他差別? (ASP.NET 3.5 on Windows 7, DB是我仿北風Categories Schema做了一個簡易版)

# by FrankWu

<table class="_ctl00" id="GridView1_ctl00" style="border-collapse:collapse;" ... 這個table在Server端應該是GridView,命名成GridView1,在Client端卻變成GridView1_ctl00 而且因為你是用工具看的,我發現它只會抓到第一個ID Name,你如果用檢視原始碼,就會看到後面還有原本命名的GridView1 因為是在GridView1_ctl00後面,大概是工具看到第一個ID Name,就認定這個元素就是那個ID Name <tr class="_ctl01" id="GridView1_ctl01"> 這標籤本來應該不會出現 id="GridView1_ctl01" 的 不過在引用jQueryClientIdEnhancement.RegisterExtScript()之後 而且當POST之後,就會出現此命名 但當不POST,你會發現並沒有此ID Name

# by Jeffrey

to FrankWu, 經專案小組連夜調查,發現是ClientID搞的鬼,詳情請見: http://blog.darkthread.net/post-2011-02-19-ensureid-when-getclientid.aspx

# by Terence

避免dictionary.Add把重覆dictionary Keys資料加入, 建議增加dictionary.Keys.Contains的檢查~ private static void searchContentPlaceHolder(Control ctrl, Dictionary<string, string> dct) { if (ctrl.HasControls()) foreach (Control c in ctrl.Controls) searchContentPlaceHolder(c, dct); if (ctrl.Visible) { if (!dct.Keys.Contains(ctrl.ClientID)) //add by Terence:新增判斷CloentID是否已存在於dictionary中 { dct.Add(ctrl.ClientID, ctrl.ID); } } }

# by Jacy.Huang

黑大您好: 小弟是初學,有幸在您這兒看到有關 MasterPage CLIENTID 的簡化方法,頗為受用。 近日發現一個實用上的狀況,若我有一個 FileUpload 是可以被使用者自行擴增的,除了第一個 FileUpload 的 ID 會被記錄在 window.aspNetWebControls 得以簡化,其他以 JQUERY 擴增的 FILEUPLOAD2 就無法使用您的方法。 小弟雖然試著想重新去讀取 jQueryClientIdEnhancement.RegisterExtScript(this.Page); 來處理,但實在不懂運作機制,不得已只好來請教黑大是否有方法呢? 謝謝您。

# by Jeffrey

to Jacy.Huang, RegisterExtScript只能搜尋在ASP.NET Server端就已經產生好的WebControl元素,而"jQuery擴增FileUpload2"的動作是HTML送到瀏覽器端才發生的,二者有時空上的差異,故不可能透過這種做法處理。 話說回來,若是jQuery動態增加的HTML元素,在id命名上就不該有被套上ctl00_ContentPlaceHolder1_*的困擾,原則上你就依其動態增加時賦與的id或name去找到它。要觀察動態增加HTML元素的資訊,可以善用IE的IE Dev Tools或是Firefox的FireBug。

# by Jacy.Huang

黑大您好: 謝謝您撥空回覆,十分感謝。^^

Post a comment