A dissection of the ASP.NET AJAX library – Part 2 The javascript language extensions advanced concepts

This is the second in a series of articles in which I intend to dissect the ASP.NET Ajax library. I’m relative new to this modern type of javascript programming so I decided to look into the ASP.NET Ajax library to see how certain things are done. This is a report of my voyage.

More advanced concepts of the javascript language used

In the next few paragraphs I will explain some of the more advanced concepts of the javascript language that are being used by the ASP.NET AJAX library. I will not provide a tutorial on the javascript language, you can find a lot of them on the internet, but I will provide some explanation on the more advanced features used.

Delegates, why?

What is the problem?
In javascript in the browser, the DOM elements expose events which trigger the handler you attached.

If we want to simulate that, we get the following:
We define a class and add to it a property, which when executed simulates the event triggering.

// Define a class

MyClass = function()

{

}

 

// Defining a function which symbolizes the standard

//  eventhandler and attach it to the class

function DoSomething()

{

    alert(“DoSomething saying:'” + this._aProperty + “‘”);

}

 

MyClass.prototype._aProperty = “Hi, it’s me: MyClass”;

MyClass.prototype.SomethingTodo = DoSomething;

 

 

// Make an object and call the function on the object

//  which is what an event triggering does, simply call

//  the eventhandler

anObject = new MyClass();

anObject.SomethingTodo();

If we execute this piece of code, we get, as expected, a messagebox saying “DoSomething saying:’Hi, it’s me: MyClass'”.

Now, when using DOM elements you attach your own eventhandlers. You do this using code like the following:

// If you attach your own event handler, what you actually do

//  is attach your own function to the SomethingTodo property

WrongClass = function(myClassInstance)

{

    myClassInstance.SomethingTodo = this.MeDoingSomething;

}

 

WrongClass.prototype._someValue = “Hi, it’s me: WrongClass”;

WrongClass.prototype.MeDoingSomething = function _MeDoingSomething()

{

    alert(“MeDoingSomething saying:'” + this._someValue + “‘”);

}

 

wrongClass = new WrongClass(anObject);

// When the event is triggered, your handler gets called

//  but within the context of the anObject object which

//  doesn’t have a _someValue property

anObject.SomethingTodo();

If we execute this piece of code, we get, maybe unexpected, a messagebox saying “MeDoingSomething saying:’undefined'”.

So what is going on here?

What we are actually doing with this code is add a reference to the _MeDoingSomething() function through the reference owned by the wrongClass object. Thus, the property SomethingTodo of our anObject object no longer points to it’s standard DoSomething() function but now points to the _MeDoingSomething() function.
If we execute this function, we execute it in the context of the anObject object. Or stated another way: the this pointer inside the function _MeDoingSomething now points to the anObject object.
And because this object doesn’t have a property _someValue defined we get the text ‘undefined’ inside our message.

What to do about it?

We should be able to preserve the context in which we want to call the function and then call our function in that preserved context.

Enter the javascript Function object’s call() method.

The call() method of the javascript Function objects allows you to supply the object on which to call the function. And because your method is of type Function it implements this call method. So, how can we use this call() method to our advantage?

We dynamically create a function which calls our function’s call() method with the object we want:

RightClass = function(myClassInstance)

{

    _me = this;

    myClassInstance.SomethingTodo = function() {

            RightClass.prototype.MeDoingAnotherthing.call(_me);

        }

}

 

RightClass.prototype._someValue = “Hi, it’s me: RightClass”;

RightClass.prototype.MeDoingAnotherthing = function()

{

    alert(“MeDoingAnotherthing saying:'” + this._someValue + “‘”);

}

 

rightClass = new RightClass(anObject);

// When the event is triggered, your handler gets called

//  but within the context of the rightClass object which

//  has a _someValue property

anObject.SomethingTodo();

Inside the construcotr function of our class RightClass, we create a function which takes the function RightClass.prototype.MeDoingAnotherthing and calls the call() method on it, with a reference to our this pointer as an argument. So if we execute this dynamicaly created function, we actually call the RightClass.prototype.MeDoingAnotherthing function on the rightClass object.

And this shows us the message we want: “MeDoingAnotherthing saying:’Hi, it’s me: RightClass'”.

Delegates and Callbacks in the ASP.NET Ajax library

ASP.NET Ajax makes a difference between Callbacks and Delegates.

The main difference between the two is that delegates allow you to preserve the object on which to call the method, while callbacks allow you to presreve the arguments to be applied to the method.

Let’s investigate this a bit closer.

Using delegates as implemented by the ASP.NET Ajax library

Delegates are used to solve the problem as shown above: they preserve the object on which the method is called.

How do i do it?
Following as some code that uses delegates:

// Register the namespace in which the class exists

Type.registerNamespace(“HFK.Demo.Extra”);

 

// This class represents the class which exposes an event

HFK.Demo.Extra.Caller = function()

{

}

 

HFK.Demo.Extra.Caller.prototype = {

 

    TheEvent : function() {

    },

 

    AnotherEvent : function() {

    }

};

 

// This class will provide an eventhandler

HFK.Demo.Extra.Provider = function()

{

    _name = “TheProvider”;

}

 

HFK.Demo.Extra.Provider.prototype = {

 

    OnTheEvent : function() {

        alert(_name + ” was called”);

    },

 

    OnAnotherEvent : function(anArgument) {

        alert(_name + ” was called with an argument ‘” + anArgument + “‘”);

    }

 

};

 

theProvider = new HFK.Demo.Extra.Provider();

 

theCaller = new HFK.Demo.Extra.Caller();

// Create a delegate and attach it to an event

theCaller.TheEvent = Function.createDelegate(theProvider,

                                theProvider.OnTheEvent);

theCaller.AnotherEvent = Function.createDelegate(theProvider,

                                theProvider.OnAnotherEvent);

 

// Calling the delegate

theCaller.TheEvent();

// Calling a delegate with an argument

theCaller.AnotherEvent(“An Argument”);

We first create two classes:

  • A class which will use the delegate
  • A class that will provide the delegate

Next, we create our delegates and attach them to the calling object.
At last we call the delegate.

Simple, but…

How do they do it?

Function.createDelegate = function Function$createDelegate(instance, method) {

 

    return function() {

        return method.apply(instance, arguments);

    }

}

As you can see, they do it exactly as we do (or actually, the other way around: we do it…)
The only difference is that they use the apply() method which allows to pass the arguments in an array.

Using callbacks as implemented by the ASP.NET Ajax library

Callbacks allow you to preserve the arguments with which a function is called.

How do i do it?

function ShowMeTheMessage(contextDictionnary) {

    alert(“this is “ + contextDictionnary.who +

        ” saying: ‘” + contextDictionnary.message + “‘”);

}

 

var singleArgumentCallback = Function.createCallback(ShowMeTheMessage,

                                {who : “Me”,

                                message: “the Message”});

singleArgumentCallback();

First, define a function for which you will make a callback.
Next, you create the callback by suppplying you function and an argument that will be passed to your function. Unfortunately, you can only supply one argument, so you must use a dictionary if you want to supply more arguments, which is what i did in the example.
Then we call the callback using the normal function calling syntax, but with out passing any arguments.

As you can see, your function has been called with the arguments supplied at the time of reation of the callback.

How do they do it?

Function.createCallback = function Function$createCallback(method, context) {

 

    return function() {

        var l = arguments.length;

        if (l > 0) {

                        var args = [];

            for (var i = 0; i < l; i++) {

                args[i] = arguments[i];

            }

            args[l] = context;

            return method.apply(this, args);

        }

        return method.call(this, context);

    }

}

You can see that the technique used to save the state is the same as used in the delegate implementation. Instead of saving the object on which the method is called, the arguments with which the method is called is saved inside a function definition.

From the code you can also deduce following important facts:

  • Only one argument can be preserved.
  • If you supply extra arguments to the callback, the last argument passed to your function is the preserved argument. A new array is created with the arguments passed to the callback and at the end the preserved argument is added. Then your function is called with its apply() method and is passed this new array.
  • The returned function calls the method on the this pointer. This means you’ll have to use a delegate if you also want to preserve the calling object.

These conlusion lead to following code samples:

Deduction 1: Only one argument can be preserved.

function ShowMeTheMessage(who, message) {

    alert(“this is “ + who +

        ” saying: ‘” + message + “‘”);

}

 

who = “Me”;

message = “the Message”;

var multiArgumentCallback = Function.createCallback(ShowMeTheMessage, who, message);

multiArgumentCallback();

Because only one agument can be preserved the above code does not work.

Deduction 2: If you supply extra arguments to the callback, the last argument passed to your function is the preserved argument.

function ShowMeTheMessageMultiargument(who, message) {

    alert(“this is “ + who +

        ” saying: ‘” + message + “‘”);

}

 

who = “Me”;

message = “the Message”;

var multiArgumentCallback = Function.createCallback(ShowMeTheMessageMultiargument, message);

multiArgumentCallback(who);

As a result of this, creation of callbacks is not really transparent to your code. Your method for which you create a callback will have to be aware of this when:

  • multiple arguments will be passed in the callback. You will have to pack them in a dictionnary on creating the callback and read them from the dictionnary in your method for which you use a callback. (See the above code demonstrating the single argument callback)
  • your method for which you create the callback receives multiple arguments on creating the callback and one or more arguments on calling the callback. In this case, accessing the arguments will be different:
    • The arguments passed on calling the callback can be accessed as regular arguments
    • The arguments passed on creating he callback must be accessed using a dictionnary

    You can not create a method receiving 4 arguments and decide to once pass the first two arguments on calling and he last two arguments on creation, and then at another time pass the first argument on calling and pass the last three arguments at creation without using some intermediary function.

    function AFourArgumentFunction(arg1, arg2, arg3, arg4)

    {

        alert(“You called me with arg1[“ + arg1 + “], arg2[“ + arg2 + “], arg3[“ + arg3 + “], arg4[“ + arg4 + “]”);

    }

     

    // This will not give you what you want …

    var lastTwoArgumentCallback = Function.createCallback(AFourArgumentFunction,

                                    {arg3 : “A3”,

                                    arg4: “A4”});

    lastTwoArgumentCallback(“A1”, “A2”);

    // … and neither will this

    var lastThreeArgumentCallback = Function.createCallback(AFourArgumentFunction,

                                    {arg2 : “A2”,

                                    arg3 : “A3”,

                                    arg4: “A4”});

    lastThreeArgumentCallback(“A1”);

     

    // This intermediary will to the trick in one case …

    function LastTwoArgumentIntermediary(arg1, arg2, contextDictionary)

    {

        AFourArgumentFunction(arg1, arg2, contextDictionary.arg3, contextDictionary.arg4);

    }

    var lastTwoArgumentIntermCallback = Function.createCallback(LastTwoArgumentIntermediary,

                                    {arg3 : “A3”,

                                    arg4: “A4”});

    lastTwoArgumentIntermCallback(“A1”, “A2”);

     

    //  … this in the other case

    function LastThreeArgumentIntermediary(arg1, contextDictionary)

    {

        AFourArgumentFunction(arg1, contextDictionary.arg2, contextDictionary.arg3, contextDictionary.arg4);

    }

    var lastThreeArgumentIntermCallback = Function.createCallback(LastThreeArgumentIntermediary,

                                    {arg2 : “A2”,

                                    arg3 : “A3”,

                                    arg4: “A4”});

    lastThreeArgumentIntermCallback(“A1”);

Deduction 3: The returned function calls the method on the this pointer.

// You define properties and methods using the object

//  literal syntax on the prototype object of you newly defines class object

HFK.Demo.Dog.prototype = {

 

    getName : function() {

        return this._firstName;

    },

 

    Bark : function(context) {

        alert(this._name + ” says: ‘Arff’ at “ + context.target);

    },

};

 

// register the class to the ASP.NET Ajax library …

HFK.Demo.Dog.registerClass(‘HFK.Demo.Dog’);

// … and instantiate an object of it

aDog = new HFK.Demo.Dog(“Fido”);

 

// This shows ‘undefined’ for the dogs name

var theDogBarksWrong = Function.createCallback(aDog.Bark, {target: “me”});

theDogBarksWrong();

 

var theDogBarksRight = Function.createCallback(Function.createDelegate(aDog, aDog.Bark), {target: “me”});

theDogBarksRight();

Because the callback is called on the this pointer, the above code shows ‘undefined’ as the dogs name when not using a delegate.

Resources:

[1] Create Client Controls in ASP.NET 2.0 AJAX 1.0 Extensions
[2] Creating Custom Client Events
[3] Not Delegates < (a Weblog by Jason Diamond)
[4] Function.call and Function.apply – JavaScript
[5] The call Method
[6] The apply Method

Updates

12 September 2007: original version

Advertisements

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