Click to search the site Click to log in
Online articles
Download free tools
Support pages, per product
Services
Frequently asked questions, per product
Proper Web User Control client-side event hooking through JavaScript
Author: George Mihaescu
Published: March 20, 2007
Category: Implementation technique / ASP.Net 2.0 / JavaScript
Notes: Tested on .Net 2.0 / IE 6 & 7 / Firefox 1.5 & 2
Description: This article describes the correct way a Web User Control should hook up its JavaScript handler(s) to events in the page in which it lives - without disturbing the page (or other controls') own handlers of the same event. Also known as "event handler chaining".
View count: 6,959
Comments: 3 Read comments or post your own

  Print viewOpens in new window
 Proper Web User Control initialization through JavaScript using document

Proper Web User Control client-side event hooking through JavaScript

 

By George Mihaescu

 

Summary: Shows how to properly implement ASP.Net Web User Control client-side event hooking through JavaScript so that the control can co-exist in pages that user other controls with a similar requirement.

Tested using ASP.Net 2.0 on IE 6.0 and 7.0 and Firefox 1.5 and 2.0.

 

The problem

You are implementing a Web User Control which you intend to use in many pages of many sites (and even possibly sell to ASP.Net developers). Your control however needs to have some initialization done when the document is loaded. No problem, you think, you simply add a JavaScript line in your control’s aspx:

 

document.onload = control_initialization_js_function;

 

Of course, this will work… only if the page using the control needs only one such control. If the page happens to use multiple instances of this control or different controls that have the need to initialize on the document onload, handler, then the last one to hook the event wins, and all the others will stay un-initialized. And this is valid for any UI event-hooking the control needs to do.

 

The bottom line is that a Web User Control that needs to hook in any client-side event handling must do so without disrupting whatever such even handling the using page (or other controls with similar needs) might already have in place.

 

The solution

Of course, there is always leaving this issue to the caller. Your control has an InitControl JavaScript function and you simply document that it is the control user’s responsibility to make sure this function gets called in the document’s onload handler. This low-tech solution is not particularly nice to the user of your control (to whom you’re actually passing a buck that belongs to you) and may not even work in more complex scenarios (where you need to temporarily hook an event, then later un-hook it).

 

A much better solution would be one in which the control is self-contained; the control user just drops it on his page and sets its properties just like he does with any other control, without having to do any special JavaScript trick the control may need. The control takes care of hooking the events itself. We achieve this through JavaScript event chaining (for lack of a better term), a simple technique through which we dynamically create a handler for the event in question, and this handler’s body always calls the previous handler that was hooked for that event (if there was one).

 

If each control used in a page uses this technique, their handlers will “chain”, resulting in all the handlers being called. For example, assume that the page has its own onload handler called page_handler and we have three controls, each with its own handler: control1_handler, control2_handler and control3_handler, respectively. Then the technique I described is the equivalent of having the following handler:

 

<script language="javascript" type="text/javascript">

 

function conceptual_handler ()

{

    page_handler ();

    control1_handler ();

    control2_handler ();

    control3_handler ();

}

</script>

 

<body onload="conceptual_handler ();">

...

</body>

 

So how does this actually happen? Below is an example consisting of a master page and two Web user controls, which the master page uses. The master page and each control has its own onload handler, and each control correctly chains its handler on the window.onload.

 

UserControl1.aspx:

 

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="UserControl1.ascx.cs" Inherits="UserControl1" %>

 

<script language="javascript" type="text/javascript">

 

function control1OnLoadHandler()

{

    alert ("JS: initializing user control 1");

}

 

function addLoadEventHandler(func)

{    

    var previous_handler = window.onload;

    if(typeof window.onload != "function") window.onload = func; 

    else window.onload = function()

    {            

        previous_handler ();             

        func();        

    }

}

 

//add this control's onLoad handler to the document's onload, without

//replacing whatever handler may be there already

addLoadEventHandler (control1OnLoadHandler);

 

</script>

 

<asp:Label ID="Label1" runat="server" Text="This is a label in UserControl1"></asp:Label>

 

 

UserControl2.aspx:

 

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="UserControl2.ascx.cs" Inherits="UserControl2" %>

 

<script language="javascript" type="text/javascript">

 

function control2OnLoadHandler()

{

    alert ("JS: initializing user control 2");

}

 

function addLoadEventHandler(func)

{    

    var previous_handler = window.onload;

    if(typeof window.onload != "function") window.onload = func; 

    else window.onload = function()

    {            

        previous_handler ();             

        func();        

    }

}

 

//add this control's onLoad handler to the document's onload, without

//replacing whatever handler may be there already

addLoadEventHandler (control2OnLoadHandler);

 

</script>

 

<asp:Label ID="Label1" runat="server" Text="This is a label in UserControl2"></asp:Label>

 

 

MasterPage.master:

 

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>

<%@ Register Src="UserControl1.ascx" TagName="UserControl1" TagPrefix="uc1" %>

<%@ Register Src="UserControl2.ascx" TagName="UserControl2" TagPrefix="uc2" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

 

<head runat="server">

    <title>Untitled Page</title>

   

    <script type="text/javascript" language="javascript">

   

    function masterPageOnLoadHandler ()

    {

        alert ("JS: init the master page called");

    }

   

    </script>

</head>

 

<body onload="javascript:masterPageOnLoadHandler ();">

 

    <form id="form1" runat="server">

   

    This is the master page content.<br /><br />

   

    <uc1:UserControl1 ID="UserControl1_1" runat="server" /><br /><br />

   

    <uc2:UserControl2 ID="UserControl2_1" runat="server" /><br /><br />

   

    <div>

        <asp:contentplaceholder id="ContentPlaceHolder1" runat="server">

        </asp:contentplaceholder>

    </div>

    </form>

</body>

</html>

 

The trick is actually in the addLoadEventHandler function which checks to see whether there is already a handler registered on the event we care for (here, onload):

·         if not, registers as handler the function that it received as parameter

·         if yes, dynamically creates a new handler that it registers for the event, and the new handler first calls the previously registered handler, than the function that was passed as parameter.

 

Then this addLoadEventHandler function is called having as parameter the function the control needs to register as event handler. This basically creates the logical “function chain” having the same effect as the conceptual_handler shown before.

 

For the sake of completeness, to have each of the controls even better behaved, its JavaScript code should be placed in its own namespace. This ensures that there are no clashes among the JavaScript functions used by various controls within a page – this becomes critical if you are implementing a suite of such controls for distribution to other ASP.Net developers:

 

 

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="UserControl2.ascx.cs" Inherits="UserControl2" %>

 

<script language="javascript" type="text/javascript">

 

var JSControl2 = {};    //JS namespace for this control

 

JSControl2.control2OnLoadHandler = function ()

{

    alert ("JS: initializing user control 2");

}

 

JSControl2.addLoadEventHandler = function (func)

{    

    var previous_handler = window.onload;

    if(typeof window.onload != "function") window.onload = func; 

    else  window.onload = function()

    {            

        previous_handler();              

        func();        

    }

}

 

//add this control's onLoad handler to the document's onload, without

//replacing whatever handler may be there already

JSControl2.addLoadEventHandler (JSControl2.control2OnLoadHandler);

 

</script>

 

<asp:Label ID="Label1" runat="server" Text="This is a label in UserControl2"></asp:Label>

 

 

 

Shortcomings: only one that I can see so far, and a minor one for that matter: you have no control over the order in which the handlers of the controls are called. The mechanism described above basically calls the handlers in the order in which the controls appear in the page.

But if the controls need to have their handlers called in a specific order (e.g. must first call the handler for control 4, then 2, then 3, and only at the end for control 1) then there is something wrong with the design of the controls and / or page that contains them. Controls should not be aware or affected (in their behaviour) by the presence of other controls on the page – after all, this is exactly why we’ve gone through all the trouble above: to create encapsulated, self-contained controls that take care of hooking in their environment automatically if they need to.

 


Reader comments:
Name: (optional)
Verification text:    
(type as in image next to it)
Comment: max 2,000 characters; for security reasons no active content / no HTML formatting is supported.
Please stick to the subject of the article; comments are reviewed and unrelated / inappropriate ones will be deleted.

On Jun 30, 2008 at 17:35 EST USA, New York said:

Very nice resource - thank you.

On Apr 22, 2008 at 9:20 EST S@ntosh Kumbh@r said:

Hi... Thanks for the article. I am trying to implement the above to handle multiple dropdown onchange events. The flow is as follows I have a two custom controls with four dropdowns and which are filled using web service called through javascript . The problem that i am facing is as explained by you according to my understanding , i.e. Even if i register both the user controls on the page, only the last one on the page is registered and same is applied to the previous.This results in firing of the last user control events independent of whether you select items from 1st user control or second user control on the page. I need some guidance on this. Thanks in advance.

On Apr 17, 2007 at 16:56 EST Anonymous said:

Oh, cool. I did not realize that you can do stuff like that with javascript (I'm more on the server-side). But with user controls and all, now you need to do both. I like the fact that the control can hook itself in the page that contains it without any intervention from me
Copyright 2308 registered users, 18 users online now