Friday, December 26, 2008 - 文章

Resolving MasterPage ClientId Issue in jQuery

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,希望用起來夠方便。

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

搜尋

Go

<December 2008>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

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

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication