6

I have a multi-form which is divided into three fieldsets. The workflow I'm trying to achieve is:

  • User is presented form, which has fields from the first fieldset.
  • User completes what they can and hits the .next button.
  • validate() occurs on .next button click, if invalid (i.e. required fields are not filled in), I want to add an error class to those inputs so I can style them (add red borders).
  • If they are correct (all fields are filled in that are required), then continue on animating in the next fieldset.

At the moment my validate() is showing up a bit glitched. For example in my demo below, perform the following steps:

  • Click the next button (check the console to see the message to confirm the button has been clicked).
  • Notice how no error messages have come up?
  • Now click on the first name field. Notice how the error message now shows up?
  • This is also weird as address is also a required field (as defined in the JS) but doesn't show up with errors?

On the button click, I want the errors to show up.

Now, let's say first name and address (two required fields in that fieldset) are filled out, on next click, since those fields are valid, I want to animate in the next fieldset, but the submitHandler isn't working? Unsure why?

Demo:

jQuery(function($) {

  var current_fs, next_fs, previous_fs;
  var left, opacity, scale;
  var animating;

  $(".next").click(function() {
  
  console.log('next is clicked');


    $("form").validate({
      rules: {
        // name : param
        fname: "required",
        address: "required",
        phone: {
          required: true,
          matches: "^(\\d|\\s)+$",
          minlength: 11,
          maxlength: 11
        }
      },
      messages: {
        fname: "Please enter your firstname",
        address: "Please enter your address",
        phone: "Please enter a valid phone number"
      },

      // if validation is correct, animate in next fieldset
      submitHandler: function(form) {
        if (animating) return false;
        animating = true;
        current_fs = $(this).parent();
        next_fs = $(this).parent().next();
        $("#progressbar li").eq($("fieldset").index(next_fs)).addClass("active");
        next_fs.show();
        current_fs.animate({
          opacity: 0
        }, {
          step: function(now, mx) {
            scale = 1 - (1 - now) * 0.2;
            left = (now * 50) + "%";
            opacity = 1 - now;
            current_fs.css({
              'transform': 'scale(' + scale + ')',
              'position': 'absolute'
            });
            next_fs.css({
              'left': left,
              'opacity': opacity,
              'height': 'auto',
              'padding': '60px 50px'
            });
          },
          duration: 800,
          complete: function() {
            current_fs.hide();
            animating = false;
          },
          easing: 'easeInOutBack'
        });
      }
    });

    $('input').blur(function() {
      $("form").validate().element("input");
    });

  });



});
.form {
  min-height: 800px;
  user-select: none;
  overflow: hidden;
}
.form form#rsvpForm {
  width: 600px;
  margin: 50px auto;
  text-align: center;
  position: relative;
}
.form form#rsvpForm fieldset {
  background: white;
  border: 0 none;
  border-radius: 3px;
  box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.4);
  padding: 60px 50px;
  box-sizing: border-box;
  position: relative;
  width: 100%;
  display: block !important;
}
.form form#rsvpForm fieldset:not(:first-of-type) {
  opacity: 0;
}
.form form#rsvpForm input,
.form form#rsvpForm textarea {
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 3px;
  margin-bottom: 10px;
  width: 100%;
  box-sizing: border-box;
  outline: none;
}
.form form#rsvpForm input.error,
.form form#rsvpForm textarea.error {
  border: 1px solid red;
}

.form form fieldset .error__message{
  display: none;
}

.form form fieldset.has-error .error__message{
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js?ver=5.3.2'></script>
<script type="text/javascript" src='https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js'></script>

<div class="form" id="rsvp-form">

  <form id="rsvpForm" action="" method="post">

    <!-- fieldset 1 -->
    <fieldset>
      <input type="text" name="fname" placeholder="First name*" />
      <textarea name="address" placeholder="Address*"></textarea>
      <input type="button" id="confirm" name="next" class="next" value="Next" />
    </fieldset>
    
    <!-- fieldset 2 -->
    <fieldset>
      <input type="tel" name="phone" placeholder="Phone*" required />
      <input type="button" id="confirm" name="next" class="next" value="Next" />
    </fieldset>
    

    <!-- fieldset 3 -->
    <fieldset>
      <textarea name="other" placeholder="Enter your note here ..." required></textarea>
      <input type="submit" name="submit" class="submit" value="Submit" />
    </fieldset>



  </form>

</div>
Sparky
  • 98,165
  • 25
  • 199
  • 285
Freddy
  • 683
  • 4
  • 35
  • 114

3 Answers3

0

For this to work, you must create 3 separate forms. Each form will have its own validation. Setting all 3 validation on one form will always result as invalid as the validation is active and the field required is left unfilled.

<div class="form" id="rsvp-form">

  <form id="rsvpForm1" action="" method="post">
    <!-- fieldset 1 -->
    <fieldset id="field1">
      <input type="text" name="fname" placeholder="First name*" />
      <textarea name="address" placeholder="Address*"></textarea>
      <button>Next</button>
    </fieldset>
    <!--/ fieldset 1 -->
  </form>

 <form id="rsvpForm2" action="" method="post">
    fieldset 2
    <!-- fieldset 2 -->
    <fieldset id="fieldset2">
      <input type="tel" name="phone" placeholder="Phone*" required />
      <button>Next</button>
    </fieldset>
  </form>
  <!--/ fieldset 2 -->

 <form id="rsvpForm3" action="" method="post">
    fieldset 3
    <!-- fieldset 3 -->
    <fieldset id="fieldset3">
      <textarea name="other" placeholder="Enter your note here ..." required></textarea>
      <button>Next</button>
    </fieldset>
    <!--/ fieldset 3 -->
 </form>

</div>

Secondly, remove the input type button and place a button instead. Finally, add ids to all 3 forms and use that to validate the form on submission. Also, I added the validations and messages in the form of an object as it is more readable this way.

jQuery(function($) {

  var current_fs, next_fs, previous_fs;
  var left, opacity, scale;
  var animating;

  /*First Form*/
  $("#rsvpForm1").validate({
    rules: {
      fname: {
        required: true,
      },
      address: {
        required: true,
        // minlength: 5
      }
    },
    messages: {
      fname: {
        required: "Please enter your firstname",
      },
      address:{
        required: "Please enter your address",
      }
    },

    // if validation is correct, animate in next fieldset
    submitHandler: function(form) {
      formSubmit(form,'#fieldset1','#fieldset2');
    }
  });
  /*----------*/

  /*Second Form*/
  $("#rsvpForm2").validate({
    rules: {
      phone: {
        required: true,
        minlength: 5,
        maxlength: 11  
      }
    },
    messages: {
      phone:{
        required: "Please enter a valid phone number",
        matches: "Invalid value",
        minlength: "Min length is exceeded",
        maxlength: "Max length is exceeded",
      } 
    },

    // if validation is correct, animate in next fieldset
    submitHandler: function(form) {
      formSubmit(form,'#fieldset2','#fieldset3');
    }
  });
  /*-----------*/

  /*Third Form*/
  $("#rsvpForm3").validate({
    rules: {
      phone: {
        required: true,
        matches: "^(\\d|\\s)+$",
        minlength: 11,
        maxlength: 11 
      }
    },
    messages: {
      other:{
        required: "Please enter a message"
      }, 
    },

    // if validation is correct, animate in next fieldset
    submitHandler: function(form) {
      formSubmit(form);
    }
  });
  /*----------*/


  function formSubmit(form, current, next){
    if (animating) return false;
    animating = true;
    current_fs = $(current);
    next_fs = $(next);
    next_fs.addClass("active");

    next_fs.show();
    current_fs.animate({
        opacity: 0
      }, {
        step: function(now, mx) {
          scale = 1 - (1 - now) * 0.2;
          left = (now * 50) + "%";
          opacity = 1 - now;
          current_fs.css({
            'transform': 'scale(' + scale + ')',
            'position': 'absolute'
          });
          next_fs.css({
            'left': left,
            'opacity': opacity,
            'height': 'auto',
            'padding': '60px 50px'
          });
        },
        duration: 800,
        complete: function() {
          current_fs.hide();
          animating = false;
        },
        easing: 'easeInOutBack'
      });
  }

});

Working Example: https://jsfiddle.net/mrAhmedkhan/pjgz7cwq/

Mr Khan
  • 2,139
  • 1
  • 7
  • 22
0

When clicking on ".next" the form is not submitting and therefore submitHandler is not called and the form is not validated. To solve this move login from submitHandler to .next click handler.

I also change the submit button to a ".next" button with a ".submit" class. This way click handler is called and when .submit class is there then submit the form.

To make form check for any element after clicking next is to add required attribute to those elements.

There was an error with phone validation rule matches: "^(\\d|\\s)+$",. Firstly, there is no corresponding method for matches and secondly the regexp has to be re-written to this /^(\d|\s)+$/ (no quote and no escape).

Here is a working example:

jQuery(function($) {

  var current_fs, next_fs, previous_fs;
  var left, opacity, scale;
  var animating = false;

  // Add method to validate based on regexp
  $.validator.addMethod("matches", function(value, element, regexpr) {
    return regexpr.test(value);
  }, "Please enter a valid phone number");



  $("form").validate({
    rules: {
      // name : param
      fname: {
        required: true
      },
      address: {
        required: true
      },
      phone: {
        required: true,
        matches: /^(\d|\s)+$/,
        minlength: 11,
        maxlength: 11
      }
    },
    messages: {
      fname: "Please enter your firstname",
      address: "Please enter your address",
      phone: "Please enter a valid phone number"
    },
  });

  // Add process to .next click not submit
  $(".next").click(function() {
    if ($(this).hasClass("submit"))
      $("form").submit();

    current_fs = $(this).parent();

    let isValid = true;
    current_fs.children("[required]").each(function(i, e) {
      const valid = current_fs.validate().element(jQuery(e));
      if (!valid)
        isValid = false;
    });

    if (!isValid) return false;
    if (animating) return false;
    animating = true;
    current_fs = $(this).parent();

    next_fs = $(this).parent().next();
    $("#progressbar li").eq($("fieldset").index(next_fs)).addClass("active");
    next_fs.show();
    current_fs.animate({
      opacity: 0
    }, {
      step: function(now, mx) {
        scale = 1 - (1 - now) * 0.2;
        left = (now * 50) + "%";
        opacity = 1 - now;
        current_fs.css({
          'transform': 'scale(' + scale + ')',
          'position': 'absolute'
        });
        next_fs.css({
          'left': left,
          'opacity': opacity,
          'height': 'auto',
          'padding': '60px 50px'
        });
      },
      duration: 800,
      complete: function() {
        current_fs.hide();
        animating = false;
      },
      easing: 'easeInOutBack'
    });
  });
});
.form {
  min-height: 800px;
  user-select: none;
  overflow: hidden;
}

.form form#rsvpForm {
  width: 600px;
  margin: 50px auto;
  text-align: center;
  position: relative;
}

.form form#rsvpForm fieldset {
  background: white;
  border: 0 none;
  border-radius: 3px;
  box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.4);
  padding: 60px 50px;
  box-sizing: border-box;
  position: relative;
  width: 100%;
  display: block !important;
}

.form form#rsvpForm fieldset:not(:first-of-type) {
  opacity: 0;
}

.form form#rsvpForm input,
.form form#rsvpForm textarea {
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 3px;
  margin-bottom: 10px;
  width: 100%;
  box-sizing: border-box;
  outline: none;
}

.form form#rsvpForm input.error,
.form form#rsvpForm textarea.error {
  border: 1px solid red;
}

.form form fieldset .error__message {
  display: none;
}

.form form fieldset.has-error .error__message {
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js?ver=5.3.2'></script>
<script type="text/javascript" src='https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js'></script>

<div class="form" id="rsvp-form">

  <form id="rsvpForm" action="" method="post">

    <!-- fieldset 1 -->
    <fieldset>
      <input type="text" name="fname" placeholder="First name*" required />
      <textarea name="address" placeholder="Address*" required></textarea>
      <input type="button" id="confirm" name="next" class="next" value="Next" />
    </fieldset>

    <!-- fieldset 2 -->
    <fieldset>
      <input type="tel" name="phone" placeholder="Phone*" required />
      <input type="button" id="confirm1" name="next" class="next" value="Next" />
    </fieldset>


    <!-- fieldset 3 -->
    <fieldset>
      <textarea name="other" placeholder="Enter your note here ..." required></textarea>
      <input type="button" name="submit" class="next submit" value="Submit" />
    </fieldset>



  </form>

</div>
Kalimah
  • 11,217
  • 11
  • 43
  • 80
0

I observed that your current was not working because you have given display: block !important; for all fieldset,so when you where validating first fieldset it was also checking for phone which is in different fieldset.To overcome this you can use display:none for other fieldset in css.

Your submitHandler was not getting called because you have not clicked submit button yet so the code inside submitHandler was not getting executed.Also because of this the field where not getting validated. I have just added one if (form.valid() === true) to check if the fieldset is validate or not depending on that you can call next fieldset.

So here is the working code:

$(function() {

  var current_fs, next_fs, previous_fs;
  var left, opacity, scale;
  var animating;

  $(".next").click(function() {

    console.log('next is clicked');

    var form = $("#rsvpForm");

    // Custom method to validate phone
    $.validator.methods.matches = function(value, element, params) {
      var re = new RegExp(params); //pass regex
      return this.optional(element) || re.test(value); //test value with pattern
    }


    $("form").validate({

      rules: {

        fname: "required",
        address: "required",
        phone: {
          required: true,
          matches: "^(\\d|\\s)+$",
          minlength: 11,
          maxlength: 11
        }
      },
      messages: {
        fname: "Please enter your firstname",
        address: "Please enter your address",
        phone: "Please enter a valid phone number"
      },

      // if validation is correct, animate in next fieldset
      submitHandler: function(form) {
        console.log("done submit now")
        //$("#rsvpForm").submit();
      }
    });

    //checking for each fieldset validation
    if (form.valid() === true) {
      console.log("validate go to next fieldset")
      if (animating) return false;
      animating = true;
      current_fs = $(this).parent();
      next_fs = $(this).parent().next();
      $("#progressbar li").eq($("fieldset").index(next_fs)).addClass("active");
      next_fs.show();
      current_fs.animate({
        opacity: 0
      }, {
        step: function(now, mx) {
          scale = 1 - (1 - now) * 0.2;
          left = (now * 50) + "%";
          opacity = 1 - now;
          current_fs.css({
            'transform': 'scale(' + scale + ')',
            'position': 'absolute'
          });
          next_fs.css({
            'left': left,
            'opacity': opacity,
            'height': 'auto',
            'padding': '60px 50px'
          });
        },
        duration: 800,
        complete: function() {
          current_fs.hide();
          animating = false;
        },
        easing: 'easeInOutBack'
      });
    } else {

      console.log("not validate")
    }


  });

});
#b,
#c {
  display: none;
  /*to hide other fieldset on load*/
}

.form {
  min-height: 800px;
  user-select: none;
  overflow: hidden;
}

.form form#rsvpForm {
  width: 600px;
  margin: 50px auto;
  text-align: center;
  position: relative;
}

.form form#rsvpForm fieldset {
  background: white;
  border: 0 none;
  border-radius: 3px;
  box-shadow: 0 0 15px 1px rgba(0, 0, 0, 0.4);
  padding: 60px 50px;
  box-sizing: border-box;
  position: relative;
  width: 100%;
}

.form form#rsvpForm fieldset:not(:first-of-type) {
  opacity: 0;
}

.form form#rsvpForm input,
.form form#rsvpForm textarea {
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 3px;
  margin-bottom: 10px;
  width: 100%;
  box-sizing: border-box;
  outline: none;
}

.form form#rsvpForm input.error,
.form form#rsvpForm textarea.error {
  border: 1px solid red;
}

.form form fieldset .error__message {
  display: none;
}

.form form fieldset.has-error .error__message {
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js?ver=5.3.2'></script>
<script type="text/javascript" src='https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js'></script>

<div class="form" id="rsvp-form">

  <form id="rsvpForm" action="" method="post">

    <!-- fieldset 1 -->
    <fieldset id="a">
      <input type="text" class="form-control" name="fname" placeholder="First name*" required/>
      <textarea name="address" class="form-control" placeholder="Address*" required></textarea>
      <input type="button" id="confirm" name="next" class="next" value="Next" />

    </fieldset>

    <!-- fieldset 2 -->
    <fieldset id="b">

      <input type="tel" class="form-control" name="phone" placeholder="Phone*" required/>

      <input type="button" id="confirm" name="next" class="next" value="Next" />
    </fieldset>


    <!-- fieldset 3 -->
    <fieldset id="c">
      <div class="form-group">
        <textarea name="other" class="form-control" placeholder="Enter your note here ..."></textarea>
      </div>
      <input type="submit" name="submit" class="submit" value="Submit" />
    </fieldset>



  </form>

</div>
Swati
  • 28,069
  • 4
  • 21
  • 41