Check for unsaved data on your web forms using jQuery

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 :)

jQuery event delegation - Adding/removing markup dynamically

First of all, my Happy New Year wishes to all netizens! As you journey through 2010, leave a trail of hardwork, innovation, passion and excellence...

Coming to the topic, in web apps, most of the times, we would require adding and removing controls to a web page dynamically. E.g.,adding "Browse" buttons for email attachments(live mail, gmail etc). In a traditional ASP.NET app, we used to build a string containing markup and append it to some control. However, this requires postback. Here is the end result which we would want to achieve:

With the help of jQuery, we can achieve this functionality with ease. Let us see the various ways of implementing this functionality.

1. Using jQuery “clone(true)” function:

Many of us know about jQuery clone() function, which will clone any jQuery selector. But the hidden feature is, it accepts a boolean parameter. When the param is set to true, it will clone DOM elements along with their event handlers.

Though this method can help us achieve the end result, the disadvantage is, it will fail to initialize complex controls like date pickers, auto suggest textboxes etc.

2. Using jQuery “live” event:

jQuery live looks like a perfect choice for this requirement. Live events are added for current as well as future elements in the DOM and hence events for “+”, “-“ buttons can be written only once using Live.

Though this works as expected in simple screens, even this is not a perfect approach! If suppose you are loading pages via AJAX, the events would be bound multiple times. So clicking on “+” will add more than 1 row, as multiple click events are bound. This is because, Live adds events to the document and not to the element. Check this article to see how live works.

3. Using jQuery Event Delegation:

Yup! This will solve the problems caused by above methods. I don’t want to explain about event delegation as there are very good articles already available. Check these:

The concept is very simple. Suppose you have a html table with 100 cells and you need to fire an event when any cell is clicked, instead of writing the below code:

$('#myTable TD').click(function(){
    $(this).css('background', 'blue');
});

you can write code like this:

$('#myTable').click(function(e) {
    var clickedElement = $(e.target);
    clickedElement.css('background', 'blue');
});

So instead of binding 100 events as in case 1, you can achieve the same output by binding only a single event, as in case 2.

This will solve the problem of Live, as the events are not added at document level, but are added to parent container. This will also solve the problem of Clone as date pickers will be initialized properly.

DEMO: Here is a demo for the above functionality using LIVE (Use it only when you are not loading pages via AJAX). I shall very soon come up with a demo using event delegation. This is a real time problem which I faced in a large scale project. So wanted to share immediately, so that others might not fall in the traps in which I fell!

Happy Coding :)