[jQuery] Confirmation Dialog Upon Submission

I’m trying to get a confirmation dialog to appear when a form is submitted, but I’ve actually been having quite a lot of difficulty in getting it to work.

Here’s what should happen:

  1. The user clicks the “purge logs” submit button
  2. A confirmation dialog appears
  3. If the user clicks the “yes” button, the form is submitted. If the user clicks the “no” button or “close” button, nothing happens.

Here’s what actually happens:
The forms submits itself without waiting for the user to click yes or no in the dialog that appears.

Here’s the code:


<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
<script type="text/javascript">

$().ready(function(){


        $('form').submit(function(e){

            var confirm = ConfirmDialog('Confirm', 'Are you sure?');

            if(confirm){
                form.submit();
            }

            
            
        });
            
        function ConfirmDialog(title,message){
            
            var confirmdialog = $('<div></div>').appendTo('body')
            .html('<div><h6>'+message+'</h6></div>')
            .dialog({
                modal: true, title: title, zIndex: 10000, autoOpen: false,
                width: 'auto', resizable: false,
                buttons: {
                    Yes: function(){
                        $(this).dialog("close");
                        return true;
                    },
                    No: function(){
                        $(this).dialog("close");
                        return false;
                    }
                },
                close: function(event, ui){
                    $(this).remove();
                    return false;
                }
            });


            return confirmdialog.dialog("open");
    
        }
});
    

</script>
</head>
<body>
<form id="form_purge" name="form_purge" method="post" action="test.php">
<button type="submit" name="submit_purge" id="submit_purge">Purge&nbsp;Logs</button>
</form>
</html>

Hi Force Flow,

The problem with your original script is that when the form is submitted, the variable confirm is being assigned a jQuery object containing your ConfirmDialog, which then evaluates to true, thus causing the form to be submitted.

You can check this by preventing the form’s default action and logging the value of confirm to the console:

$('form').submit(function(e){
  e.preventDefault();
  var confirm = ConfirmDialog('Confirm', 'Are you sure?');
  console.log(confirm[0]);
});

outputs:

<div id="ui-id-1" class="ui-dialog-content ui-widget-content" style="width: auto; min-height: 73px; max-height: none; height: auto;">
  <div>
    <h6>Are you sure?</h6>
  </div>
</div>

regardless of what the user clicks.

I’m sure I’m overthinking things here, but one way to get around this would be to use a promise, which will only be resolved once the user has clicked “Yes”.

Here’s an example:

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>jQuery form</title>
  </head>
  
  <body>
    <form id="form_purge" name="form_purge" method="post" action="test.php">
      <button type="submit" name="submit_purge" id="submit_purge">Purge&nbsp;Logs</button>
    </form>
    
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
    <script type="text/javascript">
      var deferred = new $.Deferred(),
          promise = deferred.promise();
      
      $("form").submit(function(e){
        var self = this;
        e.preventDefault();
        promise.done( function(){self.submit();});
        return ConfirmDialog('Confirm', 'Are you sure?');
      });
      
      function ConfirmDialog(title,message){
        var confirmdialog = $('<div></div>').appendTo('body')
        .html('<div><h6>'+message+'</h6></div>')
        .dialog({
          modal: true, title: title, zIndex: 10000, autoOpen: false,
          width: 'auto', resizable: false,
          buttons: {
            Yes: function(){
              deferred.resolve();
              $(this).dialog("close");
            },
            No: function(){
              $(this).dialog("close");
            }
          },
          close: function(event, ui){
            $(this).remove();
            return false;
          }
        });
        return confirmdialog.dialog("open");
      }
    </script>
  </body>
</html>

I hope that helps you.

I would also be glad to hear suggestions from anyone else as to how one might solve this.

I attempted to run your script, but for some reason, it didn’t actually work. [edit: nevermind, I got it working]

In any case, after quite a bit of tinkering, I eventually came up with this:

<html><head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
<script type="text/javascript">


$().ready(function(){

    //when a form is submitted
    $('form').submit(function(e){

        //if a hidden input named confirmed exists in the form, display yes/no dialog
        if($('form input[name=confirm][type=hidden]').val()){

            e.preventDefault();

            var form = $(this);
            
            //since the submit button is not passed, recreate the name/value
            $("input[type=submit], input[type=button], button", form).eq(0).each(function(){
                var self = $(this);
                var tempElement = $("<input type='hidden'/>");

                // clone the important parts of the button used to submit the form.
                tempElement
                    .attr("name", this.name)
                    .val(self.val())
                    .appendTo(form);
            });

            //call the confirm dialog
            ConfirmDialog('Confirm', 'Are you sure?', this);
        }
    });

    
    function ConfirmDialog(title,message,form){    
            
        var confirmdialog = $('<div></div>').appendTo('body')
            .html('<div><h6>'+message+'</h6></div>')
            .dialog({
                modal: true, title: title, zIndex: 10000, autoOpen: false,
                width: 'auto', resizable: false,
                buttons: {
                    Yes: function(){
                        //submit the form
                        form.submit();
                        $(this).dialog("close");
                    },
                    No: function(){
                        $(this).dialog("close");
                    }
                },
                close: function(event, ui){
                    $(this).remove();
                }
            });


        confirmdialog.dialog("open");
    }
    
    
});

</script>
</head>
<body>
<form id="form_purge" name="form_purge" method="post" action="test.php">
<button type="submit" name="submit_purge" id="submit_purge">Purge&nbsp;Logs</button>
<input type="hidden" name="confirm" value="true">
</form>
</html>

The whole problem here is that the preventDefault() function needs to be used on an event. Since a form’s onsubmit attribute can’t pass an event, I had to use some javascript magic to trigger when a form submission was triggered.

Since I had to use $(‘form’).submit(), it was too generic since it caught all form submissions. However, it’s not ideal to define every form I need the dialog for in the submission trigger. So, I put a hidden input element in the forms that I needed the dialog for, and checked for it.

One interesting thing was that when javascript submits the form, the submit button that originally triggered the submit didn’t actually get passed in the form’s data, so I had to add it as a hidden element.

Also, this form can be submitted with or without javascript without any extra code on the server-side, which is a plus.

I’m not sure if this is the best approach or not, so if anyone has other suggestions, I’d be happy to see them.

Oops sorry, I was messing around with it for a couple of minutes after posting.
Here’s a demo of it working: http://hibbard.eu/blog/pages/form-with-promise.html

Would you do me a favour and check if that works for you.
Theoretically it should be cross-browser compatible.

I made a few tweaks to Pullo’s code, but overall, a much more compact solution than what I came up with

$().ready(function(){
    var deferred = new $.Deferred();
    var promise = deferred.promise();
    
    $("form").submit(function(e){
        //if a hidden input named confirmed exists in the form, display yes/no dialog
        if($('form input[name=confirm][type=hidden]').val()){
            var self = this;
            e.preventDefault();
            promise.done(function(){self.submit();});
            ConfirmDialog('Confirm', 'Are you sure?');
        }
    });
    
    function ConfirmDialog(title,message){
        var confirmdialog = $('<div></div>').appendTo('body')
            .html('<div><h6>'+message+'</h6></div>')
            .dialog({
                modal: true, title: title, zIndex: 10000, autoOpen: false,
                width: 'auto', resizable: false,
                buttons: {
                    Yes: function(){
                        deferred.resolve();
                        $(this).dialog("close");
                    },
                    No: function(){
                        $(this).dialog("close");
                    }
                },
                close: function(event, ui){
                    $(this).remove();
                }
            });
        confirmdialog.dialog("open");
    }
    
    
});

Good stuff :slight_smile:
I’m only just getting my head around deferreds/promises/futures etc, but they are proving to be pretty useful.

I don’t use javascript all that much beyond basic validation and UI stuff, so I’m very much unaware of some of the more advanced jQuery features like deferred/promise.

Thanks :slight_smile:

[edit]: Drat, I just realized that the submit button isn’t included in the form data sent to the server. I’ll have to add the code block that recreated it as a hidden element.

[edit2]: The new code:

$().ready(function(){
    
    var deferred = new $.Deferred();
    var promise = deferred.promise();
    
    $("form").submit(function(e){
        //if a hidden input named confirmed exists in the form, display yes/no dialog
        if($('form input[name=confirm][type=hidden]').val()){
            var form = this;
            e.preventDefault();
            
            //since the submit button is not passed, recreate the name/value pair as hidden element
            $("input[type=submit], input[type=button], button", form).eq(0).each(function(){
                var self = $(this);
                var tempElement = $("<input type='hidden'/>");

                //clone the important parts of the button used to submit the form.
                tempElement
                    .attr("name", this.name)
                    .val(self.val())
                    .appendTo(form);
            });
            
            promise.done(function(){form.submit();});
            ConfirmDialog('Confirm', 'Are you sure?');
        }
    });
    
    function ConfirmDialog(title,message){
        var confirmdialog = $('<div></div>').appendTo('body')
            .html('<div><h6>'+message+'</h6></div>')
            .dialog({
                modal: true, title: title, zIndex: 10000, autoOpen: false,
                width: 'auto', resizable: false,
                buttons: {
                    Yes: function(){
                        deferred.resolve();
                        $(this).dialog("close");
                    },
                    No: function(){
                        $(this).dialog("close");
                    }
                },
                close: function(event, ui){
                    $(this).remove();
                }
            });
        confirmdialog.dialog("open");
    }
    
    
});

It may just be me, but I think that some overthinking may have been occurring here.
Can’t you just pass a success function to the ConfirmDialog function, which will be run when the Yes button is pressed?

Here’s the setup, where we prevent the default submit action, this time, and we save off a reference to the form so that we can then trigger an actual submit event later on from that function being passed to ConfirmDialog:


$('form').on('submit', function (e) {
    e.preventDefault();
    var form = this;

    confirm = confirmDialog('Confirm', 'Are you sure?', function () {
        form.submit();
    });
});

And here’s the payoff, where we decide out if we want to to run that success function or not:


function confirmDialog(title, message, success) {
    ...
            Yes: function () {
                success();
                $(this).dialog("close");
            },
            No: function () {
                $(this).dialog("close");
            }
    ...
}

You can see the code in action at http://jsfiddle.net/pmw57/67Ge2/

I have also taken the liberty to run the code through jslint.com to help weed out any issues there. Seeing function names with a capital first letter cause me to go twitch

The submit button “submit_purge” is still missing from the form data with that method.

I never would’ve thought of putting a function in passed variable and calling it like that. It’s certainly a less convoluted approach.

[edit]: I reworked your code to include a few things from the code I posted earlier

$().ready(function(){
    
    $('form').submit(function(e){
        
        //if a hidden input named confirmed exists in the form, display yes/no dialog
        if($('form input[name=confirm][type=hidden]').val()){
            e.preventDefault();
            var form = this;
            
          //since the submit button is not passed, recreate the name/value pair as hidden element
            $("button[type=submit], input[type=submit], button", form).eq(0).each(function(){
                var self = $(this);
                var tempElement = $('<input type="hidden">');
    
                //clone the important parts of the button used to submit the form.
                tempElement
                    .attr("name", this.name)
                    .val(self.val())
                    .appendTo(form);
            });
            
            ConfirmDialog('Confirm', 'Are you sure?', function () {
                form.submit();
            });
        }
        
    });
    
    function ConfirmDialog(title, message, success){
        var confirmdialog = $('<div></div>').appendTo('body')
        .html('<div><h6>'+message+'</h6></div>')
        .dialog({
            modal: true, title: title, zIndex: 10000, autoOpen: false,
            width: 'auto', resizable: false,
            buttons: {
                Yes: function(){
                    success();
                    $(this).dialog("close");
                },
                No: function(){
                    $(this).dialog("close");
                }
            },
            close: function(event, ui){
                $(this).remove();
            }
        });
        
        confirmdialog.dialog("open");
    }
});

Nah, it’s not just you :slight_smile:

Thanks, Paul! Your solution is much better.

I made an additional improvement to the “recreate name/value pair” section. The hidden elements are now created in their own span, which is removed when the dialog is closed. This is so that multiple hidden elements with the same name don’t stack up within the form.

$().ready(function(){
    
    var tempFormContainerID= 'temp-form-container';
    
    $('form').submit(function(e){
        
        //if a hidden input named confirmed exists in the form, display yes/no dialog
        if($('form input[name=confirm][type=hidden]').val()){
            e.preventDefault();
            var form = this;

            $('<span id="'+tempFormContainerID+'"></span>').hide().appendTo(form);
            
            
          //since the submit button is not passed, recreate the name/value pair as hidden element
            $("button[type=submit], input[type=submit], button", form).eq(0).each(function(){
                var self = $(this);
                var tempElement = $('<input type="hidden">');
    
                //clone the important parts of the button used to submit the form.
                tempElement
                    .attr("name", this.name)
                    .val(self.val())
                    .appendTo($('#'+tempFormContainerID));
            });
            
            confirmDialog('Confirm', 'Are you sure?', function () {
                form.submit();
            });
        }
        
    });
    
    function confirmDialog(title, message, success){
        var confirmdialog = $('<div></div>').appendTo('body')
        .html('<div><p>'+message+'</p></div>')
        .dialog({
            modal: true, title: title, zIndex: 10000, autoOpen: false,
            width: 'auto', resizable: false,
            buttons: {
                Yes: function(){
                    success();
                    $(this).dialog("close");
                },
                No: function(){
                    $(this).dialog("close");
                }
            },
            close: function(event, ui){
                $('#'+tempFormContainerID).remove();
                $(this).remove();
            }
        });
        
        confirmdialog.dialog("open");
    }
    
    
});

Thanks for the initial suggestion on this approach Paul :slight_smile: