1.概括
这个问题宁波.net俱乐部里多次有人提起,故发在这里。
简单的将一句话document.getElementById("服务器端控件的ClientID属性").value 获取value
2.实例
比如 有一个名为TextBox1的TextBox,在js里访问就是document.getElementById("<%=TextBox1.ClientID%>").属性
实例代码如下:
<script type="text/javascript">
function getText()
{
alert(document.getElementById('<%=TextBox1.ClientID%>').value);
}
</script>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<input type="button" onclick="getText()" value="取值" />
实例完毕
3.原理
(续)
4.高级
(续)
来个更变态的方法,把Server Control的ID都改回去,不用NamingContainer
在Global.asax的Application_BeginRequest方法中遍历所有的控件,如果控件ID里带有$符号的,就把他变回去,具体代码
{
var form = HttpContext.Current.Request.Form;
form.GetType()
foreach (var key in form.AllKeys.Where(key => key.Contains("$")))
{
var value = form[key];
form.Remove(key);
var newKey = key.Substring(key.LastIndexOf("$") + 1);
form.Add(newKey, value);
}
}
js检测不出来控件的名称,比如Textbox id= txtName,查看源文件时它其实就变成了ctl00$ContentPlaceHolder1$txtName ,将
js中的txtName换成ctl00$ContentPlaceHolder1$txtName 也是可以实现的。就是麻烦点
1.为什么ID会变
ASP.NET的服务器控件ID会在生成HTML时变化,这使得许多人都摸不着头脑。当然如果保持原有的ID也可以正常工作的话,MS也不会空到搞出这个这么复杂的名堂,所以我们就看看为什么服务器控件的ID“必须”变化吧
始作俑者――Master page, Template control
在ASP.NET中,为了消除使用iframe制作框架的难维护性等缺点,MS引入了被称为母页板(Master page)的机制,在母页中使用ContentPlaceHolder控件来存放子页的内容,其原理看似相当简单――将aspx文件自上而下进行解释,遇到ContentPlaceHolder时递归解释子页面内容
然而正是这看似强大又简单的功能之下,隐藏了命名重复的危机,试想我们在母页和子页中分别命名了ID为MyLabel的控件,并且控件的ID保持不变。那么在输出的HTML页面中也会出现2个ID相同的元素,使得最终页面无法符合规范
同样的,对于Wizard等模板控制,因为可以在其内加入多个模板,也会导致命名冲突的问题,试想Repeater控件最终生成了数十个相同ID的控件的场景,是不是觉得背后发毛呢?
2.MS的方案――NamingContainer
为了解决命名冲突的问题,MS创造性地提出了NamingContainer的理念,使得不同层次的控件拥有完全不同的ID
对于NamingContainer的解释并不复杂,试着看一下某aspx页面的控件树
Master—Default—MyLabel
当使用NamingContainer规则生成MyLabel的客户端ID的时候,会查看其上一级命名提供者,随后将上一级的ClientID取来与自己的ID进行拼接,大致思路如下
string clientID = ID;
If (NamingContainer != null) {
clientID = NamingContainer.ClientID + “_” + clientID;
}
当然如果NamingContainer本身还有上一级的命名提供的话,会进行递归调用
3.演示
为了更形象地解释此命名规范,可以制作一个简单地例子
首先建立一个母页,命为Site.master,随后建立Default.aspx并应用Site.master,在Default.aspx中放置一名为MyLabel的Label控件,在此我们使用代码显示MyLabel的ID, ClientID和UniqueID,并显示出其NamingContainer的ID
<asp:Content ID="DefaultBody" ContentPlaceHolderID="SiteBody" runat="server"> <asp:Label ID="MyLabel" runat="server">简单命名</asp:Label> <div style="width: 200px; float: left"> ID</div> <div> <%= MyLabel.ID %></div> <div style="width: 200px; float: left"> UniqueID</div> <div> <%= MyLabel.UniqueID %></div> <div style="width: 200px; float: left"> ClientID</div> <div> <%= MyLabel.ClientID %></div> <div style="width: 200px; float: left"> NamingContainer</div> <div> <%= MyLabel.NamingContainer.ID %></div></asp:Content>运行结果如下:
简单命名 ID MyLabelUniqueID ctl00$SiteBody$MyLabelClientID ctl00_SiteBody_MyLabelNamingContainer SiteBody 其中UniqueID与ClientID相似,但以$作为分隔符,此属性作为服务器端对控件的唯一标识 对于带有模板的控件效果也是如此,在此我们以Wizard控件作为测试对象,aspx代码如下<asp:Content ID="DefaultBody" ContentPlaceHolderID="SiteBody" runat="server"> <asp:Wizard ID="MyWizard" runat="server" DisplaySideBar="false"> <WizardSteps> <asp:WizardStep ID="MyWizardStep" runat="server"> <asp:Label ID="YourLabel" runat="server">模板控件命名</asp:Label> <div style="width: 200px; float: left"> ID</div> <div> <%= YourLabel.ID%></div> <div style="width: 200px; float: left"> UniqueID</div> <div> <%= YourLabel.UniqueID%></div> <div style="width: 200px; float: left"> ClientID</div> <div> <%= YourLabel.ClientID%></div> <div style="width: 200px; float: left"> NamingContainer</div> <div> <%= YourLabel.NamingContainer.ID%></div> </asp:WizardStep> </WizardSteps> </asp:Wizard></asp:Content>运行结果如下
模板控件命名 ID YourLabelUniqueID ctl00$SiteBody$MyWizard$YourLabelClientID ctl00_SiteBody_MyWizard_YourLabelNamingContainer MyWizard注意到此处YourLabel的NamingContainer变为了MyWizard,且UniqueID和ClientID中都增加了MyWizard字样
4.关于NamingContainer
并不是所有的控件都有NamingContainer功能,在ASP.NET中,控件继承关系如下
Control—TemplateControl—UserControl
Control—WebControl
在这两条线中,只有TemplateControl及其子实现了INamingContainer接口,所以像Label等WebControl是没有NamingContainer功能的(当然他们也不会有子控件)
事实上,要实现NamingContainer功能非常简单,只需要简单地为控件加上INamingContainer接口即可,此接口并没有定义任何方法,所以对于类是没有负担的
说得很乱,呵呵,见笑了
!--Test.html-->
/ ...
unction getText()
return document.form1.Text1.value; // Text1就是对象的 id
/ ...
INPUT id="Text1" type="text" ...>
在,ASP.NET 让我们越来越习惯使用 TextBox 作为用户输入的途径。如果我们想在客户端脚本里访问一个 TextBox,原先的做法就行不通了——
!--Test.aspx-->
/ ...
unction getText()
return document.form1.Text1.value; // Text1还是对象的 id?
/ ...
asp:TextBox id="Text1" .../>
览页面时,会有一个脚本错误——“Text1对象不存在”。原因就在于,Text1作为服务器端控件 TextBox,在被发送到客户端之前,先由.NET Framework 进行转换,而它的id显然也是转换的一部分。如果你在客户端查看页面的源代码,你可以发现原先的 Text1已经不存在,取而代之的是一个普通的 INPUT——
input name="Test:Text1" type="text" id="Test_Text1" />
就是转换的结果,id 不再是设计时所指定的 id。如果我们要在客户端访问这个文本输入框,也必须改变访问的 id。如何改变?直接将
ocument.form1.Text1
为
ocument.form1.item("Test_Text1" // 保险起见,使用 item 由 id 或 name 得到控件
者
ocument.getElementByID("Test_Text1" // 保险起见,使用 getElementByID 由 id 或 name 得到控件
以吗?当然可以!只要你的控件 id 固定是"Text1"。
是,只有这个条件还不够。"Test"又是什么?它也应该被考虑在内(幸好 form 的 id 不会改变,否则要关心的内容又会多一个)。
或许已经看出,Test 就是这个 Web 页面的名字。对吗?——不完全对
切地说,控件转换后 id 中的"Test"是其所在的 Web 窗体对象的 ClientID。所有的 ASP.NET 对象都在服务器端有一个实例(如果你面向对象的基础不够,建议也补完一次吧),而这个"Test",就是这个页面实例对象的 ClientID。而 ClientID,则是每个 Web 窗体页的一个属性,它指明了这个Web窗体在客户端的标识。
什么要这么复杂?道理很简单,我们并不能在客户端脚本里确定页面的 ClientID 和控件的 ID。
应该怎样做呢?
在服务器端代码里生成客户端 JavaScript。”——似乎非常复杂,其实并不困难,只要在服务器端 Page_Load 事件里加上(在 IsPostBack 判断之外)——
egisterStartupScript("start",
"\n<script>\n" +
"function getText()\n" +
"{\n" +
" return document.forms(0).item('" + this.ClientID + ":" + this.Text1.ID + "');\n" +
"}\n" +
"</script>\n";
egisterStartupScript 是 Web 窗体(System.Web.UI.Page 类)的一个方法,作用是在生成的页面里注册客户端脚本。
这里,我们添加了一个 getText()函数,作用和之前的 getText()一样,所不同的在于,它所访问的控件 id并非脚本内指定,而是在服务器端根据页面的 ClientID(this.ClientID,this就是页面自己)和 Text1控件的 ID(this.Text1.ID)动态生成的。
编译之后重新浏览,我们会在新的页面源代码里找到这个由服务器端代码生成的 JavaScript 函数。此时,在页面的其他地方调用 getText()函数就将正确得到 Text1中的内容了。
