Safari helpfully (?) prompts before closing a tab or window when text has been entered into an input.
There are some cases where, as a web developer, this isn’t desirable — for example, when the input is a live search where the user has probably already gotten the results he’s looking for when the window is closed, even though there’s still text in the field.
How can I let Safari know that text in a particular input doesn’t need its protection?
It seems like you are able to disable this warning for an entire page by having an onbeforeunload handler on <body> (even an empty one will do). For example, the following will not produce the warning:
<body onbeforeunload="">
<form method="get"><input></form>
</body>
I'm not sure if this is the intended behaviour, or a bug.
I think I've got a solution to this problem, though it's unquestionably a hack (i.e. if Safari changes how this feature is implemented, it could stop working). Shown here with a touch of jQuery:
$('.unimportant').live('blur', function(){
var olddisplay = this.style.display;
this.style.display = 'none';
this.clientLeft; // layout
this.style.display = olddisplay;
});
Demo (try typing in the "unimportant" field, click somewhere else on the page, then close the window).
In short:
Hide the input
Trigger layout
Show the input
You can also change the value of the input, trigger layout, and change it back.
The one limitation here is that cleaning the input has to be done explicitly. In this case, it will be dirty until blur. This works well in my situation, and probably in many others, since the input will be protected from an accidental window close until something else on the page is manipulated (e.g. a link is clicked). You could choose to run this on keyup if you're willing to live with a flicker of the insertion point every time a key is pressed.
I'm open to cleaner solutions.
I found what I think is a pretty good solution to this problem. When I use AJAX to submit the form then I want the warning to suppress. This is accomplished with onbeforeunload.
window.onbeforeunload=function(e){}
But after I submit I might make additional changes and so I want the warning to show again. To do this I added a keyup handler to a form element.
$('txtarea').onkeyup=dirty;
What dirty does is checks is the input field has changed if it has then I set onbeforeunload to null.
function dirty(e){
if (e.srcElement.value != e.srcElement.defaultValue){
window.onbeforeunload=null;
}
}
I just found another solution to prevent Safari from displaying the "Are you sure you want to reload this page?" dialog when textareas have changed their content.
It turns out that setting the value through Javascript clears Safari's changed state:
$(document).on('blur', 'textarea', function() {
var value = $(this).val();
$(this).val('').val(value);
});
Clearing the value first is important, directly setting it to the content it already is does not work.
EDIT Apparently setting window.onbeforeunload to an empty function still works, however $(window).on('beforeunload', function() {}) does not.
Related
I'm using the following line (Struts1 syntax) to display a text field and allow some client side checks via Javascript.
<html:text styleId="myField" property="myProperty" onkeyup="function()" />
My intention is for a message to appear and a dropdown to disable whenever there is text entered into the form field (regardless of content). The onkeyup attribute works fine for all cases except for when the user pastes in text using mouse right-click.
It doesn't appear that onmousedown and onmouseup events notice right clicks. The same goes for onfocus.
onchange only makes the check when focus is lost, however the user can circumvent this by pasting data and clicking the form submit (same for onblur).
onmouseout somewhat works (I can break functionality) in IE8, but doesn't work at all in Chrome v41.0.2272.89
Has anyone encountered client-side form checks on Mouse-Right Click? I'd like to cover this use case across browsers and cannot count on the end user to always paste via keyboard shortcuts.
I went with a jQuery solution as suggested by Aleksandr M above in comments.
Initially I had this function:
$(document).ready(function(){
$('#myField').bind("paste",function(e) {
toggleFunction(); //preserve already existing function in use with other cases
});
});
But then came to find that while the function would run following the user's paste, it would run prior to the text actually being pasted.
Example:
User pastes (Right-click > paste OR Ctrl+V);
Function is called and executes, condition checks made
Text is pasted.
So instead I replaced the function call in the jQuery with my intended end result, making some additional changes elsewhere so that my assumptions are met.
But those conditions aside, the below ends up doing what I needed.
$(document).ready(function(){
$('#myField').bind("paste",function(e) {
document.getElementById("dropdownID").disabled = true;
document.getElementById("showMessage").style.visibility = "visible";
});
});
I have jEditable working fine with a textarea to edit the content inside an object. I'm submitting onBlur using jEditable's onblur option directly to a function (instead of an url) and all works as expected UNLESS you change windows using for e.g. alt+tab. When I use alt+tab it looks like the content is submitted through an actual http request ignoring my callback function.
My very basic sample implementation looks like this:
$(".editable").editable(function(content,settings){
content = content.replace(/\n/gi,"<br />");
return content;
},{
data: function(value, settings) {
return value.replace(/<br[\s\/]?>/gi, '\n');
},
type: "textarea",
onblur: "submit"
});
You can test it here: http://jsfiddle.net/xmajox/wePp5/
I've tried all other window operations: resize, move, etc and even moving between tabs on the browser works great (submits the data and exits the edit mode).
Any ideas of how this might be tamed?
UPDATE:
After a few more tests and some colaboration from other people, it seems that this depends on the os (window manager?). It happens consistently on ubuntu 12.10 with unity but doesn't happen on mac or windows (haven't tested other linux boxes).
Also, it is now proved that my callback method does run when I use alt-tab but the form gets POST'd anyway afterwards. Adding a live preventDefault didn't seem to help.
Found the fix for this, but coulnd't quite understand why it happens on such a specific situation.
First of, I must be the (un)luckiest guy in the world because I've tried this on 10ths of other combinations and it didn't have this problem, only place I could get it to happen was:
Ubuntu 12.10 (with Unity) and Chrome (v22 at the time of this post)
Disclaimer: This doesn't mean it does not happen on other places, I just couldn't reproduce it on the ones I tested (or asked friends to test).
Problem:
When focus is lost, jEditable - as of version 1.7.1 - sets a timeout before actually executing any action (200ms by default) to prevent duplicate actions according to the comments in the source. If you (using the above browser/os combination) alt+tab out of the browser window before the timeout is fired (aka. before the data is submitted and your method is ran) it will force a POST request with the data, completely ignoring the preventDefaults or return falses. It won't have this problem if you press alt... keep it pressed (more than those 200ms, i.e. enough time for the timeout to be fired) and then press alt to move away.
Solution:
I edited jEditable's source to reduce the timeout (or even remove it). So I around line 280, where it says:
(...)
input.blur(function(e) {
// prevent double submit if submit was clicked
t = setTimeout(function() {
form.submit();
}, 200);
});
(...)
I changed it to:
(...)
input.blur(function(e) {
form.submit();
});
(...)
Notes:
I removed this because on my situation (submitting textareas ONLY on blur and without any other controls that allow the user to submit the data) I coulnd't reproduce the double submit issue that Mika Tuupola was mentioning on the comments. If you have text input fields (that are submitted pressing enter) with the submit onBlur active, you might fall into that situation. On those cases I suggest you just reduce the timeout or simply avoid to use onBlur submits.
Is there is any way to detect text box value changed , whether users changes it explicitly or some java script code modified the text box? I need to detect this change.
To track for user changes you can add a handler for key presses:
$(selector).keypress(function() {
// your code
});
Update: besides watching for key presses,
you can use the change function to watch from changes via JavaScript. It won't work immediatly for user changes (is only called after the input loses focus), but together with the keypress I believe you cover all cases:
$(selector).change(function() {
// the same code
});
setTimeout(function() { $(selector).val("changed"); }, 2000); // Will trigger the change
Edit: sorry, it seemed to work for JavaScript too, but I was mistaken... This question, however, will be able to solve your problem (tested with setTimeout, and it was able to detect the change).
I posted an example in jsFiddle. With this new watch plugin, you no longer need keypress or change: it will work for key typing, copy/pasting, JavaScript, etc.
I'm making a widget that slides in and out of view on hover with showTracker and hideTracker functions. I want to prevent it from sliding out of view if it contains a focussed form element though, so I've got this going:
function hideTracker(){
if($('#tracker').find(':focus').length == 0){
$('#tracker').stop().hide();
}
}
Cool. Now it doesn't hide if the mouse happens to move out if there's a field in focus. Unfortunately, that also means that when the field does lose focus (and it's time for the widget to hide again) it just stays there. The unHover event has been and gone.
So I added this:
$('#tracker *').blur(function(){
hideTracker();
});
And that works too - with one little bug that I need help with!
If the focus moves from one element within the tracker to another which is also within #tracker, the tracker hides. I figured that if($('#tracker').find(':focus').length == 0) would return false, given that the next form element has focus, but I guess it doesn't.
Is it the case that .blur() fires before the next element attains focus?
How can I get around this?
How about something like this?
$('body *').focus(function(){
if(!$(this).is('#tracker *') && $('#tracker:visible').length != 0) hideTracker();
});
Yikes. Tricky. Yes, what's happening is:
mousedown: old form element gets the blur event. $(':focus').length == 0.
mouseup: new form element gets the focus event. $newFormElement.is(':focus') == true.
This is an improvement:
$('#tracker').focusout(function() //basically like $('#tracker, #tracker *').blur(), but "this" is always '#tracker'
{
if(!$(this).is('#tracker:hover')) //for some reason plain old :hover doesn't work, at least on the latest OS X Chrome
hideTracker();
});
But it's not perfect. It only really works if you use the mouse. If you use tab to move between fields (or some other possible mechanism) while your mouse is not hovering over #tracker, it won't work.
Here's another attempt. It's a bit...hackier. The gist is that, instead of handling the blur event, you handle the focus event of the second thing that's focused. But! What if you click something that can't be focused? Blank space on your page? Then no focus event is fired.
Okay. So the trick is: put a tabindex="0" in your root <html> tag. This means that there is always something that can be focused. So there's no way to focus on nothing (at least, I don't think so).
Then you can do this:
$('*').live('focus', function(e)
{
if(!$.contains($('#tracker')[0], this)) //if the new thing you focused on is not a descendant of #tracker
hideTracker();
e.stopPropagation();
});
Eh? So yeah, that's a certified hack. But it's a tough problem, and that's the best I can come up with at this hour.
Thank you all for your answers. Utilising the .focus() event rather than .blur() was a clever way to look at it. Unfortunately, it does raise a couple of browser problems, and I couldn't get any of the above working very robustly.
In the end I decided to use setTimeout(hideTracker, 100); to allow the focus() event to take place before the count of focussed elements within tracker was evaluated. Not ideal, but it's working well and the delay is fairly imperceptible.
Thanks again.
I have a page which does quite a bit of work and I don't want the user to be able to navigate away from that page (close browser, hit back button, etc.) without getting a warning. I found that the onbeforeunload event (which I think is IE-specific, which works fine for me as the project uses lots of ActiveX) works great.
Problem is, I want the user to be able to click on a little "help" icon in the upper-right corner and pop up a help window at any time. This causes onbeforeunload to fire, even though the main window never goes anywhere and the page never unloads.
The JavaScript function that runs when the onbeforeunload event runs just puts text into event.returnValue. If I could ascertain, somehow, that the help icon is the one that was clicked then I could just not put text into event.returnValue in that situation. But how could I have the page figure that out?
Let me guess: the help "icon" is actually a link with a javascript: url? Change it to a real button, a real link, or at least put the functionality in an onclick event handler (that prevents the default behavior). Problem solved.
<!-- clicking this link will do nothing. No onbeforeunload handler triggered.
Nothing.
And you could put something in before the return false bit...
...and the onunload handler would still not get called... -->
blah1
<!-- this should also do nothing, but IE will trigger the onbeforeunload
handler -->
blah2
EDIT: My "workaround" below is complete overkill, based on my lack of understanding. Go with Shog9's answer above.
OK so while I was writing the question, I came up with a workaround which will work for now.
I put a global JavaScript variable in act as a boolean on whether or not the icon is being hovered over. Then, I attach events to the image's onmouseover and onmouseout events and write functions that will set this value. Finally, I just code in the function that handles onbeforeunload that will check this value before setting event.returnValue.
Probably not a flawless workaround but it will work for now.
on the internet you will find many people suggesting you use something like
window.onbeforeunload = null
but this does not work for me in IE6. reading up in the MSDN docs for the event object i found a reference to the event.cancelBubble property, which i thought was the solution. but thanks to Orso who pointed out that setting "event.cancelBubble=true" is useless, the way to get rid of the confirm prompt is to exclude the return statement altogether, i chose to use a boolean variable as a flag to decide whether to return something or not. in the example below i add the javascript code programattically in the code behind:
Page.ClientScript.RegisterStartupScript(typeof(String), "ConfirmClose", #" <script> window.onbeforeunload = confirmExit; function confirmExit() { if(postback == false) return ""Please don't leave this page without clicking the 'Save Changes' or 'Discard Changes' buttons.""; } </script>");
then the help button contains the following aspx markup:
OnClientClick="postback=true;return true;
this sets the 'postback' variable to true, which gets picked up in the confirmExit() function, having the effect of cancelling the event.
hope you find this useful. it is tested and works in IE6 and FF 1.5.0.2.
I have a method that is a bit clunky but it will work in most instances.
Create a "Holding" popup page containing a FRAMESET with one, 100% single FRAME and place the normal onUnload and onbeforeUnload event handlers in the HEAD.
<html>
<head>
<script language="Javascript" type="text/javascript">
window.onbeforeunload = exitCheck;
window.onunload = onCloseDoSomething;
function onCloseDoSomething()
{
alert("This is executed at unload");
}
function exitCheck(evt)
{
return "Any string here."}
</script>
</head>
<frameset rows="100%">
<FRAME name="main" src="http://www.yourDomain.com/yourActualPage.aspx">
</frameset>
<body>
</body>
</html>
Using this method you are free to use the actual page you want to see, post back and click hyperlinks without the outer frame onUnload or onbeforeUnload event being fired.
If the outer frame is refreshed or actually closed the events will fire.
Like i said, not full-proof but will get round the firing of the event on every click or postback.