Feb 28 2010

Overriding jQuery/JavaScript functions using closures

Category: NovoGeek @ 01:44

Function overriding is an important feature in any programming language. In advanced languages like .NET, Java, it can be accomplished easily through a set of key words. But this is not the same in JavaScript and this is where closures come to your rescue.

This article is not something new. Infact, it is a part of every JavaScript geek's  toolkit, which does not have much exposure. So just wanted to throw some light on it. (If you are not very clear of closures, please visit this article and come back).

Let us take a vey simple example. Assume that you have a button, for which a click event is bound.

<input type="button" id="btnTest" value="Test Button"/>

var originalClick=function(e){ 
    e.preventDefault(); 
    console.log('original button click'); 
}; 
 
$(document).ready(function() { 
    $('#btnTest').click(originalClick); 
}); 

Now, if there is some requirement change and you need to add extra functionality to this button click, then instead of messing up its method, you can extend the originalClick method using closures, like this:

(function() { 
    var extendedClick= originalClick; 
    originalClick= function() { 
        console.log('Extending Test Button click'); 
        return extendedClick.apply(this, arguments); 
    }; 
})(); 

The above code is nothing but a self executing anonymous function (gets executed immediately after its definition). In the first line, we are storing the actual function in a variable 'extendedClick' (we'l use this later). Then we are creating a function with the same name as our original button click function (originalClick).

In the inner function, we are writing our extended logic and executing 'extendedClick' using .apply() method. (passing 'this' as first param to .apply() makes sure that the context is still the same as original method). So the entire code would be:

var originalClick=function(e){
    e.preventDefault();
    console.log('original button click');
};
 
(function() {
    var extendedClick= originalClick;
    originalClick= function() {
        console.log('Extending Test Button click');
        return extendedClick.apply(this, arguments);
    };
})(); 
 
$(document).ready(function() {
    $('#btnTest').click(originalClick);
}); 

Thats it! Your method is now overridden!! Ok, now let us see what exactly happened.

As per the definition of closure, we have 2 functions which are nested and extendedClick is a local variable. The outer function is a self executing one. During its execution, ‘extendedClick’ is obviously available inside the inner function ‘originalClick’, since it is defined in the parent context. The magic of closure comes after the outer anonymous function is executed. How?

When you click on button, the overwritten ‘originalClick’ function will be fired, which will print the console statement ‘Extending Test Button click’. Then the ‘extendedClick’ function, which has a copy of the first ‘originalClick’ function, will be fired. But why is it still accessible? The variable ‘extendedClick’ is private to the outer anonymous function, which means, it should have been destroyed after the outer function executed. But it is still accessible, which is the power of closure.

(Q) How can I override core jQuery methods using this concept?
(A) Ben Nadel
explained it clearly in his article, which I happened to see while I started this article. It is the same concept, but still I wanted to write and explain in layman’s terms. Thanks to Ben for his great blog posts and for quick turn out in times of need.

Using this technique, you can override even $(document).ready function! This would help you the most when you are in a maintenance project and have to do bulk changes in code with minimal impact.

Happy coding :)

Tags: ,

Feb 8 2010

Scope problems with JavaScript setInterval & setTimeout? Use closures!

Category: NovoGeek @ 21:15

JavaScript closures is one of those topics which, I always thought, is beyond my head. Though several articles explain the concept, somehow, I was often confused.

I was working on a thick client application, which heavily uses lot of jQuery and JavaScript. I need a client side timer to run periodically, to keep user's session alive and so I was using JavaScript's native "setInterval()" method for my timer. Everything was fine, till I suddenly faced a weird problem.

Suppose, you have created a  global function which is to be fired periodically. In simple JS, it can be written as:

var timer=function (){ 
    console.log('my timer'); 
}; 
var timerId=setInterval('timer();',1000); 

This works perfectly well. Here the function "timer()" is at a window level and has global scope. So no issues so far.

However, to clean up the code, if you follow any of these patterns,  you will have good code, but setInterval will not work! See the below code for more clarity:

var TestObject=function(){ 
    var timerId; 
    this.timer=function(){ 
        console.log('test timer'); 
    }; 
    this.startTimer=function(){ 
        timerId=setInterval(function(){ 
            this.timer(); 
        },1000); 
    }; 
    this.stopTimer=function(){ 
        clearInterval(timerId); 
    }; 
}; 
 
var obj=new TestObject(); 
obj.startTimer(); 
obj.stopTimer(); 

In the above code, TestObject is the class which is holding the entire code, so that all my functions are not exposed to global window object. "timer()" is a public function which will be fired periodically and "startTimer()" is the function which will trigger the timer.

If you run the above code (you can quickly try in firebug console), you will get a JavaScript error - "this.timer is not a function". The reason is, setInterval() function will take the scope to window object and this will check for "timer()" at window level. Since you are wrapping it in "TestObject", "timer()" will not be found. To solve this scope problem, you need to use closures and store the instance of the class. So the "startTimer()" function should be like:

this.startTimer=function(){ 
      var inst=this; 
      console.log('startTimer this: ',this); 
      timerId=setInterval(function(){ 
          console.log('setInterval this: ',this); 
          inst.timer(); 
      },1000); 
}; 

The above code has two console statements. The “this” keyword in the first console statement prints the instance of TestObject, while the “this” keyword in setInterval function prints “window” object. This shows that setInterval function will take the scope to window level and hence it is not able to find “this.timer” in the first snippet.

Notice that we are storing the instance of TestObject class in a local variable "inst". Also notice that setInterval is using an anonymous function, which is forming a closure. In this anonymous funtion, we are able to access timer() function using "inst.timer()". This would solve the scope problem! setInterval function will be called for every 1 second and it will always have access to “inst.timer()”. This is the power of closures.

To explain closures in my words,  when there are nested functions, the child function will have access to local variables of parent function(which is as expected). But after the parent function has executed, the child function, when called explicitly, will still have access to the local variable of parent function. JavaScript garbage collector will not clear the value of the variable.

For a graphical explanation of JavaScript closures in jQuery context, please check BenNadel's blog. Ben explains the concept of closures very clearly in this article.

Happy coding :)

Tags:

Feb 1 2010

Check for unsaved data on your web forms using jQuery

Category: NovoGeek @ 08:55
One of the most important Usability requirements in business applications is, to periodically inform the users when there is any unsaved data on their web pages. This can be seen in email apps like Live mail/Gmail (compose a new mail and try to navigate to Inbox, without saving or sending the mail. An alert will pop out asking to save the mail).

To meet this requirement, one page load, one should loop through all the controls of the page, store the initial values of the controls, bind blur event handlers to all the controls, on blur event compare the final value with the initially stored values. If there is any change in the value, mark the form as dirty (unsaved data), else mark it as clean (saved data).

jQuery Dirty Form plug-in:

I started writing my own code, but found the beautiful dirty form plug-in. Thanks to the author Wilson for the plug-in, which does exactly the same as my pseudo-code. To check for a dirty form, all you need to do is:

$(document).ready(function(){
   $("#YourFormId")
     .dirty_form()
     .dirty(function(event, data){
       //The dirty event fires when you blur from a control after changing its value.
     })
 });

The plug-in is not well documented, hence you should spend time walking through the code. The plug-in will set a flag to the form’s data, to indicate if it is dirty or not, like this:

form.data("dirty", true) 

The dirty() event in snippet 1 will set the dirty flag to true. So at a later stage if you need to check the form’s status, you should use the form’s data. To a good extent I could leverage the the plug-in, but to meet my project specifications, I had to tweak it a lot.

Bug in Dirty Form plug-in and fix for the bug:

The plug-in has a function called “input_checker”, which will check for form status on each control’s blur. As said earlier, it will compare the initially stored values with final ones, which is fine. However, if you switch between forms, the data of one form will be compared with data of latest form, which spoils the show. I’m not sure why others haven’t raised the bug, but I’m very sure of it. Hence publishing the below fix:

In the “input_checker” function, replace the below code

input_checker : function(event){
    var npt... ... ...
    inputs = event.data.inputs, settings = event.data.settings

with this one:

input_checker : function(event){
    var npt ... ... ...
    inputs = $(':input:not(:hidden,:submit,:password,:button)', form), settings = event.data.settings

What is happening is, the plug-in is caching all the inputs to be checked in data parameter of the form. When we navigate to a new form, the controls of old form(from cache) are being checked, instead of the controls from new form. Hence I am querying the controls of new form again and using it for comparison. This would solve the problem :)

Here are some of the functionalities which I could leverage using the plug-in:

(1) On page load you can disable save buttons and enable them only in the dirty() event.

(2) You can check for form dirty flag and decide to extend user’s session accordingly.

(3) You can give a warning when user navigates to another page without saving data (similar to Gmail/Live mail).

Thanks to Wilson once again for the plug-in. Hope the fix would help some of the folks who are facing similar issues. 

Happy coding :)

Tags: