ASP.NET, JavaScript, and HTML Element ID attributes
Today I had to fix some legacy code. It is ASP.NET code and it has both ASP.NET elements and ASP.NET Controls. The plan was to replace a large portion of code behind with JavaScript. The ASP.NET code needed to be a UserControl that could appear twice on the same page. This created some problems:
- ASP.NET creates some html controls but changes the id and the name attributes. While it is possible to run the web page and see what the attributes will be changed to and then use those strings statically in JavaScript, the JavaScript could easily break with slight changes to the ASP.NET code. This is not scalable or maintainable.
- ASP.NET does NOT rename the the id or the name attributes for normal HTML tags. First, that is a consistency issue. Second it is an issue using the same control multiple times. Third, if you want to get the value of a form element, doing so uses the name attribute and so each form element needs a separate name.
So lets explain the is problem with a real world scenario.
Lets say you have the following requirements:
- Create a UserControl, called PersonControl, that accepts person’s basic info: First name, Last name, and Birthdate.
- The form also has a button and it should only be enabled if all three fields are populated.
- The Birthdate should use JQuery’s DateTimePicker.
- The First name and Last name should be ASP.NET text boxes.
Now imagine the site already exists and you have to add this requirement:
- A web page should exist that has multiple PersonControls showing: for example: Employee, Emergency Contact.
The problem with ASP.NET, JavaScript, and HTML Element ID attributes
So let’s build this project using a single control and static id and name attributes and see how it works. Later we will see what we need to do to get this working with multiple PersonControls.
- Open Visual Studio and create a new ASP.NET Empty Web Application.
- Add JQuery and JQuery-UI. Do this as follows:
- Right-click on the project and choose Manage NuGet Packages.
- In the Manage NuGet Packages window, on the left, click Online.
- On the right, in the search field, type JQuery.
- Install JQuery and JQuery UI.
- Create new Web Form called PersonForm.
- Add the following into your PersonForm.aspx file:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PersonForm.aspx.cs" Inherits="AspAndJavaScriptExample.PersonForm" %> <%@ Register Src="~/PersonControl.ascx" TagPrefix="uc1" TagName="PersonControl" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>ASP.NET, JavaScript, and HTML id Attributes</title> </head> <body> <form id="form1" runat="server"> <div> <h2>Employee</h2> <uc1:PersonControl runat="server" ID="PersonControlEmployee" /> </div> <%--<div> <uc1:PersonControl runat="server" ID="PersonControlEmergencyContact" /> </div>--%> </form> </body> </html>
- Create a new Web User Control called PersonControl.
- Add the following into your PersonControl.ascx file:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PersonControl.ascx.cs" Inherits="AspAndJavaScriptExample.PersonControl" %> <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="PersonControl.ascx.cs" Inherits="AspAndJavaScriptExample.PersonControl" %> <link href="Content/themes/base/minified/jquery.ui.datepicker.min.css" rel="stylesheet" /> <script src="scripts/jquery-2.0.0.min.js"></script> <script src="scripts/jquery-ui-1.10.3.min.js"></script> <script src="scripts/ButtonManager.js"></script> <script> $(function () { $(".datepicker").datepicker(); }); </script> <div style="border: solid; padding: 2px"> First name: <asp:TextBox ID="FirstName" runat="server"></asp:TextBox><br /> Last name: <asp:TextBox ID="LastName" runat="server"></asp:TextBox><br /> Birthdate: <input type="text" name="BirthDate" id="BirthDate" class="datepicker" /><br /> <asp:Button ID="SubmitPerson" runat="server" Text="Submit" OnClick="SubmitPerson_Click" /> </div>
- Add the following button SubmitPerson_Click method into your PersonControl.ascx file:
protected void SubmitPerson_Click(object sender, EventArgs e) { // Put break point here var firstName = FirstName.Text; var laststName = LastName.Text; var birthdate = Request.Form["Birthdate"]; }
- Now add this ButtonManager.js file.
jQuery(document).ready(function () { $("#PersonControlEmployee_FirstName").bind("propertychange keyup input paste", setButtonState); $("#PersonControlEmployee_LastName").bind("propertychange keyup input paste", setButtonState); $("#BirthDate").change(setButtonState); setButtonState(); }); var setButtonState = function () { if (!areValuesPopulated()) $("#PersonControlEmployee_SubmitPerson").attr("disabled", "disabled"); else $("#PersonControlEmployee_SubmitPerson").removeAttr("disabled"); } var areValuesPopulated = function () { return $("#PersonControlEmployee_FirstName").val() != "" && $("#PersonControlEmployee_LastName").val() != "" && $("#BirthDate").datepicker().val() != ""; }
- Now run the project and look at the source code of the html. It looks as follows:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title> ASP.NET, JavaScript, and HTML id Attributes </title></head> <body> <form method="post" action="PersonForm.aspx" id="form1"> <div class="aspNetHidden"> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="nkizDAjcAtd96A9EOpli0xdG3n6zTXVaM/5t2fmcAI5+LPQ6OzzIV2wUpisxoUTMFxIKkUwKDY4Xk36/NouRsiE81gq5z3Ch/tz3DlxJW9g=" /> </div> <div class="aspNetHidden"> <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="XCB0fxwKvdqEXpUBaICUtf6EzkAvBeahq0bywZekyukCuzvGVagqOnVHUWFHF2Cycd2xkg/UhNh/B3qNqabkBI+Flj52GkM3p6SF5eL/M4SnnfwmFBjWVOaTZ+IlwMvkR1bGuMxomyeJJ5HaU1FXWSYDYHVtgM9tdsVi31EireM=" /> </div> <div> <h2>Employee</h2> <link href="Content/themes/base/minified/jquery.ui.datepicker.min.css" rel="stylesheet" /> <script src="scripts/jquery-2.0.0.min.js"></script> <script src="scripts/jquery-ui-1.10.3.min.js"></script> <script src="scripts/ButtonManager.js"></script> <script> $(function () { $(".datepicker").datepicker(); }); </script> <div style="border: solid; padding: 2px"> First name: <input name="PersonControlEmployee$FirstName" type="text" id="PersonControlEmployee_FirstName" /><br /> Last name: <input name="PersonControlEmployee$LastName" type="text" id="PersonControlEmployee_LastName" /><br /> Birthdate: <input type="text" name="BirthDate" id="BirthDate" class="datepicker" /><br /> <input type="submit" name="PersonControlEmployee$SubmitPerson" value="Submit" id="PersonControlEmployee_SubmitPerson" /> </div> </div> </form> </body> </html>
Problems
- Notice the id and name attributes on the tags. The ASP.NET controls have been altered by ASP.NET with a prefix. This is not the problem. This is good. If the control is used multiple times, then this keeps the id and name attributes unique and they are supposed to be unique. However, the problem is, if the id is changed in this line . . .
<uc1:PersonControl runat="server" ID="PersonControlEmployee" />
. . . then id and name attributes in the child control will change. Since we are using those values statically in the ManageButton.js, any such change also breaks in the javascript. Also, we aren’t using a master page, but if you decided to add a master page, that add an additional prefix, which would create different id and name attributes, again causing the javascript to break. In fact, any such nesting change will change the id and name attributes breaking the javascript.
- The control that is not an ASP.NET control, the JQuery datepicker control, did not have the same modifications made to the Birthdate. So this tag won’t work if the control is used multiple times.
Do you want to see the problem?
Update your form in PersonForm.aspx to include multiple controls.
<form id="form1" runat="server"> <div> <h2>Employee</h2> <uc1:PersonControl runat="server" ID="PersonControlEmployee" /> </div> <div> <h2>Emergency Contact</h2> <uc1:PersonControl runat="server" ID="PersonControlEmergencyContact" /> </div> </form>
Now give it try. See the problems? Ok. So now you have a simulation of the problematic code that I faced today.
Solution to ASP.NET, JavaScript, and HTML Element ID attributes
So we are going to fix this in parts. We are going to use a variable in the ASP.NET control called ClientID, that is basically the prefix used.
- Fix html elements to use the same prefix as the ASP.NET controls
- Fix the javascript to receive the ClientID as a parameter
- In the PersonControl.ascx file, add a little snippet of JavaScript code to pass the ClientID into the JavaScript files.
<script> $(function () { $(".datepicker").datepicker(); }); jQuery(document).ready(function () { startManagingButton("<%=ClientID%>"); }); </script>
- Now update your JavaScript file to use that ClientID as the prefix. Notice, this is used in the events. I created a simple buildId method that I use throughout now.
var startManagingButton = function (inIdPrefix) { $(buildId(inIdPrefix, "_FirstName")).bind("propertychange keyup input paste", inIdPrefix, setButtonState); $(buildId(inIdPrefix, "_LastName")).bind("propertychange keyup input paste", inIdPrefix, setButtonState); $(buildId(inIdPrefix, "_BirthDate")).change(inIdPrefix, setButtonState); setButtonState(inIdPrefix); } var setButtonState = function (inIdPrefix) { if (inIdPrefix.data) inIdPrefix = inIdPrefix.data; if ($(buildId(inIdPrefix, "_FirstName")).val() == "" || $(buildId(inIdPrefix, "_LastName")).val() == "" || $(buildId(inIdPrefix, "_BirthDate")).val() == "") $(buildId(inIdPrefix, "_SubmitPerson")).attr("disabled", "disabled"); else $(buildId(inIdPrefix, "_SubmitPerson")).removeAttr("disabled"); }; var buildId = function (inIdPrefix, idSuffix) { return "#" + inIdPrefix + idSuffix; };
Now no matter how you update or move this web page, the Id values always work.
Fix the datepicker attributes
First, let’s fix the ASP.NET and HTML so that all the id and name attributes are consistently changed.
Change the datepicker line and add some code so it will have the same prefix as ASP.NET created html controls.
<input type="text" name="<%=ClientID%>$BirthDate" id="<%=ClientID%>_BirthDate" class="datepicker" /><br />
Pass the prefix into the JavaScript
Use the ClientID in ASP.NET SubmitPerson_Click method
Update the code to look as follows:
protected void SubmitPerson_Click(object sender, EventArgs e) { // Put break point here var firstName = FirstName.Text; var laststName = LastName.Text; var birthdate = Request.Form[ClientID + "$Birthdate"]; }
Using the PersonControl multiple times
Now everything should be working and you should be able to include as many instances of your control in your web page as you want.
Update your form in PersonForm.aspx to include many of these controls.
<form id="form1" runat="server"> <div> <h2>Employee</h2> <uc1:PersonControl runat="server" ID="PersonControlEmployee" /> </div> <div> <h2>Spouse</h2> <uc1:PersonControl runat="server" ID="PersonControlSpouse" /> </div> <div> <h2>Emergency Contact</h2> <uc1:PersonControl runat="server" ID="PersonControlEmergencyContact" /> </div> </form>
Now give it try. All instances of the PersonControl are now working. The dynamic id and name attributes are not a problem as we are handling them; in fact, they are part of the ultimate solution to make the control reusable.
Conclusion
ASP.NET, JavaScript, and HTML Element ID attributes can all be used to work together to make a nice cohesive application.
If you have a better solution, please post a comment and let me know.
Downloads
Here is the project in both states:
AspAndJavaScriptExample-problematic.zip
AspAndJavaScriptExample-working.zip
Bugs
Now the only bug I can find is on clicking submit the JQuery datepicker field is cleared. I’ll try to fix that and post the solution in another post.
seo Agencies in mesa
ASP.NET, JavaScript, and HTML Element ID attributes | Rhyous