JavaScript Closure Example
Posted: September 2nd, 2011 | Author: nramakrishnan | Filed under: Uncategorized | Tags: javascript, technical | Comments OffIt has taken me time to fully understand the concept of closures in JavaScript. Here is an example of code that I rewrote so that we can use closures and get the thing to work the way we actually want it to.
The situation:
We have a set of buttons and we have to attach on-click handlers to them. These handlers should be called with parameters that are button dependent i.e., the innerHTML of the text box next to that particular button or say we need some attribute of the button (data-value, or some custom attribute).
We want to attach a function inviteUserToGroup with the specific parameters as event handlers to all the buttons.
So we initially wrote :
//add event handlers to invite buttons
var inviteBtns = $$('.inviteBtn'); //Prototype to get an array of elements with this class name
var len = inviteBtns.length;
var i;
var groupIdForUser;
var detailsForUserInGroupName;
for(i=0;i<len;i++){
groupId = $('groupIdForUser'+i).innerHTML;
details = $('detailsForUserInGroupName'+i).innerHTML;
//Protoype syntax to attach event handlers
//Event.observe(element,event,handler)
Event.observe(inviteBtns[i],'click',function(){
inviteUserToGroup(groupId,i,details);});
}
function inviteUserToGroup(a,b,c,d){ doSomething();}
So, we attach inviteUserToGroup method with the parameters. Since we enclosed the handler as a function, one can assume that due to the scope of the function, it should work fine and the event handler will be called with the correct parameters. That doesn’t work that way. All the event handlers get called with the value of i = len. You thought closures would work here, but it does not.
So what do you do? Create a real closure. The following code does that
/* DOES NOT WORK
Event.observe(inviteBtns[i],'click',function(){
inviteUserToGroup(groupId,i,details);});
*/
Event.observe(inviteBtns[i],'click',
(function(gId,i,dtls){
return function(){
inviteUserToGroup(gId,i,dtls);
}
})(groupId,i,details)));
So what is happening?
1) Create an immediate anonymous function which takes in the parameters that you want in your handler.
2) In the anonymous function return the actual function with parameters that you want to call. In this case we return inviteUserToGroup() with the parameters.
The important point to understand is that, the immediate function will execute and hence have the current values of groupId, details and i, which are passed to it. Due to the closure, the inner function (which is returned) has access to these values even after anonymous function has completed execution. So, inviteUserToGroup is attached to each button with the correct parameters.
For completion, another way of doing this is
Event.observe(inviteBtns[i],'click',
'inviteUserToGroup('+groupId+','+i+',\''+details)');});
Not recommended by many people as this uses the eval function to evaluate the JavaScript inside the quotes.
Is there a better (maybe more legible) way of doing this? Let me know!