Understanding ASP.NET Ajax Callbacks, Delegates and DomEvents

Last night I had some feedback about my recent article about Adding Verbs to the InfoBox for Virtual Earth.  The question that was asked was, why when I wired up the mouseover event for the map, did I use an ASP.NET Ajax Delegate like this

mapMoveDelegate = Function.createDelegate(this, this._MapMoveHandler);
map.AttachEvent("onmouseover", mapMoveDelegate);

As opposed to simply assigning the function to the map like so:

map.AttachEvent("onmouseover", _MapMoveHandler);

To answer this question we first need to learn a bit about the difference between normal function callbacks and Delegates.

In this brave new world of client-centric web development with ASP.NET Ajax, the trend is increasingly towards an OO model where we encapsulate the logic for components inside individual classes.  An example of where I do this is by creating my own Javascript Map class which wraps the Virtual Earth VEMap class.  I do this so that all of my messy coded and cleanup code is all packaged neatly within its own class – and not spewing out into the Javascript of the calling page.  So this model of having classes for discrete components makes the paradigm much closer to the C# model of coding.

Using delegates to control the scope of ‘this’

Within classes it is common to wire up events – just as we would in C# – to handlers within our class.  And it’s from within these handlers that things start to get interesting – and it has to do with ‘state’.  Consider the following snippet of code:

this.Message = "Hello" ;

function pageLoad() {
    map = new VEMap(‘theMap’) ;
    map.LoadMap() ;
    map.AttachEvent("onmouseover", _Alert);
}

function _Alert() {
    alert(this.Message) ;
}

The code simply creates an instance of the VEMap class and wires up an event handler to the mouseover event.  From within the handler function we do an alert to display the value of this.Message.  When we run the code and place our mouse over the map the alert fires and the following message is displayed:

EventHandlers1

Now, if you are used to server-side ASP.NET programming then this is going to seem odd as you would have expected the alert to display the value of this.Message instead of ‘undefined’.  The problem is that, from within the _Alert function, ‘this’ refers to the _Alert function and not the ‘class’ that contains it.  So this differs from the OO experience that we are used to getting from our normal .NET classes.

This is where the ASP.NET Ajax delegates come to the rescue.  From the above snippet of code, change the line where we wire up the event handler so that our pageLoad method now looks like this:

function pageLoad() {
    map = new VEMap(‘theMap’) ;
    map.LoadMap() ;

    var d1 = Function.createDelegate(this, _Alert);
    map.AttachEvent("onmouseover", d1);
}

Re-run the page and this time we see the following result:

EventHandlers2

So using the Function.createDelegate method call has enabled us to exert control over the scope of what ‘this’  is within our handling code.  And because of this, we get access to our class instance from the ‘this’ object within our handling code.

Using the Sender and EventArgs style of handling events

Having our handling methods running in the context of our class is a good thing because it helps our code remain properly encapsulated because, without it, we’d have to revert to passing state around in globally-scoped variables – and we don’t want that.  Having the state for our classes remain within them is how properly encapsulated code should be.

There’s still an issue though because, even within the scope of our class, there will be times when we want to pass some additional context to the handling functions.  In the .NET world this problem is solved by the EventArgs class.  I’m sure that anybody who has written .NET code is very familiar with the Sender and EventArgs signature of event handling code.

In ASP.NET Ajax, callbacks allow us to mimic this model as the Function.createCallback method allows us to supply a ‘context’ parameter.  Change the code over to use a callback so that we can pass some event arguments into the handling function:

function pageLoad() {
    map = new VEMap(‘theMap’) ;
    map.LoadMap() ;
    var context = {extraData: "extra stuff here"} ;
    var cb = Function.createCallback(_Alert, context) ;
    map.AttachEvent("onmouseover", cb);
}

function _Alert(sender, e) {

    var s = String.format(
        "this.Message=’{0}’, extra data=’{1}’",
            this.Message,
            e.extraData
            ) ;
    alert(s) ;
}

Notice that this time I’ve used the same Sender and EventArgs signature that we are familiar with from our .NET experiences.  Now when we re-run the page we get the following result:

EventHandlers3

While it’s good to see that our extra context data has passed through OK, we now see that the ‘this’ object no longer gains us access to our class instance and therefore the call to ‘this.Message’ shows up as an empty string.  We’re kinda back to where we started again.

What I’ve been doing to get around this is to store the class instance in my EventArgs class like so:

var context = {me: this, extraData: "A"} ;

…and then access the me property like so:

function _Alert(sender, e) {

    var s = String.format(
        "this.Message=’{0}’, extra data=’{1}’",
            e.me.Message,
            e.extraData
            ) ;
    alert(s) ;
}

We again re-run the page and this time all of our data is in place:

EventHandlers4

e.me.Message is obviously not the most elegant solution if you are used to the .NET model, but it works and we get properly encapsulated access to our data.  For the price of a little syntactical gymnastics we’ve removed the larger cost of having additional state variables lying all over the place.

So what about ‘sender’?

We’ve got our head around delegates and callbacks and the differences between each and we’ve seen how we can create complex EventArgs types to pass information to our event handlers.  The last thing that I’d like to look at is the ‘sender’ argument that gets passed to our handler function, because there’s some subtle scenarios that can be played out here as well.

For my next example I’ll move away from the VEMap control and use a simple HTML button element and wire-up a delegate (this could just as easily be a callback if we wanted to pass context data):

<input type="button" value="btn1" id="myButton" />

var del = Function.createDelegate(this, _Alert) ;
$get("myButton").attachEvent("onclick", del);

In the _Alert handler function, we’ll add the ‘sender’ argument to show that it represents an instance of the control that invoked the event.  In our case this will be the button control, so we’ll display it’s ID from our alert:

function _Alert(sender) {
    alert(sender.srcElement.id) ;
}

Sure enough, running the example displays the following result:

EventHandlers5

Just as we expected, the ID of our button is displayed.  The main thing to be aware of here is that the ‘sender’ argument is an instance of a HTML DOM event and the srcElement property that we referred is an HTML DOM element – the same sort of DOM element that we get from the following call:

var e = document.getElementById("myButton") ;

This works but with ASP.NET Ajax there’s a better way.

The cross-browser DOM – DomElement and DomEvent’s

The problems with HTML DOM and DHTML are very well documented.  For years we’ve had to deal with the different way that browsers implement DOM events and elements and as a result, we’ve had to write conditional code that compensates for these differences.  With ASP.NET Ajax we get the DomElement and DomEvent classes which provide us with consistent API and programming access to the DOM.  With this in mind, it’s important to understand that there is a better way of wiring up our event handlers so that the ‘sender’ that we receive is a cross-browser friendly instance of the Sys.UI.DomEvent class.  The way to do this is the use the DomEvent’s addHandler method to attach the event handler like so:

var del = Function.createDelegate(this, _Alert) ;
Sys.UI.DomEvent.addHandler($get("myButton"), "click", del);

And change our _Alert handler to reference the ‘target’ property of the DomEvent like so:

function _Alert(sender) {
    alert(sender.target.id) ;
}

While the result that get’s displayed to the screen appears to be the same, we are now dealing with instances of the event and element that provide a consistent model for working with multiple browsers, thus making it easier to perform things such as positional logic and styles manipulation.

Finally, the ASP.NET team have also provided us with a global shortcut for accessing the Sys.UI.DomEvent.addHandler method with the $addHandler and $addHandlers shortcuts.  So the following shorter syntax is 100% equivalent with the longer version:

$addHandler($get("myButton"), "click", del) ;

Conclusion

In this article we’ve seen the differences between callbacks and delegates and witnessed their impact on context and state.  We saw that while Function.createCallback does not allow us to keep the same call context as does Function.createDelegate but that it gives us access to the Sender and EventArgs model that we are familiar with to pass additional contextual data to event handler functions.

Finally we saw the subtle differences between the event type that we receive from when we bind the event handler directly onto the HTML DOM element using the standard attachEvent call as opposed to the more cross browser consistent version that we get when we use the $addHandler approach.

Happy coding!

About these ads

~ by D on November 7, 2007.

24 Responses to “Understanding ASP.NET Ajax Callbacks, Delegates and DomEvents”

  1. fantastic article.  I have been struggling to get my head around these concepts but you make it very clear and understandable thanks!

  2. First, fantastic article. I really like the step-by-step instructions. Building up each concept and culminating with the DomElement and DomEvent seems to really flow well.Second, I have a more elegant way to maintain \’this\’ when using callbacks other than "me". Use a delegate as the function in the callback.Something like …var del = Function.createDelegate(this, _Alert);var context = "123";var cb = Function.createCallback(del, context);…when _Alert executes, this will be whatever was the first parameter in the delegate. Happy Coding!

  3. A friend to buy wow goldTo wow power leveling?On the world\’s most concessions to the most reputable sites under the single!

  4. Hi,Do you need digital signage, digital sign, ad players and ad displays? Please go Here:www.amberdigital.com.hk(Amberdigital).we have explored and developed the international market with professionalism. We have built a widespread marketing network, and set up a capable management team dedicated to provide beyond-expectation services to our customers.
    amberdigital Contact Us
    website:www.amberdigital.com.hk
    alibaba:amberdigital.en.alibaba.com[cdbadfjheabdah]

  5. Hi,Do you need advertising displays, digital signages, advertising player and LCD displays? Please go Here:www.amberdigital.com.hk(Amberdigital).we have explored and developed the international market with professionalism. We have built a widespread marketing network, and set up a capable management team dedicated to provide beyond-expectation services to our customers.
    amberdigital Contact Us
    website:www.amberdigital.com.hk
    alibaba:amberdigital.en.alibaba.com[dejbbbbjbhedbaj]

  6. no spam

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: