Tips/Tricks#0:母版页中对控件ID的处理

注:此系列记录在我实际开发中遇到的问题和收藏一些技巧文章。

本篇技巧和诀窍记录的是:母版页中对控件ID的处理。

一、问题提出

由于总体排版和设计的需要,我们往往创建母版页来实现整个网站的统一性,最近我由于统一性的需要,把原来整个项目单独的页面全部套用了母版页。但是出现了一个错误……在我的Blog中记录一下,方便大家参考。

二、 抽象模型

由于整个页面内容过多,所以我把这个页面中最为本质的问题抽象出来。原来单一页面,就是利用按钮触发JS事件,在文本域中插入“(_)”功能,其实现代码如下:

<head id="Head1" runat="server">
    <title>单一页面抽象模型-YJingLee</title>
    <script language="javascript" type="text/javascript">
        // <!CDATA[
        function insert() {
             document.getElementById("txt").value=document.getElementById("txt").value+"(__)";
       return;
        }
        // ]]>
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <textarea id="txt" runat="server" name="txt" rows="10" cols="50"></textarea>
        <asp:Button ID="btnInsert" runat="server" Text="服务器端插入(_)" OnClientClick="insert();" />
        <input id="btnInsert2" name="insert" onclick="insert();" type="button" value="客户端插入(_)"
            runat="server" /></div>
    </form>
</body>
</html>

上述页面可以正常使用。后来使用模板页后,其代码如下:

<asp:content id="Content1" contentplaceholderid="ContentPlaceHolder1" runat="Server">
<script language="javascript" type="text/javascript">
// <!CDATA[
    function insert() {
        document.getElementById("txt").value = document.getElementById("txt").value + "(__)";
        return;
    }
// ]]>
</script>
<div>
   <textarea id="txt" runat="server" name="txt" rows="10" cols="50"></textarea>
   <asp:Button ID="btnInsert" runat="server" Text="服务器端插入(_)"  OnClientClick="insert();"/>
   <input id="btnInsert2" name="insert" onclick="insert();" type="button"
 
value="客户端插入(_)" runat="server"/></div> </asp:content>

当打开后按下按钮出现了“Microsoft JScript 运行时错误: 'document.getElementById(...)' 为空或不是对象”。这是什么原因呢?原来好好的,怎么套用个母版页就出现这个奇怪的问题呢?困扰了好久,和朋友讨论了一下,终于找到了答案……

三、分析本质

原来我们仔细看看其生成的HTML代码:单一页面:

<form name="form1" method="post" action="Default.aspx" id="form1">
    <textarea name="txt" id="txt" rows="10" cols="50"></textarea>
    <input type="submit" name="btnInsert" value="服务器端插入(_)" onclick="insert();" id="btnInsert" />
    <input name="btnInsert2" type="button" id="btnInsert2" onclick="insert();" value="客户端插入(_)" />
</form>

再看看套用母版页之后,生成的HTML代码:

<form name="aspnetForm" method="post" action="Default2.aspx" id="aspnetForm">
    <textarea name="ctl00$ContentPlaceHolder1$txt" id="ctl00_ContentPlaceHolder1_txt"
        rows="10" cols="50"></textarea>
    <input type="submit" name="ctl00$ContentPlaceHolder1$btnInsert" value="服务器端插入(_)"
        onclick="insert();" id="ctl00_ContentPlaceHolder1_btnInsert" />
    <input name="ctl00$ContentPlaceHolder1$btnInsert2" type="button" 
id="ctl00_ContentPlaceHolder1_btnInsert2" onclick="insert();" value="客户端插入(_)" /> </form>

是不是看到问题了,源文件控件元素的ID和生成HTML文件的ID不一致。表单from的name属性和id属性变成了aspnetForm,控件的id属性被无缘无故了加上了ctl00_ContentPlaceHolder1_前缀,其name属性也加上了ctl00$ContentPlaceHolder1$前缀。

这下知道了,难怪提示“'document.getElementById(...)' 为空或不是对象”的错误了,原来生成页面后其ID都变了。那么我们如何解决它呢?既然他id变了,我们就把JS代码id改为生成后的id。代码如下:

function insert() {
    document.getElementById("ctl00$ContentPlaceHolder1$txt").value = 
    document.getElementById("ctl00$ContentPlaceHolder1$txt").value + "(__)";
    return;
}
//或者
function insert() {
    document.getElementById("ctl00_ContentPlaceHolder1_txt").value = 
    document.getElementById("ctl00_ContentPlaceHolder1_txt").value + "(__)";
    return;
}

好了,问题解决了,不过想想有什么更好的办法呢?到底为什么呢?其实分析一下,它是后来生成的客户端id,我们可以用C#语句Control的ClientID属性,像这样写:txt.ClientID; txt还是原来控件的id,后面的ClientID就是新生成的id。txt.ClientID是从程序里取到的后来生成新的id,这样不是更好吗。修改代码如下:

function insert() {
    document.getElementById("<%=txt.ClientID %>").value = 
    document.getElementById("<%=txt.ClientID %>").value + "(__)";
    return;
}

还有在后台Request.Form["txt"]键值需要改变,必须变为Request.Form["<%=txt.ClientID %>"]才能接收到页面的值。想想如果想要得到ID的control是一个用户控件的话,当生成页面后尽管能得到其ClientID,但是却得不到这个对象,所以也就不能设置或获得其属性了。比如,我要做的这个用户控件,由三个DropDownList组成,可是我却想得到一个完整的日期值(指在客户端),一种思路是先获得三个DropDownList的ClientID,然后再由ID1.value+ID2.value+ID3.value取得,可是如果你一个页面上需要放多个这样的用户控件的话,你需要取得多少个ClientID?显然这样做的话,工作量会很大,而且要操作众多的对象,很容易出错。

四、总结

这一类问题我像在我们编写程序时往往经常会遇到,总结一下:这应该属于“使用了MasterPage,或者GridView中的模版列后所有元素ID不一致问题”。由于种种原因(比如使用了MasterPage,或者GridView中的模版列),一个控件在设计时的ID往往不同于生成页面后的ID,为了获得控件客户端ID,我们可以从生成的页面入手,取控件id修改方法:

document.getElementById("ctl00$编辑区ID$控件ID");
document.getElementById("ctl00_编辑区ID_控件ID");
document.getElementById("<%=控件名ID.ClientID%>"); //推荐

在我们设计时往往就会出现一些莫名其妙的问题,我想我们遇到问题时,冷静思考,把握主次,从底层框架入手,纠其原因,相信最终会找到答案。


作者:李永京YJingLee's Blog
出处:http://lyj.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Tag标签: ASP.NET
posted @ 2007-10-30 21:18 李永京 阅读(3665) 评论(50)  编辑 收藏 网摘 所属分类: ASP.NET

  回复  引用  查看    
#1楼2007-10-30 21:36 | 菌哥      
地球人都知道!
  回复  引用  查看    
#2楼2007-10-30 21:56 | 怪怪      
其实模板页最后编程控件的一个子控件, 本身NamingContainer又会把自己的ID放到控件ID前作为前缀. 所有在客户端对ID的引用, 都应该用ClientID的方式才保险.
  回复  引用  查看    
#3楼2007-10-30 21:58 | 怪怪      
document.getElementById("ctl00$编辑区ID$控件ID");
document.getElementById("ctl00_编辑区ID_控件ID");

万万要不得..

  回复  引用    
#4楼2007-10-30 23:18 | funwun[未注册用户]
這個都可以寫一篇文章,強!!!!!!!!!!!1
  回复  引用  查看    
#5楼2007-10-30 23:31 | watson hua      
@怪怪
这倒不见得。

我举一个例子。
现在需要一个标签的dom id作为另一个标签的某个属性的值。
如果在服务器端控件标签的属性里,服务器端脚本是不会被解释执行的,更取不到clientId,只能冲到aspx.cs里去attributes.add,这又是破坏页面结构的。所以如果像"ctl00$编辑区ID$控件ID"这种id,维护的起的话,用用无妨。

当然我的意思是,平衡多种方案,选一个代价最小的,而"ctl00$编辑区ID$控件ID",也可以作为一种方案。

没有什么是肯定的。

  回复  引用  查看    
#6楼2007-10-30 23:49 | 怪怪      
@watson hua
这个服务器控件属性中的代码解释问题,我自己写了ExpressionBuilder, 可以完美解决,其实这个ExpressionBuilder其实这个只需覆盖盖两个方法,每个方法里写一行话。

