Introduction
While you browse and surf through the net, you must have seen a message somewhere similar to the following:
A sample navigate away message in the browser
You know when this happens. The above message appears if you are in the middle of doing some changes in the current web page (changing form data) and you are about to leave the page for some reason without submitting the changes. Simple.
But, as a developer, this might not be an easy feature for you to implement. At first glance, it looks as if you have to track the changes of every little form element value and show a message if one has changed. To make the situation tougher, not every page has the same set of input elements. So, tracking change for each page might get even tougher, and each page might require implementing its own change tracking logic.
Frankly speaking, when I first encountered such a requirement, I thought, "oh, there must be some built-in feature in the browsers". The browser should be able to track changes of the input elements of the currently displayed page (everything is HTML and each object is available in the DOM, right?). So all I might need to know is to just enable the feature.
Sadly, I was wrong. Browsers don't have such a built-in feature (I still believe they should have), and hence, it is us (poor developers) who have to track the changes and show those lovely(?) messages.
To implement this feature, most of the time, we may end up writing code for tracking changes in each different page (because each page may have a different set of input elements), or may be, we might implement some logic to track the changes of the input elements in a harder way (not in a smarter way).
So, even if I am not a JavaScript or jQuery expert, I thought about trying to develop something in a generic way that would allow us to implement the "Navigate Away" feature for an ASP.NEt web application (and probably for any web platform, be it ASP.NET or PHP) without writing any actual code! This article is an effort to demonstrate this generic implementation.
(Yes, there may be one or two Navigate Away jQuery plug-ins appearing in your Google search, but I found those to be buggy or not meeting the exact requirements.)
The requirement
- If the user modifies any value in any input field in a web form (page), and if he/she performs anything that requires leaving the page (clicking a hyperlink, closing the page, etc.), the web page should show a message that states he/she may lose the changes if they navigate away.
- If the user modifies values in input fields and then reverts the value changes for some reason, and then if he/she performs anything that requires leaving the page, the web page should not show any navigate away message (because no value has actually changed).
- If the user submits a form or performs some actions (which may differ from page to page), the web page should not display any "Navigate Away" message even if input element values have changed. In each page, it should be easy to configure which actions should not cause showing a navigate away message.
- It should be easy for any page to not use the "Navigate Away" feature.
- The page developer should not be required to write any code for implementing the "Navigate Away" feature. The implementation should be developed in a "plug-in" fashion.
- The plug-in implementation logic should be simple (this shouldn't be rocket science).
Sample implementation
I've developed a jQuery script for implementing the "Navigate Away" feature and used this in a sample ASP.NET application, which you can download from above:
Sample ASP.NET web site that uses the "Navigate Away" script
The sample application is an ASP.NET web site which contains the following things:
- "navigateaway.js" is the jQuery script that this article is all about, and "navigateaway.min.js" is the minified version of the same file.
- MasterPage.master is where the NavigateAway.js script is used as follows:
<script language="javascript" src="js/jquery.js"></script>
<script language="javascript" src="js/navigateaway.min.js"></script>
The Default.aspx uses the Navigate Away feature, and to do so, it doesn't have to do anything (because it includes the MasterPage.master). The Default2.aspx also includes the Master page, but it doesn't use the Navigate Away feature. To not to use the feature, it just calls the following JavaScript method:
<script language="javascript">
DisableNavigateAway();
</script>
To have a look at the sample application, browse Default.aspx of the sample application. You will see the following screen output:
Page output of Default.aspx
The page contains the following input form elements:
- A text box
- A drop-down list
- A check box group
- A radio button group
- A file selection
And it contains the following action elements, which could make the user leave the current page:
- A hyperlink (
Hyperlink
) - A normal button (
Button
) - A submit button (
Submit
)
If the user changes any value in the input element of the page, the following actions should display the Navigate Away message:
- Clicking on the hyperlink (
Hyperlink
) - Clicking on the normal button (
Button
) - Closing the page
However, the following action does not show any Navigate Away message, even if a form element value has changed:
- Clicking on the submit button (
Submit
)
If user changes the input element values, and changes them back to their initial value again, the Navigate Away message is not shown.
That's it! Feel free to play with the page. Happy "Navigate Away".
How to use the NavigateAway script
To implement the "Navigate Away" feature, all you need to do is to add a reference to the script "navigateaway.min.js" (the minified version of the jQuery script) in the HTML (or XHTML markup), and most of the time, you might have the opportunity to add the reference within a single file (which is included or reused across most or all files within the application), and you are done. For example, if you are working with an ASP.NET application, including the script in the MasterPage file will be good enough for you, if all other pages in your application uses that MasterPage file.
<script language="javascript" src="js/navigateaway.min.js"></script>
If the script is included in the MasterPage or a common re-usable file, the "Navigate Away" feature will be turned on by default for every page which uses or includes the file. Now, there will be some pages where you might not want to use the "Navigate Away" feature. How should you configure this?
Fortunately, this is simple. You just need to call the following function in the HTML (or XHTML) to achieve this:
<script language="javascript">
DisableNavigateAway();
</script>
If the "Navigate Away" feature is enabled for a particular page, it will be turned on for each and every action that results in leaving the current page for the user. This may not be desired at times. For example, you surely would not like to see the "Navigate Away" message after filling up a form and pressing the Submit button or clicking on a "Link" or "Image Link" that submits and saves the form data to the server. Also, you may not want to show the Navigate Away message if the user clicks on a button/link in a modal popup confirmation box.
The navigateaway.js script makes this extremely easy to configure. You just need to specify the class nonavigate
for an element for which you would not want to fire the "Navigate Away" action.
For example, "Default.aspx" has a submit button (Submit
) for which the "Navigate Away" message should not be displayed, even if an input element value in the form was modified.
Clicking on the "Submit" button should not show the "Navigate Away" message
To configure the "Submit
" button not to fire the "Navigate away" action, the class nonavigate
has to be specified as follows:
<input type="submit" id="submit" class="nonavigate" value="submit" />
And, if an element in the HTML (XHTML) in the page has the nonavigate
class specified, it will not fire the "Navigate Away" action, no matter the input values in the form were changed or not.
The Script
Following is the jQuery code. It is small, and is built upon an easy logic.
var initialValue = '';
var userValue = '';
var NAVIGATE_AWAY_MESSAGE =
"The changes you made will be lost if you navigate away from this page.";
var onBeforeUnloadFired = false;
var DISABLE_ONBEFOREUNLOAD_PLUGIN = false;
var IgnoreNavigateForEventSource = false;
$(document).ready(function() {
initialValue = GetFormValues();
window.onbeforeunload = handleOnBeforeUnload;
$("a,input,img").click(function() {
IgnoreNavigateForEventSource = $(this).hasClass("nonavigate");
});
});
function DisableNavigateAway() {
DISABLE_ONBEFOREUNLOAD_PLUGIN = true;
}
function SetNavigateAwayMessage(message) {
NAVIGATE_AWAY_MESSAGE = message;
}
function IgnoreNavigateAwayFor(elementId) {
$("#" + elementId).addClass('nonavigate');
}
function GetFormValues() {
var formValues = '';
$.each($('form').serializeArray(), function(i, field) {
if (field.name != '__EVENTVALIDATION'
&& field.name != '__EVENTTARGET'
&& field.name != '__EVENTARGUMENT'
&& field.name != '__VIEWSTATE'
&& field.name != '__VIEWSTATEENCRYPTED') {
var inputField = $("[name=" + field.name + "]");
var displayProperty = $(inputField).css("display");
if (displayProperty != "none") {
formValues = formValues + "-" + field.name + ":" + field.value;
}
}
});
$(':checkbox').each(function() {
formValues = formValues + "-" + $(this).attr("checked");
});
$(':radio').each(function() {
formValues = formValues + "-" + $(this).attr("checked");
});
$(':file').each(function() {
formValues = formValues + "-" + $(this).val();
});
return formValues;
}
function ResetOnBeforeUnloadFired() {
onBeforeUnloadFired = false;
}
function handleOnBeforeUnload(event) {
if (DISABLE_ONBEFOREUNLOAD_PLUGIN) return;
if (!onBeforeUnloadFired) {
onBeforeUnloadFired = true;
if (IgnoreNavigateForEventSource) {
IgnoreNavigateForEventSource = false;
return;
}
window.setTimeout("ResetOnBeforeUnloadFired()", 10);
userValue = GetFormValues();
if (userValue != initialValue) {
if (NAVIGATE_AWAY_MESSAGE == "") {
return "The changes you made will " +
"be lost if you navigate away from this page.";
}
else {
return NAVIGATE_AWAY_MESSAGE;
}
}
}
}
The implementation logic
The functionality has been developed using a very simple logic, which is as follows:
- Read all input element values in the form while the page loads, append the values to a JavaScript variable, and store it (say,
InitialValues
). - While leaving the current page, don't do anything if the source element (the event source element which caused the action for leaving the current page) has the class "
nonavigate
". - Otherwise, read all input element values in the form again, append the values to another JavaScript variable (say,
UserValues
) and compare both variable values (whether InitialValues == UserValues
), and show a Navigate Away message if they are not the same.
Reading form element values
The GetFormValues()
method reads the input values in the form and appends them to a variable. It basically uses a jQuery method to serialize the input form values, which is as follows:
$('form').serializeArray()
If the script is being used in an ASP.NET Web Forms application, there will be some hidden input elements within the form which we would like to filter out while trying to read and append the form's input field values. These are filtered as follows:
if (field.name != '__EVENTVALIDATION'
&& field.name != '__EVENTTARGET'
&& field.name != '__EVENTARGUMENT'
&& field.name != '__VIEWSTATE'
&& field.name != '__VIEWSTATEENCRYPTED')
If the NavigateAway script is being used in any other platform, and if any such hidden form element is there, these needed to be filtered as above.
One limitation with the $('form').serializeArray()
jQuery function is that, it doesn't return some special elements in the form, like:
- Check box groups
- Radio button groups
- Files
These input element values are to be read and appended to the variables, using the following code:
$(':checkbox').each(function()
{
formValues = formValues + "-" + $(this).attr("checked");
});
$(':radio').each(function()
{
formValues = formValues + "-" + $(this).attr("checked");
});
$(':file').each(function()
{
formValues = formValues + "-" + $(this).val();
});
Handling double invocation of "onbeforeunload" in the IE browser
Using jQuery, the onbeforeunload
event is attached to the page load event as follows:
$(document).ready(function() {
initialValue = GetFormValues();
window.onbeforeunload = handleOnBeforeUnload;
$("a,input,img").click(function() {
IgnoreNavigateForEventSource = $(this).hasClass("nonavigate");
});
});
Note that a "Click" event is also attached to each <a/>
, <input/>,
and <img/>
element to detect whether the event source (which is currently causing the "Navigate Away" action) has the "nonavigate
" class specified. If that is so, this value will be used later to determine whether or not to show the "Navigate Away" message even if the form element value is changed by the user.
Unfortunately, in IE, the onbeforeunload
event is fired twice. To handle this issue, the following logic is used:
- Use a flag to find whether the
onbeforeunload
event was fired already (onBeforeUnloadFired
). - Within the
handleOnBeforeUnload()
method, do not do anything if onBeforeUnloadFired == true
; otherwise, set onBeforeUnloadFired =
true
and proceed to the rest of the logic. - If the form input values are changed, show the Navigate Away message. Note that as
onBeforeUnloadFired = true
now, even if the event is fired again in IE, the Navigate Away logic will not be executed again. - Reset
onBeforeUnloadFired = false
after a certain time interval (making sure that by this time the second event fired in IE has been handled) by using the following code:
window.setTimeout("ResetOnBeforeUnloadFired()", 10);
The ResetOnBeforeUnloadFired()
method is as follows:
function ResetOnBeforeUnloadFired()
{
onBeforeUnloadFired = false;
}
Controlling the "Navigate Away" feature
Disabling "Navigate Away" for specific pages
Including the navigateaway.js script will enable the "Navigate away" feature by default. So, if the navigateaway.js script is included in a common file (say, in a MasterPage), the feature will be enabled for all of the pages using the common file. So, there has to be a way to disable the feature for a particular page, if required.
Fortunately, this is very easy. Only the following method has to be called from within the page markup:
<script language="javascript">
DisableNavigateAway();
</script>
Ignoring the "Navigate Away" feature dynamically for an element
Setting the "nonavigate
" class to an element ensures that the element will not fire the "Navigate Away" message. You may set the class nonavigate
manually to the HTML/XHTML markup in your pages, or you may call the following function to achieve this:
<script language="javascript">
IgnoreNavigateAwayFor('<%= btnSubmit.ClientID %>');
</script>
The IgnoreNavigateAwayFor()
method simply adds the class nonavigate
to an element dynamically. The method is defined as follows:
function IgnoreNavigateAwayFor(elementId) {
$("#" + elementId).addClass('nonavigate');
}
Setting a custom Navigate Away message
By default, a generic common message is shown in the "Navigate Away" message. If required, this message could be customized as follows:
<script language="javascript">
SetNavigateAwayMessage("Custom Navigate Away message");
</script>
Give it a try, and let me know about any feedback or improvement suggestions. I'd love to hear what you say!