Thursday, July 1, 2010

Generic code to detect browser close

Problem description:
You need to detect the browser close , irrespective of the fact how the user has closed it !

Background:

I had a code that was detecting whether the user has attempted to close the browser or not .If yes ,then there should be a message ,other wise not .The code that was working fine ,is was like
<html>
<head>
<title>Detecting browser close in IE</title>
<script type="text/javascript">
var message='Browser close attempt is detected';
function ConfirmClose()
{
if (event.clientY < 0)
{
event.returnValue = message;
}
}
</script>
</head>
<body onbeforeunload="ConfirmClose()" >
<h4>Close browser!</h4>
</body>
</html>
This works fine but with two exceptions .
1.If the user does right-click on the title bar , an option to close the browser appears.Now , if he clicks on that close option browser gets closed without any message .
2.If the browser is minimized and the user right-clicks on the minimized browser , an option to close the browser appears.Now also if close option clicked browser gets closed without any message .

Both of the cases do occur as the condition mentioned in my code i.e.
"event.clientY < 0 " is violated .Now that code above is not good, because I am considering on the mouse coordination!!! The mouse could be anywhere. An alternative would be

<html>
<head>
<title>Detecting browser close in IE</title>
</head>
<body onbeforeunload="return 'Browser close attempt is detected';">
<h4>Close browser!</h4>
</body>
</html>

Now the problem with the code is that 'onbeforeunload' will be fired even when a new page gets loaded ,if the user navigates from one page to another .In that case ,this message would appear irrespective of the situation whether the user wants to close the browser or wants navigate to other page .That is where the risk lies .

So far I have thought about handling two things
1.Catch any kind of window termination (formal close)
2. Do not catch navigations to other pages of the application.
[But wait there can be other variations also :).For example if the user has a google toolbar and tries to search somthing with it in my application, or even directly open another URL in the window of your page...that makes 2 kind of navigation. internal & external. (and some externals like the google one, will result in coming back to your page again).So by having this in mind, do you want to "Dont Catch" all type of navigations? or only internal ones? ]
3.POWER LOSS !!
4.Ending the IExplorer.exe from task manager and so on ...
Confusing.Is'nt it ? Lets re-frame the entire thing little bit .First ask ourselves why we want to detect a browser close ? why ? In most of the cases it is detected , to log out the user who has closed the browser .Say there is a logout url in the page for a logged in user , but he has not clicked the ,instead he has just closed the browser.But now an updation in the database should be done when the user logs out .So while the user logs out (or CLOSES THE BROWSER!!) I need to trap that event and fire an updated to the DB.
For the time if I can trap following three events it will be sufficient for me .
1.If the user closes the browser using the 'X' button at the top right at the corner.
2.If the user does right-click on the title bar , an option to close the browser appears.Now if he clicks on that close option browser gets closed without any message .
3.If the browser is minimized and the user does right-click on the minimized browser , an option to close the browser appears.Now the user clicks that option.

First I need to need to display a message to the user while closing the browser ,in these cases.The code below shows a Message on the above conditions I mentioned earlier.The message MUST NOT APPEAR when:
1. User hits a hyperlink (by mouse or keyboard).
2. User submits a form.

How does it work:
If you hit on a hyperlink or submit a form, a variable called "Navigating" turns "true".
It's because all links.onclick & forms.onsubmit events are registered with this line of code (Navigating=true;) without loosing the old registered event handler of them (if there is any).So, afterwards the event "body.beforeunload" fires, then it only shows you the message if "Navigating" is not "true". That on this point the message will not appear.But if you only use those 3 kinds of closing options you've mentioned, the "Navigating" variable doesn't change (remains "false") and the message will appear.

Some issues ,fixes and enhancements:
Currently this code defines 2 kinds of navigations that could be trapped by links.onclick & forms.onsubmit events.If you have any other kinds of navigations on your pages, that you can trap them on any events, just register the line (Navigating=true;) with that event. use the function "ChargeHandler" to do that without loosing the old event handlers...
and just use a code like the last lines of javascript codes, to cover all instances of that event on the page...
Please note, if you dynamically add links or forms (or even other defined types of navigations that you added) after the page is loaded, the message may appear for navigations launched by these new elements.For fixing this issue you should call "ChargeHandler" after adding such element to the page.So the final code is ....
<html>
<body onbeforeunload="if(!Navigating){return 'Browser close attempt is detected';}else{Navigating=false;}">
<!-- Unimportant Sample Links & Forms to test all kinds of them, with/without event handlers... -->
<a href="http://www.google.com/">Navigate Away 1 (No Event Handler)</a><br>
<a href="http://www.google.com/" onclick="alert('Link Event Handler 1');">Navigate Away 2 (Event Handler 1)</a><br>

<form action="http://www.google.com/">
<input type="submit" value="Navigate Away Submit Case 1 (No Event Handler)">
</form>
<form action="http://www.google.com/" onsubmit="alert('Form Event Handler');">
<input type="submit" value="Navigate Away Submit Case 2(Event Handler)">
</form>
<!-- End of Unimportant Samples! -->
<script language="javascript">
var Navigating=false;
function ChargeHandler(obj,eve)
{
obj.oldHandler=obj[eve];
obj[eve]=function()
{
if(obj.oldHandler!=null) obj.oldHandler();
Navigating=true;
};
}
//Add Navigating=true; to all links.onclick & all forms.onsubmit event handlers...
for(i=0;i<document.links.length;i++) ChargeHandler(document.links[i],'onclick');
for(i=0;i<document.forms.length;i++) ChargeHandler(document.forms[i],'onsubmit');
</script>
</body>
</html>

Notes:
The final solution that I have mentioned above is a result derived from the valuable feed backs ,opinions as well as sample codes of my several colleagues and specifically some experts from different forums . Many thanks to them.
My role was of merely an observer and coordinator and nothing more.

No comments:

Post a Comment