我不是一个人

逆风的方向,更适合飞翔

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

[ASP.NET 2.0]创建母版页引来的麻烦

一、问题提出

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

二、 抽象模型

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

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head 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>

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

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" Title="使用母版页面抽象模型-YJingLee" %>
<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代码:
    单一页面:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><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 name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTEzMjE5NDA0NWRkKlEH1jSXJkIbnUaP2d9Dra8LQEk=" />
</div>
    
<div>
        
<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="客户端插入(_)" /></div>
<div>
    
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBALVid/5DQKShrDCCQL5w9POBQL5w4vOBZPGqxUU/yvoKTqG8k+uG8YroGTv" />
</div></form>
</body>
</html>

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>使用母版页面抽象模型-YJingLee</title></head>
<body>
    
<form name="aspnetForm" method="post" action="Default2.aspx" id="aspnetForm">
      
<div>
        
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTEwMTY2NjE0OWRkADUETiohcorj2qXOE9M1qhFVw20=" />
     
</div>
   
<div>        
<script language="javascript" type="text/javascript">
// <!CDATA[
function insert() {
           document.getElementById(
"txt").value=document.getElementById("txt").value+"(__)";
        
return;
}
// ]]>
</script>
    
<div>
        
<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="客户端插入(_)" /></div>
    
</div>    
<div>
    
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBAKyga4JAtO59ZELApOT2tEDApOTwvAC83bfMO00kt0PYcRte7XQOsXBcFE=" />
</div></form>
</body>
</html>

    是不是看到问题了,源文件控件元素的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 %>");推荐

    其实模板页最后编程控件的一个子控件, 本身NamingContainer又会把自己的ID放到控件ID前作为前缀. 所有在客户端对ID的引用, 都应该用ClientID的方式才保险.  永远不要去假设客户端id属性,需要此属性就去ClientID取,这样才是正交的设计——不依赖于任何外部的控件,如果你把客户端id写死了,那么你就依赖于当前的INamingContainer的套叠情况,一旦控件树改变了,ClientID随之改变,你硬编码的id就不再正确,而这种错误是难以发现的。
随之而来的,肯定有人说客户端执行时,无法取到ClientID。然而服务器端在客户端之前执行,如果你要把ClientID先保存到客户端,是肯定能做到的,因此总能在客户端代码执行之前先获得ClientID。

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

posted on 2008-03-27 09:52  点6K  阅读(1013)  评论(0)    收藏  举报