‘m doing a little work on cleaning up an application and came acrossed an undesirable circumstance related to the UI. The app contains what appears to be a tab navigator, but rather then the tabbed links revealing hidden form elements in another tab they take the user to a whole new page. The obvious problem with this design is that the user may populate elements in the first ‘pseudo-tab’ and click on the next tab assuming that they will be able to save the data after populating the other tab. Since I’m not at liberty to make massive changes to the UI I came up with the following solution using jQuery so the user is at least warned when they leave without saving their changes. I think it’s a workable solution, and better yet it was terribly easy to do.
The first step here is to know when the form is ‘dirty’. That is, when a user has made a change to the form that has not been saved. There are a number of ways to determine that a form is dirty, but I chose to declare a global variable called ‘isDirty’:
Note: Please see the update below for a better method using the onbeforeunload event of the window.
var isDirty = false;
The next step is to update the dirty flag when a user has made a change. When the DOM is ready I simply register a ‘keyup’ ‘change’ listener for all form inputs that will update the dirty flag as needed. Note: the ‘:input’ selector will handle all form inputs, including <select> and <textarea> tags.
$(‘:input’).change(function(){
if(!isDirty){
isDirty = true;
}
});
Simple enough, right? Finally, I want to capture when the user clicks on a link that would navigate them away from the current page.
$(‘a’).click(function(){
if(isDirty){
var confirmExit = confirm(‘Are you sure? You haven\’t saved your changes. Click OK to leave or Cancel to go back and save your changes.’);
if(confirmExit){
return true;
}
else{
return false;
}
}
});
Here we assign a click handler for every <a> element on the page. If the form is dirty we display a confirm dialog that warns them of the unsaved changes. If they confirm that they want to discard the changes we return true (allowing the normal action on the click event), else we return false (interrupting the normal link action). Since the pages save action will post the form there is no need to reset the dirty flag anywhere else, but if the save action was handled with Ajax you’d obviously want to reset the dirty flag.
I should mention that this solution is not fail proof. Any JavaScript methods that use ‘window.location’ to navigate will not be caught. Also, if the user closes the browser window they will not be prompted to save their changes. I may look into capturing the window unload event to handle that case, but memory tells me that the window unload event is pretty hard to catch in a reliable cross browser way.
To recap here is the entire example:
<script>
var isDirty = false;
$(document).ready(function(){
$(‘:input’).change(function(){
if(!isDirty){
isDirty = true;
}
});
$(‘a’).click(function(){
if(isDirty){
var confirmExit = confirm(‘Are you sure? You haven\’t saved your changes. Click OK to leave or Cancel to go back and save your changes.’);
if(confirmExit){
return true;
}
else{
return false;
}
}
});
});
</script>
Update: It seems assigning a function to the window.onbeforeunload event works pretty well. Here is the simplified and revised function that should catch all attempts at leaving the page (closing the window, following links, etc):
var isDirty = false;
var msg = ‘You haven\’t saved your changes.’;
$(document).ready(function(){
$(‘:input’).change(function(){
if(!isDirty){
isDirty = true;
}
});
window.onbeforeunload = function(){
if(isDirty){
return msg;
}
};
});