Google

Tuesday, October 24, 2006

.Net 2.0 and JavaScript

Up until now, most sites built by ASP.NET developers have had post-backs to manipulate the user interface to provide some feedback to the user. This was probably due to the lack of JavaScript knowledge. Due to recent activity on the use of AJAX, its hard not to need JavaScript in almost any web site today. This brings me to my discovery.

I have been working on a project and my clients most recent request has been to eliminate page refreshes due to post-backs. Of course I said no problem and went about the process of using the ICallbackEventHandler interface to utilize what .NET has provided. To do this requires the writing of many JavaScript functions, which almost all need some reference to one or more controls on the UI. No big deal you say. That's what I said when I started this process. Then I realized that all of the controls have a an ID on the server and an ID on the client side which are different. The problem arose when I needed to reference a text field called "UserID". In the C# code its no problem getting to this object but client side, the "document.getElementById('UserID')" was not returning anything but null.

It took very little time to figure out why this was happening. The control emit a unique id to the client to make sure that all controls are unique. So my "UserID" control ended up with an ID of "ctl00_ctl00_PageContent_Content_UserID". Once I determined this it was easy for the "document.getElementById()" to find the proper control. The only problem was that I was going to have to render every page to find out what the final ID was going to be for each control and if anything changed to alter the hierarchy then that meant the IDs could change.

I was having a field day complaining about why Microsoft would do this and how I was going to create an affective way to handle this. I went through a few different ideas before realizing that all of the control are nested and could be traversed.

I then decided that since I have a master page, that all pages were using, I could use code in the master page to find all controls on any page that used it. Since I know about all of the controls before the page gets rendered, why not emit some JavaScript to reference all of the controls.

First I declared an ArrayList to hold some info:

ArrayList jsControls = new ArrayList();

Then I added some code to my Master Pages "Page_Load" event handler:

this.loadJsControls( this.Page );
jsControls.Sort();
this.Page.ClientScript.RegisterStartupScript(
this.Page.GetType(),
"vars",
string.Join( "", ((string[])jsControls.ToArray(typeof(string)))),
true );

This calls a custom recursive function that loads the ArrayList:

private void loadJsControls( Control control ) {

if ( control is HtmlImage || control is HtmlInputControl ||
control is HtmlContainerControl || control is WebControl ) {

if ( control.ID != null && control.ID.Length > 0 &&
!control.ID.StartsWith( "Image" ) &&
control.ClientID != null &&
control.ClientID.Length > 0 ) {

jsControls.Add(
"var " + control.ID +
" = document.getElementById( \"" +
control.ClientID + "\" );\n" );
}
}

for ( int i = 0; i < control.Controls.Count; i++ ) {
loadJsControls( control.Controls[ i ] );
}
}

Here is an example of the results produced by this:

<script type="text/javascript">
<!--
var changePAsswordLink = document.getElementById(
"ctl00_ctl00_PageContent_header_ctl13_changePAsswordLink" );
var closeLink = document.getElementById(
"ctl00_ctl00_PageContent_header_ctl13_closeLink" );
var exportLink = document.getElementById(
"ctl00_ctl00_PageContent_header_ctl13_exportLink" );
var UserID = document.getElementById(
"ctl00_ctl00_PageContent_Content_UserID" );
// -->
</script>

This allows my JavaScript to not need to look up the control but only know the server side name. Here is an example:

UserID.value = "Joe Blow";

This is possible due to the fact that the JavaScript declarations happen after the page loads so each one already points to the proper control.

I hope this will at least help one other developer.

Friday, June 23, 2006

Leverage the C# Preprocessor

Leverage the C# Preprocessor: "Like other languages in the C-family, C# supports a set of 'preprocessor' directives, most notably #define, #if and #endif (technically, csc.exe does not literally have a preprocessor as these symbols are resolved at the lexical analysis phase, but no need to split hairs…).

The #define directive allows you to set up custom symbols which control code compilation. Be very aware that unlike C(++), C#'s #define does not allow you to create macro-like code. Once a symbol is defined, the #if and #endif maybe used to test for said symbol. By way of a common example:

#define DEBUG using System; public class MyClass { public static void Main() { #if DEBUG Console.WriteLine('DEBUG symbol is defined!'); #endif } }

When you use the #define directive, the symbol is only realized within the defining file. However if you wish to define project wide symbols, simply access your project's property page and navigate to the 'Configuration Properties | Build' node and edit the 'Conditional Compilation "