Ever since I asked myself and tried to find out why people tend to hate JavaScript I have been loving the language. My research revealed that JavaScript has many cool features from the very beginning. The reason people are hating it are the sucky browsers. Every browser has its own implementation which causes problems. What is more JavaScript's main use is to deal with the dreaded DOM. Luckily in recent years people seem to discover the power of JavaScript and appreciate the fact that the language is very well suited for the untyped nature of the DOM. As you probably know I do not believe that the Web should be rich and made to resemble desktop applications. Unfortunately this means that I did not have a chance to write complicated JavaScript and use the language to its full potential. The stuff I did was really simple validation and calculations like adding two numbers. However our current project is more user-centric and requires rich UI so it seems like the time has come for me to write some elite JavaScript.
This article is really about passing arguments to callback functions. In our case we wanted to pass arguments to the
ASP.NET page methods callback functions. In case you do not know page methods are static methods in the ASP.NET page which are exposed as a JSON web service. If you set the EnablePageMethods property of the ScriptManager object to true it will generate a JavaScript proxy for you. We wanted to create voting functionality so in the simplest form we had a web method with signature like this:
[WebMethod]
public static string Vote(int articleID)
{
return "300";
}
Of course in the real application we were writing the vote to the database and returning the number of votes but for the purposes of this demo I will keep the method as simple as possible. The JavaScript proxy that ASP.NET generates for us has the following signature:
PageMethods.Vote(articleID, onSuccess, onFailed, userContext)
The first argument is the actual argument of the page method in the code behind. The second argument is a callback function that should be called if the Vote method returns as expected. The third argument is a callback function that should be called in case something goes wrong. I will tell you what the fourth argument does later. To keep the demo simple we will use only the first two arguments. In our application we wanted to show the number of votes to the user after he had voted. Our designer Veso did some jQuery magic for this but we will stick to simple alert. So lets create a callback function to be called on success.
function onVoteSuccess(result) {
alert(result);
}
The result argument is automatically passed to the success callback function by the page method proxy. Similarly an error argument that contains error information is passed to the failure callback function. Lets add a function that calls the Vote method:
function vote(articleId) {
PageMethods.Vote(articleId, onVoteSuccess);
}
and an html element that calls the function:
<a href="#" onclick="vote(1);">test</a>
In the real application there are many links generated dynamically for each article with the corresponding articleId. Now if we click on the link we are gonna see an alert that says "300" returned from the server. This is fine but usually we need to change the interface so we need to know where the click came from. This is usually done by passing this to the function call:
<a href="#" onclick="vote(1, this);">test</a>
function vote(articleId, element) {
PageMethods.Vote(articleId, onVoteSuccess);
}
This is good but we do not need the element until the function has actually succeeded. However the Vote method will not pass anything but the result argument to the callback function. It seems like now is the time to leverage the power of inner functions and closures.
Lets refactor the vote and onVoteSuccess functions like this:
function vote(articleId, element) {
function onVoteSuccess(result) {
element.onclick = null;
element.innerHTML = "Stop clicking you already voted! By the way the result is " + result;
}
PageMethods.Vote(articleId, onVoteSuccess);
}
This will remove the click handler from the link element and will set the text to inform the user that he has already voted. The element is captured in a closure by the inner function and the inner function is passed as a callback. Once the Vote page method completes the AJAX call it will call the onVoteSuccess function. This works fine but we may want to call the onVoteSuccess function from other functions except vote or maybe the code is just too long and does not sit well in an inner function. We can extract it and call it like this:
function vote(articleId, element) {
PageMethods.Vote(articleId, function(result) { onVoteSuccess(result, element) });
}
function onVoteSuccess(result, element) {
element.onclick = null;
element.innerHTML = "Stop clicking you already voted! By the way the result is " + result;
}
We are using an anonymous function to take advantage of the closure and then just call whatever function we want.
Now is the time to reveal what the userContext argument of the Vote page method does. It is intended for the non-elite JavaScript users who are not familiar with closures. Basically you can build whatever object you want and pass it to the page method. When the callback is called the same object will be passed back as an argument. We could have written it like this:
function onVoteSuccess(result, userContext) {
userContext.onclick = null;
userContext.innerHTML = "Stop clicking you already voted! By the way the result is " + result;
}
function vote(articleId, element) {
var userContext = element;
PageMethods.Vote(articleId, onVoteSuccess, null, userContext);
}
To be honest I did not know that I could use the userContext argument when we wrote the script but that does not mean that the closures are not useful. While Microsoft provided us with a way to pass arguments to the callback one can easily imagine that some libraries will not.
We also created a namespace for our JavaScript functions using
this method. There are more methods to do it but this one is one of the most popular and the simplest. The other methods however have more features like class-like behavior that we did not need. Using the namespace we made our onVoteSuccess and onVoteFailed functions private. I hope in the future we have more chances to use the true power of JavaScript.