Want to follow this site? Here's the RSS feed.

Creating JavaScript objects from ASP.NET objects

September 10, 2007

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>

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 (update: I no longer use this-- now, I use prototype.js from the Prototype JavaScript Framework to replace a lot of my own code):

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 + ' = $(aspNetId)'); 
        }
        else {
            eval('window.' + clientId + ' = $(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 interop code used to register itself with JavaScript; therefore, you aren't really breaking any rules.

In general, you should never, ever place JavaScript in your ASP.NET system.  There are of course some exceptions to this, but the exceptions are based on common sense and decades of interop research from the industry.  Two of the most common exceptions to never having JavaScript in your ASP.NET system are for control generation and for sewing code ("interop code").  Control generation would be when a server-side control creates that which a browser will use in order to protect users (the developers using the control) from the interop between ASP.NET and JavaScript.  That is, to hide the plumbing, thereby increasing the level of abstraction of the system.  The C++ guys deal with the pointers, protecting me from memory management and the ASP.NET/AJAX control creators deal with the JavaScript plumbing so other developers don't have to.  It's the same idea.  Continuing with this analogy, while C# allows unsafe pointers, they should only be used in extremely rare circumstances.  JavaScript in ASP.NET should be about as rare.  One example of this rarity is in reference to the other exception: sewing code.

Sewing code ("interop code"), on the other hand, is exactly what you are seeing this this technique.  It simply connects one technology to another.  One major example of sewing code in the .NET framework is where ADO.NET connects directly to SQL Server.  At some point there must be a connection to the external system and the calling system must speak its language (i.e. SQL).  In the technique here, the interop is between ASP.NET and JavaScript and, as with all interop, sewing is therefore required.  Mixing languages is a very strong sign of poor design skills and a lack of understanding of GRASP patterns.  Many excellent, genius programmers would take their systems to the next level by following this simple, yet profound time tested technique.  Martin Fowler, author of the classic computer science text "Refactoring: Improving the Design of Existing Code" (one of my core books right next to the framework design guidelines!), is often quoted as saying "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."  That's, of course, contextual as people who are complete fools in software design are often 100x better hardcore programmers than the best software designers.

Now, to use the AspNet JavaScript namespace, you simply put code similar to the following somewhere in your ASP.NET page (or the Event.observe function in the Prototype Framework):

<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):

<script type="text/javascript">  
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';
            }
        };
    }
};
</script>

As you can see there is no document.getElementById( ) or $( ) 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 similar 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.

Related Links

Creative Commons License
This work is licensed under a Creative Commons Attribution 2.5 License.

Mini-icons are part of the Silk Icons set of icons at famfamfam.com