第二,如果你需要一个服务器端控件的dom id作为另一个runat="server"的标签的属性,除了上述做法,还可以自己继承需要接收这个参数的标签所对应的控件,定义一个属性来接收目标服务器控件的服务器端ID,然后在NamingContainer树中查找这个服务器控件ID,得到其ClientID,你可以看看微软自己的DataSourceID等用服务器端ID做参数,找到控件的方法(而现在咱们的目标是找到这个控件,得到其ClientID属性值).

为什么我说这个事情最好这么做, 而不能"ctl00$编辑区ID$控件ID", 因为这个控件ID生成的方法是可以被子类覆盖的。所以控件的UniqueID和ClientID不见得一定是这种形式, 所以我说这种写法不可取。另外如果控件树发生变化,也会造成那些自动生成的ID部分比如ctl00发生变化, 这就很不可靠了。

没有什么是肯定的, 这是在哲学意义上来说,有些事情在某一个范围内确实有个对错之分...,你说呢 :)

  回复  引用  查看    
#7楼2007-10-30 23:55 | Cat Chen      
永远不要去假设客户端id属性,需要此属性就去ClientID取,这样才是正交的设计——不依赖于任何外部的控件,如果你把客户端id写死了,那么你就依赖于当前的INamingContainer的套叠情况,一旦控件树改变了,ClientID随之改变,你硬编码的id就不再正确,而这种错误是难以发现的。
  回复  引用  查看    
#8楼2007-10-30 23:59 | Cat Chen      
随之而来的,肯定有人说客户端执行时,无法取到ClientID。然而服务器端在客户端之前执行,如果你要把ClientID先保存到客户端,是肯定能做到的,因此总能在客户端代码执行之前先获得ClientID。

很多时候,大家觉得JavaScript反正是辅助性的,随便写也可以,然后函数也没有参数。例如隐藏某Label用的函数,就是function hideLabel(),之后你当然要在里面硬编码那个Label的id啊。事实上你应该把函数定义为function hideLabel(id),在调用该函数的代码在服务器端生成,把ClientID嵌入进去。

  回复  引用  查看    
#9楼2007-10-31 00:02 | Zhuang miao      
走过
  回复  引用    
#10楼2007-10-31 08:38 | gm[未注册用户]
我还以为是什么大的发现呢,这个用了asp2.0的都应该知道的!!!
  回复  引用    
#11楼2007-10-31 08:44 | 随风飘扬[未注册用户]
这些是常识了吧....
  回复  引用    
#12楼2007-10-31 08:55 | e2mars[未注册用户]
我就不知道,谢谢博主!

  回复  引用  查看    
#13楼2007-10-31 09:00 | JerryChou      
常识还能写出这么大一篇来,佩服。
  回复  引用    
#14楼2007-10-31 09:24 | 哑佬邓[未注册用户]
大家都是新人走过来的,应该多多支持一下,虽然我知道,但是可以加深一下和鼓励一下别人,何乐而不为呢,
  回复  引用    
#15楼2007-10-31 10:24 | melodyyyyy[未注册用户]
汗。。。我也不知道有document.getElementById("<%=控件名ID.ClientID %>");这种取法。。。都是硬取的ID。。。受教了 谢谢。
  回复  引用  查看    
#16楼2007-10-31 11:29 | 沙加      
最好就是不要用服务器端的控件,我现在都用HTML控件了,实在不行再加一个runat=server就不结了.
实现INamingContainer的都这样
  回复  引用  查看    
#18楼[楼主]2007-10-31 12:54 | 李永京      
大家都是新人走过来的!在此留下自己的足迹,何乐而不为呢?也给新手带来帮助。。。
  回复  引用  查看    
#19楼2007-10-31 13:35 | 壁虎      
共享自己的经验有错吗?
共享并不高深的经验错了吗?

你懂你可以一带而过,但是不懂却是可以学到东西。

我不认为简单的东西就不能共享。毕竟简单或者难,那是相对的。

  回复  引用  查看    
#20楼2007-10-31 14:32 | Teracy_Joy      
共享自己得经验,我支持,每个人工作开发得内容不同,接触得东西不同,各又所长,大家应该虚心学习,另外我还补充一点:在母板页还有在用户控件中得TextBox等等控件ID在应用到某各页面得时候控件ID都会发生改变得,所以在写js也是一样得,用你提到你得:document.getElementById("<%=控件名ID.ClientID %>");这样得方法,其实不管在什么地方用这个最保险了.
  回复  引用    
#21楼2007-10-31 14:41 | heng-xi[未注册用户]
及时的支援
  回复  引用  查看    
#22楼2007-10-31 17:28 | Cat Chen      
@沙加
只要有runat="server",就是服务器端控件,<div id="myDiv" runat="server" />同样受INamingContainer影响,输出的ClientID仍然可能不等于ID。

  回复  引用  查看    
#23楼2007-10-31 19:34 | Matrix      
如果是母板再套母板再套webpart呢。
呵。
用ClientID直接搞定

  回复  引用  查看    
#24楼2007-11-01 20:35 | 随心所欲      
document.getElementById("ctl00$编辑区ID$控件ID");
这种方式真是要不得。切记。

  回复  引用  查看    
#25楼[楼主]2007-11-01 21:46 | 李永京      
@随心所欲
为什么啊?告诉我好吗?我想用document.getElementById("<%=控件名ID.ClientID %>");.这个document.getElementById("ctl00$编辑区ID$控件ID"); 是我开始想到的....谢谢

  回复  引用  查看    
#26楼2007-11-02 12:42 | 随心所欲      
因为“ctl00$编辑区ID$控件ID”是一个自动生成的Id,不保证正确。最好还是ClientID。
这是给你自己下绊子,给程序留下隐患。

  回复  引用  查看    
#27楼[楼主]2007-11-02 14:05 | 李永京      
@随心所欲
谢谢啊...吸取教训,不能只看表面现象.

  回复  引用  查看    
#28楼2007-11-02 15:32 | 随心所欲      
都有一个学习的过程。
我也遇到过这个问题,解决方法不大一样,你也可以借鉴一下。
http://www.cnblogs.com/dlwang2002/archive/2006/12/21/599107.html" target="_new">http://www.cnblogs.com/dlwang2002/archive/2006/12/21/599107.html

  回复  引用    
#29楼2007-11-24 16:10 | 阙荣文[未注册用户]
看到那么多对楼主不屑的回复,真是让人感慨. 都是些什么人啊.
我觉得楼主写得好, 从现象到本质到解决方案, 思路清晰条理明了. 支持.
那些所谓"常识" 就怎样怎样的人有没有想过,
难道你在娘胎里就知道这个"常识"
难道你一开始学ASP2.0就知道这个常识??
再退一步说,让你写一篇文章说这个问题你还不一定说得有楼主那么好.
你们这些人得瑟什么?
有什么资格得瑟?

楼主请无视这些垃圾, 我支持你!!

  回复  引用  查看    
#30楼[楼主]2007-11-24 18:22 | 李永京      
@阙荣文
他们都是高手啊,这些的确对于他们来说就是常识了,但是我考虑到像我这样新手很多,书上也没有相关介绍,只有在实际项目中总结出来,写写的......

  回复  引用    
#31楼2007-11-27 19:49 | 阙荣文[未注册用户]
TO 楼主:
我上面说的话和技术无关, 只是我认为能把这点知识写出来,而且写得那么好就值得肯定.我鄙视的使那种用很不屑的语气在边上指手画脚的行为.
现在很多论坛的风气就很不好,发个帖子,写篇文章,总有人跳出来指手画脚说这个怎样怎样,那个又怎样怎样. 我觉得是态度问题,如果确实对这篇帖子(文章)有异议,可以讨论,可以补充. 凭空说一些大话,莫名其妙的话,有何意义?

当然, 我可能理会错某些评论的本意了,也许发表者并没有这个意思.那就算我借题发挥吧. 共勉.

  回复  引用    
#32楼2007-12-05 22:56 | 可乐虫[未注册用户]
仁者见仁,智者见智
  回复  引用    
#33楼2007-12-14 11:33 | xiazhi33[未注册用户]
其实 呵呵``` 还有种 好的办法就是 findcontrol 了`
不知道 大家使用过没的 `` 我反正经常使用的哈!

  回复  引用  查看    
#34楼2008-01-09 10:26 | ColdDog      
我这里的一篇翻译文第4点也提到这个问题,请参考:
http://www.cnblogs.com/lxinxuan/archive/2007/04/21/721980.html" target="_new">http://www.cnblogs.com/lxinxuan/archive/2007/04/21/721980.html

  回复  引用  查看    
#35楼[楼主]2008-01-09 12:37 | 李永京      
@ColdDog
恩,去看看!

  回复  引用    
#36楼2008-04-09 13:02 | Must Studying[未注册用户]
你知道你怎么不提啊???博主提的不错,说你不好的都垃圾
  回复  引用    
#37楼2008-06-18 16:56 | 乔[未注册用户]
博主,
谢谢你的分享,但我有个问题,假如我的控件是在使用了母板页的gridview的模板中,该如何获取呢。

  回复  引用  查看    
#38楼[楼主]2008-06-18 17:11 | 李永京      
@乔
this.gvList = this.Master.FindControl("Content").FindControl("gvList") as GridView;

  回复  引用    
#39楼2008-06-19 08:34 | 乔[未注册用户]
@ 李永京
谢谢你的解答,
可能我没说清楚。
我是在GridView的FooterTemplet里面放了一个TextBox和一个Lable控件,
这个Gridview是包含在<asp:content>中的,现在我想利用AjaxPro来实现对TextBox里面的值与数据库中的验证,把验证结果利用javascript写到lable中,后台的方法和前台的javascript方法都已经定义好了,在输出结果的时候,我要获得到lable,我开始是用 var msg=document.getElementById (Message');来取得这个ID的,其中这个“Message”是lable的ID,但是后来发现取不到,最后采用博主提出的查看生成后的HTML代码里面其ID值,就如下所示:
var msg=document.getElementById ('ctl00_ContentPlaceHolder1_grvPromotes_ctl03_Message');
刚开始的时候是工作的好好的,可是最后我发现,有时候这个ID值也是会变的,所以,我总不能再把那个ID硬编码到我的代码中。昨天弄了一下,可是不管用什么方法都得不到Lable里面的值,最后看到博主的这篇文章,所以还得请博主为我解答一下,十分感谢。

  回复  引用  查看    
#40楼[楼主]2008-06-19 09:38 | 李永京      
@乔
这个文章中提到了。用document.getElementById("<%=控件名ID.ClientID %>");方法。

  回复  引用    
#41楼2008-06-19 10:22 | 乔[未注册用户]
@李永京
我这样做过了,可是没用。
这里的控件名ID指的就是gridview中foottemplet中的labelID吧。
首先他就报出这个ID是不存在的。

  回复  引用    
#42楼2008-06-19 10:25 | 乔[未注册用户]
编译器错误信息: BC30451: 未声明名称“Message”。
  回复  引用    
#43楼2008-06-27 15:19 | moonnight366[未注册用户]
谢谢博主,我就遇到这样的问题来的,原来解决起来这么简单,
楼上的同志谦虚点嘛,你会,不是还有不会的么,至少看博主的文不会让我感觉浪费时间,看你们的评论反之。

  回复  引用    
#44楼2008-07-04 17:33 | 追梦客1[未注册用户]
谢谢博主,帮我解决了问题。
  回复  引用  查看    
#45楼[楼主]2008-07-11 20:57 | 李永京      
@乔
不好意思,一直没有时间上网,没得及时回复。

  回复  引用  查看    
#46楼[楼主]2008-07-11 20:57 | 李永京      
@moonnight366
@追梦客1
:)

  回复  引用  查看    
#47楼2008-08-06 16:00 | 破宝      
尽量不要用硬编码。还是老老实实用 ClientID 吧。
首先一条,ClientID的生成规则,微软并没有明确写进文档,形成标准,目前的命名规律在以后版本是否发生变化,除了微软没人可以打包票。
如果某个ID用的次数多,可以在脚本里声明成变量,只赋一次值。

  回复  引用  查看    
#48楼[楼主]2008-08-09 23:12 | 李永京      
@破宝
恩,以上仅仅是个思路,导出ClientID

  回复  引用    
#49楼2008-12-02 09:27 | byj010byj[未注册用户]

告诉大家,就是好滴!谢谢博主!

  回复  引用    
#50楼2009-02-04 18:47 | 路人甲2009[未注册用户]
@JerryChou
我很看不惯那些自己写不出来文章却对别人文章说三道四的人了。
倒是请您也多写一些非常识的好文章吧




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 943527




相关文章:

相关链接: