97

I'm doing some simple form validation here and got stuck on a very basic issue. I have 5 field pairs for name and entree (for a dinner registration). The user can enter 1-5 pairs, but an entree must be selected if a name is present. Code:

http://jsfiddle.net/eecTN/1/

<form>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    <input type="submit" value="Submit">
</form>
// Prevent form submit if any entrees are missing
$('form').submit(function(e){

    e.preventDefault();

    // Cycle through each Attendee Name
    $('[name="atendeename[]"]', this).each(function(index, el){

        // If there is a value
        if ($(el).val()) {

            // Find adjacent entree input
            var entree = $(el).next('input');

            // If entree is empty, don't submit form
            if ( ! entree.val()) {
                alert('Please select an entree');
                entree.focus();
                return false;
            }
        }
    });

    $('form').unbind('submit').submit();

});

The error message is working, but it's submitting the form every time. I know there's something wrong with this line:

$('form').unbind('submit').submit();

...but I'm not sure what I need to do.

Wesley Murch
  • 101,186
  • 37
  • 194
  • 228
  • Why you make unbind for your submit form ??? – d.danailov Mar 12 '14 at 21:08
  • 1
    I've seen this post but the solutions look dubious and use `setTimeout`: http://stackoverflow.com/questions/14866626/submitting-form-after-using-e-preventdefault?rq=1 – Wesley Murch Mar 12 '14 at 21:09
  • @d.danailov So it doesn't run the validation again. Please, help me out - what do I need to do? – Wesley Murch Mar 12 '14 at 21:09
  • But it ** should ** run the validation every time, shouldn't it? I mean, what if the user doesn't correct the errors and clicks the button again? – Oscar Paz Mar 12 '14 at 21:11
  • It doesn't need to rerun validation if validation already passed. I just didn't want to end up in an infinite loop (happened while I was writing this earlier). There's probably no good reason for `unbind`. – Wesley Murch Mar 12 '14 at 21:13
  • The infinite loop happened because your code was wrong, so, even if the validation failed, the form tried to submit, which then caused validation and so on. With my changes, or any of the others posted here, the form will only be submitted if there are no errors. – Oscar Paz Mar 12 '14 at 21:16
  • 1
    Why don't you change it so that `e.preventDefault()` is only called if validation fails? Then you can remove the line with `.unbind().submit()`. – nnnnnn Mar 12 '14 at 21:17
  • A good solution too. Probably the best one. – Oscar Paz Mar 12 '14 at 21:18
  • @nnnnnn I didn't realize I could delay calling `e.preventDefault()` in the script. That seems like the right solution. – Wesley Murch Mar 12 '14 at 21:18
  • @nnnnnn You nailed it, I'm having a derpy day so excuse me. Thanks a ton, feel free to post answer for credit, here's my final code: http://jsfiddle.net/eecTN/6/ – Wesley Murch Mar 12 '14 at 21:22
  • 1
    Ok other one is OK but also idea here http://jsfiddle.net/eecTN/9/ thank you for patience – stormdrain Jun 12 '14 at 21:08
  • @stormdrain it working when changed to Mootols Nightly thaks in advancved – Wesley Murch Jun 12 '14 at 21:34

11 Answers11

116

The simplest solution is just to not call e.preventDefault() unless validation actually fails. Move that line inside the inner if statement, and remove the last line of the function with the .unbind().submit().

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 21
    For some reason I thought I needed to call `e.preventDefault()` before anything else or risk some kind of race condition. Thanks for the enlightenment. – Wesley Murch Mar 12 '14 at 21:24
  • 2
    No, the whole function will complete before the default action continues (or doesn't continue if you've cancelled it). If you knew you never wanted the default action for a particular event it would make sense to put that as the first line where it's obvious (e.g., if you wanted to submit the data via Ajax rather than with a form submit, or in a click handler on a link that triggers JS instead of doing standard navigation). Otherwise, putting it in conditional logic is fine. – nnnnnn Mar 12 '14 at 21:27
  • I'm positive in the past with some heavily ajax driven sites race conditions were a problem so the first line in was always e.preventDefault(). Are you 100% sure it's safe to do what you're suggesting? – JamesNZ Jul 31 '14 at 02:29
  • 3
    @JamesNZ - I'm positive it's safe. In a heavily Ajax based site race conditions may be a problem if there are multiple Ajax requests made before the responses come back, because there's no guarantee that the responses will come back in any particular order - but even then you still don't need `e.preventDefault()` as the first line because the current block of synchronous code *will* complete before any Ajax callbacks are triggered. That is, JS will *not* interrupt the currently running function to call an Ajax callback - or a second event handler or timeout callback or whatever. – nnnnnn Jul 31 '14 at 14:00
  • @nnnnnn Ah ok. I'll double check myself on the next project, but that's great if it's not an issue. Thanks for the response. – JamesNZ Aug 01 '14 at 00:27
  • 1
    This is simply brilliant! – KimvdLinde Sep 29 '15 at 15:12
  • 1
    great advice and just got me out of a hole - thank you. – dimButTries Aug 06 '20 at 12:23
57

Actually this seems to be the correct way:

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

    //prevent default
    e.preventDefault();

    //do something here

    //continue submitting
    e.currentTarget.submit();

});
Mike Cottier
  • 671
  • 5
  • 3
49

Use the native element.submit() to circumvent the preventDefault in the jQuery handler, and note that your return statement only returns from the each loop, it does not return from the event handler

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

    var valid = true;

    $('[name="atendeename[]"]', this).each(function(index, el){

        if ( $(el).val() ) {
            var entree = $(el).next('input');

            if ( ! entree.val()) {
                entree.focus();
                valid = false;
            }
        }
    });

    if (valid) this.submit();

});
adeneo
  • 312,895
  • 29
  • 395
  • 388
15

The problem is that, even if you see the error, your return false affects the callback of the .each() method ... so, even if there is an error, you reach the line

$('form').unbind('submit').submit();

and the form is submitted.

You should create a variable, validated, for example, and set it to true. Then, in the callback, instead of return false, set validated = false.

Finally...

if (validated) $('form').unbind('submit').submit();

This way, only if there are no errors will the form be submitted.

Oscar Paz
  • 18,084
  • 3
  • 27
  • 42
6
$('form').submit(function(e){

    var submitAllow = true;

    // Cycle through each Attendee Name
    $('[name="atendeename[]"]', this).each(function(index, el){

        // If there is a value
        if ($(el).val()) {

            // Find adjacent entree input
            var entree = $(el).next('input');

            // If entree is empty, don't submit form
            if ( ! entree.val()) {
                alert('Please select an entree');
                entree.focus();
                submitAllow = false;
                return false;
            }
        }
    });

    return submitAllow;

});
falinsky
  • 7,229
  • 3
  • 32
  • 56
3
$(document).ready(function(){
      $('#myform').on('submit',function(event){
            // block form submit event
            event.preventDefault();

            // Do some stuff here
            ...

            // Continue the form submit
            event.currentTarget.submit();
      });
});

Source

2

Sorry for delay, but I will try to make perfect form :)

I will added Count validation steps and check every time not .val(). Check .length, because I think is better pattern in your case. Of course remove unbind function.

My jsFiddle

Of course source code:

// Prevent form submit if any entrees are missing
$('form').submit(function(e){

    e.preventDefault();

    var formIsValid = true;

    // Count validation steps
    var validationLoop = 0;

    // Cycle through each Attendee Name
    $('[name="atendeename[]"]', this).each(function(index, el){

        // If there is a value
        if ($(el).val().length > 0) {
            validationLoop++;

            // Find adjacent entree input
            var entree = $(el).next('input');

            var entreeValue = entree.val();

            // If entree is empty, don't submit form
            if (entreeValue.length === 0) {
                alert('Please select an entree');
                entree.focus();
                formIsValid = false;
                return false;
            }

        }

    });

    if (formIsValid && validationLoop > 0) {
        alert("Correct Form");
        return true;
    } else {
        return false;
    }

});
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
d.danailov
  • 9,594
  • 4
  • 51
  • 36
1

came across the same prob and found no straight solution to it on the forums etc. Finally the following solution worked perfectly for me: simply implement the following logic inside your event handler function for the form 'submit' Event:

document.getElementById('myForm').addEventListener('submit', handlerToTheSubmitEvent);

function handlerToTheSubmitEvent(e){
    //DO NOT use e.preventDefault();
   
    /*
    your form validation logic goes here
    */

    if(allInputsValidatedSuccessfully()){
         return true;
     }
     else{
         return false; 
     }
}

SIMPLE AS THAT; NOTE: when a 'false' is returned from the handler of the form 'submit' event, the form is not submitted to the URI specified in the action attribute of your html markup; until and unless a 'true' is returned by the handler; and as soon as all your input fields are validated a 'true' will be returned by the Event handler, and your form is gonna be submitted;

ALSO NOTE THAT: the function call inside the if() condition is basically your own implementation of ensuring that all the fields are validated and consequently a 'true' must be returned from there otherwise 'false'

0

In my case there was a race, as I needed the ajax response to fill a hidden field and send the form after it's filled. I fixed it with putting e.preventDefault() into a condition.

var all_is_done=false;
$("form").submit(function(e){
  if(all_is_done==false){
   e.preventDefault();
   do_the_stuff();
  }
});
function do_the_stuf(){
  //do stuff
  all_is_done=true;
  $("form").submit();
}
Fanky
  • 1,673
  • 1
  • 18
  • 20
-1

Why not bind the submit button event than the form itself? it would really much easier and safer if you bind the buttons than the form itself as the form will mostly submit unless you will use preventDefault()

$("#btn-submit").on("click", function (e) {
    var submitAllow = true;
    $('[name="atendeename[]"]', this).each(function(index, el){
        // If there is a value
        if ($(el).val()) {
            // Find adjacent entree input
            var entree = $(el).next('input');

            // If entree is empty, don't submit form
            if ( ! entree.val()) {
                alert('Please select an entree');
                entree.focus();
                submitAllow = false;
                return false;
            }
        }
    });
    if (submitAllow) {
        $("#form-attendee").submit();
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="form-attendee">
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    Name: <input name="atendeename[]">
    Entree: <input name="entree[]"><br>
    <button type="button" id="btn-submit">Submit<button>
</form>
  • 1
    Because clicking the submit button is not the only way to submit a form. Also because that isn't preventing the submit when it doesn't validate even on the button click. – Jimbo Jonny Jan 31 '21 at 08:01
-1

Binding to the button would not resolve for submissions outside of pressing the button e.g. pressing enter

David Latty
  • 87
  • 1
  • 6