Trigger onbeforeunload if form is not submitted - javascript

I've got a form which is being submitted via PHP with 3 submit actions:
Save and Continue
Save and Exit
Exit without Saving
I'd like to trigger an "OnBeforeUnload" alert to display if the user DOES NOT click on any of the form actions to advise them that they're leaving the page, and their changes may not be saved.
I've tried the following code but it seems as though the unbeforeunload is being triggered before my click event. Any suggestions on how best to achieve this?
$buttonpressed = false;
$j(".Actions input").click(function(){
$buttonpressed = true;
});
if(!$buttonpressed){
window.onbeforeunload = function(){
return "Your changes may not be saved.";
}
}

You need to do the check inside the handler, like this:
window.onbeforeunload = function(){
if(!$buttonpressed){
return "Your changes may not be saved.";
}
}
Currently it's binding window.onbeforeunload when your code is run because $buttonpressed is false when it runs...it doesn't matter if it changes later since you already bound the handler. An alternative is to make it a bit simpler, like this:
window.onbeforeunload = function(){
return "Your changes may not be saved.";
}
$j(".Actions input").click(function(){
window.onbeforeunload = null;
});
This just removes the handler on click instead. A more appropriate event to handle other submit cases would be to attach to the submit event, like this:
$j(".myForm").submit(function(){
window.onbeforeunload = null;
});

Related

jQuery form getting submitted multiple times

I've made a simple lightbox implementation in my code. By Clicking on #makePayment link a lightbox is opened, and within that lightbox there is a form. When user clicks on #paymentDetailsConfrimLB to submit the form, I've just displayed an alert.
Before I explain the problem, please have a look at the code I've written:
$("#makePayment").click(function() {
$("body").addClass("modalPrint");
var lb = new LightBox("paymentDetailsLB", "675", "500");
lb.htmlObjRef.style.height = "auto";
lb.show();
$("#paymentDetailsCloseLB, .hideBox").click(function() {
$("body").removeClass("modalPrint");
lb.hide();
});//paymentDetailsCloseLB
$("#paymentDetailsConfrimLB").click(function( event ) {
alert('form submission happened.');
event.preventDefault();
return false;
});//paymentDetailsConfrimLB
return false;
});//makePayment
Problem is, when I first load the page, and open the lightbox, and click the submit button, the alert is shown once (as it should), but if I close the lightbox, re-open it, and then submit the form, it is submitted twice and alert is shown twice. Similarly if I again close and re-open the lightbox, upon form submission the alert shows up 3 times, and it goes on like this.
Any idea on how I can resolve this?
Other than Kavin's approach, another solution also worked for me. I just added this line immediately after the event.preventDefault() method:
event.stopImmediatePropagation()
And it resolved the issue.
You're setting click callback every time you open lightbox. Try to move click callbacks out of #makePayment:
$("#makePayment").click(function() {
$("body").addClass("modalPrint");
var lb = new LightBox("paymentDetailsLB", "675", "500");
lb.htmlObjRef.style.height = "auto";
lb.show();
return false;
});//makePayment
$("#paymentDetailsCloseLB, .hideBox").click(function() {
$("body").removeClass("modalPrint");
lb.hide();
});//paymentDetailsCloseLB
$("#paymentDetailsConfrimLB").click(function( event ) {
alert('form submission happened.');
event.preventDefault();
return false;
});//paymentDetailsConfrimLB
You're binding a new handlers every time the submit button is clicked. You only need to define a handler once, and it will be executed whenever that action occurs. Otherwise, each handler you bind will execute.
If you absolutely needed to bind the handlers the way you are, then you could also use .one, which will only bind the handler the first time for each element.
jQuery .one() documentation
Attach a handler to an event for the elements. The handler is executed
at most once per element per event type.
Try something like this.
$(document).on('click', '#makePayment', function() {
$("body").addClass("modalPrint");
var lb = new LightBox("paymentDetailsLB", "675", "500");
lb.htmlObjRef.style.height = "auto";
lb.show();
return false;
}).on('click', '#paymentDetailsCloseLB, .hideBox', function() {
$("body").removeClass("modalPrint");
lb.hide()
}).on('click', '#paymentDetailsConfrimLB', function() {
alert('form submission happened.');
event.preventDefault();
return false;
});
The problem is:
$("#paymentDetailsConfrimLB").click(function( event ) {
alert('form submission happened.');
event.preventDefault();
return false;
});//paymentDetailsConfrimLB
it adds to the click queue, so to speak. So if you add multiple click events to something, all of them get added and all of them run by default. You don't notice it because all your other functions don't matter about being run multiple times.
A way to solve this is to put a check within the function.
$("#paymentDetailsCloseLB, .hideBox").click(function() {
$("body").removeClass("modalPrint");
lb.hide();
});//paymentDetailsCloseLB
pDCLB=$("#paymentDetailsConfrimLB");
if(!pDCLB.attr('alertc')); //check if the attribute exists, if not, we've never been here before.
pDCLB.attr('alertc',1); //adds the attribute so we can check it later.
$("#paymentDetailsConfrimLB").click(function( event ) {
alert('form submission happened.');
event.preventDefault();
return false;
});//paymentDetailsConfrimLB
}

JQuery / dirty forms / window.onbeforeunload only triggered after link selection

I'm trying to implement a warning for the users in case they are leaving the form without saving.
The warning dialog works as expected but with the only exception that when the user chooses to 'Stay on Page', the selected side menu entry changed to the one the user clicked on (form is the same).
How can I make sure that the same menu item is still selected once the user chooses to 'Stay on Page'?
var warnMessage = "Unsaved changes. Do you really want to leave the page?";
$(document).ready(function () {
$('a.k-link').on('click', function () {
window.onbeforeunload = function () {
if (isDirty) return warnMessage;
}
});
});
You might find it easier (and possibly more consistent across browsers) to use a confirm prompt (remember, it's a blocking dialog).
<script>
var warnMessage = "Unsaved changes. Do you really want to leave the page?";
$('a.k-link').on('click', function (e) {
if (isDirty && !confirm(warnMessage)) {
e.preventDefault();
}
});
</script>
There are at least two reasons to avoid onbeforeunload:
The spec doesn't require a browser to display the message you
provide, and not all browsers do, and
the correct event is actually beforeunload
You can and should handle this event through window.addEventListener() and the beforeunload event. More documentation is available there. (MDN)
I'm just guessing, since the Kendo UI scripts aren't exactly fun to read through, but the 'selected' class is getting applied because a navigation event was started, even though you cancel it; the Kendo script is probably just listening for a successful click. onbeforeunload and beforeunload both happen after the click event has resolved (AFAIK, anyway).
Following worked for me (adding e.stopPropagation):
$('a.k-link').on('click', function (e) {
if (isDirty && !confirm(warnMessage)) {
e.preventDefault();
e.stopPropagation();
} });
or by returning false:
$('a.k-link').on('click', function (e) {
if (isDirty && !confirm(warnMessage)) {
return false;
} });

Preventing users from accidentally navigating away from unsaved pages

I got the snippet below from this SO post, and it works when a user tries to reload the page or close the browser etc. but if the user clicks on a link then it lets them naivagate away, and then incorrectly starts displaying the message on the wrong page. I am using pjax for the links.
$(document).ready(function () {
$('textarea').change(function () {
window.onbeforeunload = function () { return "Your changes to the survey have not been saved?" };
});
});
You should use onbeforeunload like this, inconditionally:
<script type="text/javascript">
saved=true; // initially, it is saved (no action has been done)
window.onbeforeunload = confirmExit;
function confirmExit() {
if (!saved) {
return "You did not save, do you want to do it now?";
}
}
</script>
It is not safe to handle this event only when another event is fired. The onchange event of your textarea here probably don't fire before you click on a link so the window won't handle the onbeforeunload at all. The link will work as expected: you will get redirected.
To deal with the saved flag, you could listen to what happens in your textarea, for example, when the user is actually typing something:
$('textarea').keyup(function(){
saved=false;
});
Then, if you save the data in ajax, the save button could set it back to true:
$('#btnSave').click(function(){
// ajax save
saved=true;
});
Otherwise, it will load the next page with the saved flag on.
what about something like the following?
Listening on all <a> links and then, depending on whether the variable needToSave is set to true, showing the message or letting it go.
var needToSave = false; // Set this to true on some change
// listen on all <a ...> clicks
$(document).click("a", function(event){
if (needToSave == true) {
alert("You need to save first");
event.preventDefault();
return;
}
});
UPDATE (as per Roasted's suggestion) this should trigger the unload event every time the link is clicked and perform your existing logic:
// listen on all <a ...> clicks
$(document).click("a", function(event){
$(window).trigger("unload");
});
jsFiddle here - http://jsfiddle.net/k2fYM/

Alert for unsaved changes in form

I want to write Jquery code in master file, so that if there if user changes page and there is any unsaved changes user should get alert.
I got one answer from this: link
However in most solution I will have to write code on all pages. I want it to write only at one place so that everybody dont have to worry to write it in their modules. My code is like:
<script type="text/javascript">
var isChange;
$(document).ready(function () {
$("input[type='text']").change(function () {
isChange = true;
})
});
$(window).unload(function () {
if (isChange) {
alert('Handler for .unload() called.');
}
});
</script>
But everytime i make changes in text boxes .change() event is not firing.
What can be wrong in the code?
EDIT:
I changed .change() to .click and it is fired. i am using jquery 1.4.1..is it because of jquery version that change() is not working?
This is what i am using, Put all this code in a separate JS file and load it in your header file so you will not need to copy this again and again:
var unsaved = false;
$(":input").change(function(){ //triggers change in all input fields including text type
unsaved = true;
});
function unloadPage(){
if(unsaved){
return "You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?";
}
}
window.onbeforeunload = unloadPage;
EDIT for $ not found:
This error can only be caused by one of three things:
Your JavaScript file is not being properly loaded into your page
You have a botched version of jQuery. This could happen because someone edited the core file, or a plugin may have overwritten the $
variable.
You have JavaScript running before the page is fully loaded, and as such, before jQuery is fully loaded.
Make sure all JS code is being placed in this:
$(document).ready(function () {
//place above code here
});
Edit for a Save/Send/Submit Button Exception
$('#save').click(function() {
unsaved = false;
});
Edit to work with dynamic inputs
// Another way to bind the event
$(window).bind('beforeunload', function() {
if(unsaved){
return "You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?";
}
});
// Monitor dynamic inputs
$(document).on('change', ':input', function(){ //triggers change in all input fields including text type
unsaved = true;
});
Add the above code in your alert_unsaved_changes.js file.
A version that use serialization of the form :
Execute this code, when dom ready :
// Store form state at page load
var initial_form_state = $('#myform').serialize();
// Store form state after form submit
$('#myform').submit(function(){
initial_form_state = $('#myform').serialize();
});
// Check form changes before leaving the page and warn user if needed
$(window).bind('beforeunload', function(e) {
var form_state = $('#myform').serialize();
if(initial_form_state != form_state){
var message = "You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?";
e.returnValue = message; // Cross-browser compatibility (src: MDN)
return message;
}
});
If the user change a field then manually rollback, no warn is displayed
change event is fired once the user blurs from input not on every single character inputed.
If you need it to be called every time something is changed (even if focus is still in that input field) you would have to rely on combination of keyup and bunch of events to keep track of pasting/cuting using mouse only.
P.S.
I hope you're aware that your approach to detecting changes isn't the best one? If user input some text, leaves the field and then reverts the changes the script would still alert him about modified text.
you should register events for not only inputs but also textareas, if you mean textarea with text box. You can use keyup for isChange, so that you don't wait for user to blur from this area.
$("input[type='text'], textarea").keyup(function () {
isChange = true;
})
This is really just a different version of #AlphaMale's answer but improved in a few ways:
# Message displayed to user. Depending on browser and if it is a turbolink,
# regular link or user-driven navigation this may or may not display.
msg = "This page is asking you to confirm that you want to leave - data you have entered may not be saved."
# Default state
unsaved = false
# Mark the page as having unsaved content
$(document).on 'change', 'form[method=post]:not([data-remote]) :input', -> unsaved = true
# A new page was loaded via Turbolinks, reset state
$(document).on 'page:change', -> setTimeout (-> unsaved = false), 10
# The user submitted the form (to save) so no need to ask them.
$(document).on 'submit', 'form[method=post]', ->
unsaved = false
return
# Confirm with user if they try to go elsewhere
$(window).bind 'beforeunload', -> return msg if unsaved
# If page about to change via Turbolinks also confirm with user
$(document).on 'page:before-change', (event) ->
event.preventDefault() if unsaved && !confirm msg
This is better in the following ways:
It is coffeescript which IMHO automatically makes it better. :)
It is entirely based on event bubbling so dynamic content is automatically handled (#AlphaMale's update also has this).
It only operates on POST forms as GET forms do not have data we typically want to avoid loosing (i.e. GET forms tend to be search boxes and filtering criteria).
It doesn't need to be bound to a specific button for carrying out the save. Anytime the form is submitted we assume that submission is saving.
It is Turbolinks compatible. If you don't need that just drop the two page: event bindings.
It is designed so that you can just include it with the rest of your JS and your entire site will be protected.
Why not simply bind the event to the change callback?
$(":input").change(function()
{
$(window).unbind('unload').bind('unload',function()
{
alert('unsaved changes on the page');
});
});
As an added bonus, you can use confirm and select the last element that triggered the change event:
$(":input").change(function()
{
$(window).unbind('unload').bind('unload',(function(elem)
{//elem holds reference to changed element
return function(e)
{//get the event object:
e = e || window.event;
if (confirm('unsaved changes on the page\nDo you wish to save them first?'))
{
elem.focus();//select element
return false;//in jQuery this stops the event from completeing
}
}
}($(this)));//passed elem here, I passed it as a jQ object, so elem.focus() works
//pass it as <this>, then you'll have to do $(elem).focus(); or write pure JS
});
If you have some save button, make sure that that unbinds the unload event, though:
$('#save').click(function()
{
$(window).unbind('unload');
//rest of your code here
});
Without jQuery:
var unsaved = false;
document.addEventListener("DOMContentLoaded", function() {
var els = document.querySelectorAll('textarea, input, select');
els.forEach( function(el) {
el.addEventListener('change', function() {
unsaved = true;
});
});
window.addEventListener('beforeunload', function(event) {
if(unsaved){
event.returnValue = "string";
}
});
var forms = document.querySelectorAll('form');
forms.forEach( function(form) {
form.addEventListener('submit', function() {
unsaved = false;
});
});
});
The weird 'string' hack explanation can be found here.
I use $('form').change etc. function to set a dirty bit variable. Not suitable to catch all changes (as per previous answers), but catches all that I'm interested in, in my app.

How to reenable event.preventDefault?

I have a web page which I have prevented the default action on all submit buttons, however I would like to re-enable default submit action on a button how can I do this?
I am currently preventing the default action using the following:
$("form").bind("submit", function(e){
e.preventDefault();
});
I have successfully done this using the following:
$(document).ready(function(){
$("form:not('#press')").bind("submit", function(e){
e.preventDefault();
});
But can I do this dynamically when the button is clicked?
You would have to unbind the event and either rebind to a separate event that does not preventDefault or just call the default event yourself later in the method after unbinding.
There is no magical event.cancelled=false;
As requested
$('form').submit( function(ev){
ev.preventDefault();
//later you decide you want to submit
$(this).unbind('submit').submit()
});
Either you do what redsquare proposes with this code:
function preventDefault(e) {
e.preventDefault();
}
$("form").bind("submit", preventDefault);
// later, now switching back
$("form#foo").unbind("submit", preventDefault);
Or you assign a form attribute whenever submission is allowed. Something like this:
function preventDefault(e) {
if (event.currentTarget.allowDefault) {
return;
}
e.preventDefault();
}
$("form").bind("submit", preventDefault);
// later, now allowing submissions on the form
$("form#foo").get(0).allowDefault = true;
function(e){ e.preventDefault();
and its opposite
function(e){ return true; }
cheers!
$('form').submit( function(e){
e.preventDefault();
//later you decide you want to submit
$(this).trigger('submit'); or $(this).trigger('anyEvent');
With async actions (timers, ajax) you can override the property isDefaultPrevented like this:
$('a').click(function(evt){
e.preventDefault();
// in async handler (ajax/timer) do these actions:
setTimeout(function(){
// override prevented flag to prevent jquery from discarding event
evt.isDefaultPrevented = function(){ return false; }
// retrigger with the exactly same event data
$(this).trigger(evt);
}, 1000);
}
This is most complete way of retriggering the event with the exactly same data.
I had a similar problem recently. I had a form and PHP function that to be run once the form is submitted. However, I needed to run a javascript first.
// This variable is used in order to determine if we already did our js fun
var window.alreadyClicked = "NO"
$("form:not('#press')").bind("submit", function(e){
// Check if we already run js part
if(window.alreadyClicked == "NO"){
// Prevent page refresh
e.preventDefault();
// Change variable value so next time we submit the form the js wont run
window.alreadyClicked = "YES"
// Here is your actual js you need to run before doing the php part
xxxxxxxxxx
// Submit the form again but since we changed the value of our variable js wont be run and page can reload (and php can do whatever you told it to)
$("form:not('#press')").submit()
}
});
You can re-activate the actions by adding
this.delegateEvents(); // Re-activates the events for all the buttons
If you add it to the render function of a backbone js view, then you can use event.preventDefault() as required.

Categories

Resources