Friday, June 12, 2009

ASP.NET 2.0 Master Pages - accessing server side control from JavaScript

Master pages are the latest and greatest addition to ASP.NET 2.0. It helps us build consistent and maintainable user interfaces. But as with every new thing, they are not without their gotchas. One of the most often faced problem is with accessing the server side controls from client side javascript. This is because both the MasterPage and Content controls are naming containers. Naming container is any control that carries the INamingContainer interface and one thing a naming container does is to mangle its children’s ClientID property.Mangling ensures all ClientID properties are unique on a page.

Let us consider the following simple Master-Content page:


So for instance, the ID for our Label control is “MyLabel”, but the ClientID of the Label is "ctl00_BodyContent_MyLabel". Each level of naming container prepends it’s ID to the control (the MasterPage control ID in this form is ctl00). So now if we try to access this control from Javascript with client side script functions like document.getElementById(), it will fail with a JavaScript error: " 'MyLabel' is undefined ".

In the content page, I have two server side controls. One is a Label and another is a Button. On every click of the button I want to display the current time in the label and I want to do it without post-back (obviously!!!) i.e. from client side javascript. So I write a javascript function called SetTime() and attach it to the 'onlick' event of the button.In the SetTime() function we calculate the current time and then set the value to the label control.

As I mentioned earlier if we do MyLabel.innerHTML = _curtime or document.getElementById(MyLabel).innerHTML = _curtime, in both cases we would get the 'MyLabel' is undefined error.

So how to fix the problem?

Solution 1: One possible solution is to directly use the generated client side ID of the Label control.

ctl00_BodyContent_MyLabel.innerHTML =_curtime ;

But as you can see this is really bad practice as we’d never want to hardcode the client ID into a script. Typically we should build the SetTime() javascript function dynamically using StringBuilder or String.Format and emit the complete client script with the ClientScript.RegisterStartupScript() function.
This approach works but only if your functions are small and simple.As your javascript functions becomes more complex this apprach falls apart.

Solution 2: Another alternative is to extend the first approach with use of markers in the script and use a call to String.Replace. Essentially we'll create a client side variable containing the control's ClientID value as follows:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Dim MyLabelID As String = "var MyLabelID = ""{}"";"
MyLabelID = MyLabelID .Replace("{}", MyLabel.ClientID)
ClientScript.RegisterStartupScript( Me.GetType(), "ClientID", MyLabelID, True)
End Sub

Now we can use MyLabelID.innerHTML = _curtime or document.getElementById(MyLabelID).innerHTML = _curtime with out any problem.

So then for all the server side controls which you want to access from client side you can use approach 2 and create a corresponding JavaScript variable and use it in your client script
But what if you have 50 server side controls in your page ? Or you add 50 new server side controls to your page and want to access either all or some of them from client side?Maintenance becomes increasingly difficult as you have to remember to repeat the steps in approach 2 for every server side controls added.

Can we do better? Sure we can.

Solution 3: Let us write a function that will find all the controls in the page and automatically create the corresponding client side javascript variable with the same name.


Call the above function on Page load event. Now if you add another server side label control and name it "MyLabel2" you can directly access it from JavaScript (without the quotes).


No comments:

Post a Comment