If you have worked with ASP.NET for any length of time you probably know that the ASP.NET ID you set on a control on the server-side changes when it gets to the client side. For example, if you have a textbox with an ID of "txtUsername" in ASP.NET, you will probably have a textbox with an ID of something like "ctl100_txtUsername". When working only with server-side code, this is fine. However, I'm a JavaScript programmer as well as a .NET programmer. Most of my applications are heavily Ajax based and sometimes the entire application through all of its screens and uses will have ZERO postbacks. So, it's important for me to have the correct ID on the client. So, I need to be able to access controls on the client-side. Not only so I can access the ID from a JavaScript functions, but also so I can set loosely-coupled events on objects.
Typically the way people get around this is with simple, yet architecturally blasphemous techniques. The first technique is to break a foundational rule of software architectural (e.g. low-coupling) by putting an event right on the element itself. That is, they hard code the event they want to raise right on the control itself. This is a very strange technique as the .NET developers who do this technique are usually thos wwho would never put a server-side event on a control using OnServerClick. Somehow, they think that putting an even directly on a client-side control by OnClick is less wrong. This is obviously a case of extremely object coupling, an extremely poor architectural practice. In case you can't picture it, here's what I'm talking about:
<asp:TextBox id="txtUsername" runat="server" Text="Username" OnClick="ClearBox( );"></asp:TextBox>
A much, much better way of getting around this is to use the ClientID property of an ASP.NET control to assign a multi-cast JavaScript event to that button. However, we must be careful with this technique as it too could lead to design problems. The most obvious problem is that of spaghetti code, the mixing of two or more languages in one same file. Professional ASP.NET developers know that to have a sound system, you must be using code-behinds. The ASP.NET development model greatly improves the readability of code by making sure that the C# (or VB) code and the ASP.NET declarations are completely separate. While reading one page, your brain doesn't need to be flipping all over the place trying to translate multiple languages at the same time. To be sure, those of us from the PHP world know that with time you can become very proficient in developing in spaghetti code, but, on the other hand, those of us who have taken over a project from another person know the pains of trying to decode that slop.
The typical technique for applying loosely-coupled events (and for many other JavaScript functionality) is actually very strange. Though the ASP.NET developers will insist on a separation for their C# (or VB) away from their ASP.NET pages, they have no problem throwing JavaScript in the midst of C# code. This is almost as bad as putting ad-hoc SQL queries in your C# code (very bad) or coupling CSS rules to an element via the HTML "style" attribute, thereby making the solution absolutely impossible to theme and breaking any chance of debugging CSS problems (very, very bad). JavaScript and CSS have had a code-behind model long before ASP.NET was around. So, we need to respect the practices of code separation as much as possible. To this end, we need a better solution than throwing a large block of JavaScript in to an ASP.NET page.
Here is an example of the old technique using legacy JavaScript (in contrast to Modern JavaScript shown in a bit):
<script type="text/javascript">
function ClearBox( ) {
document.getElementById(<%=txtUsername.ClientID%>).value = '';
}
document.getElementById(<%=txtUsername.ClientID%>).onclick = ClearBox;
</script>
<asp:TextBox id="txtUserName" runat="server" Text="Username"></asp:TextBox>
Typically, however, you will see a TON of JavaScript code simply thrown into the page with no respect for code separation and with no possibility for multicast events. (Furthermore, not only is this code raw spaghetti code, that function isn't even in a JavaScript namespace. Please see my link below for more information on JavaScript Namespaces; If you are familiar with .NET namespaces, then you have a head start on learning JavaScript namespaces. Would you ever throw a class into an assembly that without putting it in a namespace? Probably not... it's the same idea in JavaScript.)
Fortunately, there is a better model using a couple of JavaScript files. The first JavaScript file (Event.js) is one of my standard files you will see in all of my JavaScript applications:
var Event = {
Add: function (obj, evt, func, capture) {
if(obj.addEventListener) {
obj.addEventListener (evt, func, capture);
}
else if(obj.attachEvent) {
obj.attachEvent('on' + evt, func);
}
},
Remove: function (obj, evt, func, capture) {
if(obj.removeEventListener) {
obj.removeEventListener (evt, func, capture);
}
else if(obj.detachEvent) {
obj.detachEvent('on' + evt, func);
}
}
}
This Modern JavaScript document, simply allows you to add or remove events from an object. It's fairly simple. Here's a file (AspNet.js) you will find in some of my applications:
var AspNet = {
Objects: new Object( ),
RegisterObject: function(clientId, aspNetId, encapsulated) {
if(encapsulated) {
eval('AspNet.Objects.' + clientId + ' = D(aspNetId)');
}
else {
eval('window.' + clientId + ' = D(aspNetId)');
}
}
};
This one here is where the meat is. When you call the RegisterObject function you will actually register an ASP.NET control with JavaScript so that you can use it without needing the fancy ASP.NET ClientID. Furthermore, it also allows you to use the object directly in JavaScript without relying on document.getElementById( ). This technique is actually a cleaner version of the one I previously mentioned. It does require you to put a little JavaScript in your page, but that's OK as it's ASP.NET specific code used to register itself with JavaScript; you aren't really breaking any rules here. To use the above JavaScript namespace , you simply put code similar to the following somewhere in your ASP.NET page:
<script type="text/javascript">
Event.Add(window, 'load', function(evt) {
// ASP.NET JavaScript Object Registration
AspNet.RegisterObject('txtUsername', '<%=txtUsername.ClientID%>');
AspNet.RegisterObject('txtPassword', '<%=txtPassword.ClientID%>');
Initialization.Init( );
}, false);
</script>
Basically, when the page loads your objects will be registered. What does this mean? It means you can use the object as they are used in this Initialization.js file (another file in all of my JavaScript projects):
var Initialization = {
Init: function( ) {
txtUsername.onclick = function(evt) {
if(!txtUsername.alreadyClicked) {
txtUsername.value = '';
txtUsername.alreadyClicked = true;
}
};
txtPassword.onclick = function(evt) {
if(!txtPassword.alreadyClicked) {
txtPassword.value = '';
txtPassword.alreadyClicked = true;
txtPassword.type = 'password';
}
};
}
};
As you can see there is no document.getElementById( ) here. You are simply naturally using the object as if it were strongly typed. The best part is that to support another ASP.NET page, you simply have to put a similiar JavaScript script block in that page. That's it. Furthermore, if you don't want to access the control directly, perhaps because you are worried about potential naming conflicts you can send a boolean value of true as the third argument in the AspNet.RegisterObject function, this will put the objects under the AspNet.Objects namespace. Thereby, for example, making txtUsername accessible by "AspNet.Objects.txtUsername" instead of simply "txtUsername".
There is one catch though: you have to assign events to your window.load event using multi-cast events. In other words, if at any point you assign an event directly to the window.load event, then you will obviously overwrite all events. For example, the following would destroy this entire technique:
window.load = function(evt) {
// Do something...
}
This should not be a shocker to C# developers. In C#, when we assign an event we are very careful to make sure to assign it using the "+=" syntax and not the "=" syntax. This the same idea. It's a very, very poor practice to ever assign events directly to the window.load event because you have absolutely no idea when you will need more than one event to call more than one function. If your MasterPage needs the window.load event, your Page needs the window.load event, and a Control needs the window.load event, what are you going to do? If you decide you will never need to do multicast events on load and then get a 3rd party tool that relies on it, what will you do when it overrides your load event or when you override its? Have fun debugging that one. Therefore, you should always use loosely-coupled JavaScript multi-cast events for window.load. Furthermore, it's very important to following proper development practices at all times and never let deadlines stop your from professional quality development.