Forms & Validation demos

Multiple Fields / Complex validation

Please note :

  • When you're done playing with the form, make sure you scroll to the How-To below the form, a quick tutorial will show you how the demo works.
  • There is no client-side validation in this demo. It's on purpose since we want to demonstrate how server-side validation works and how the Validation Messages can be displayed.
  • The demo is fully functional even when javascript is disabled.

In this demo, we're going to look at more complex validation, where many different types of fields are validated, and where the validity of some fields depends on the value of other fields.

Enter any valid or invalid values in the following form and submit it to see the validation in action!

You can also try the tests suggested on the right...

Validations to test :
Validations to test :
  • This is a group of fields : they all have the same "name" attribute. Because of this, when the form is submitted those fields are retrived together as an array on the server.

    The three tags are required.

    Fill all tags
Validations to test :
  • A favorite drink is required.

    Choosing "beer" is invalid if the "Action on submit" field is set to "Warning choice!"... Try it!

Validations to test :
  • You have to pick exactly two numbers and both must be odd or both be even!

Validations to test :
  • There is no validation on this field, but you can check that if you select multiple of its options (using the CTRL/Command key), they will still be selected when the form is redisplayed.


Validations to test :
  • Choosing the "Warning choice" will result in a Warning Validation Message. If you choose this option, and if all the other fields are valid, you will see that the form is going to be processed and you are going to be redirected.

    This is to demonstrate that Warning Validation Messages don't make a form invalid, only Error Validation Messages do!

Code

How to - Frontend

We already introduced the process of validating a form in the Introduction demo. In this one, we focus mostly on how to display the various types of fields, how to validate a field by comparing its value with another field, and how to run conditional validations.

Let's first examine how we render the fields in the HTML template, and how we use Validation Filters for the validation messages...

The "email" and "emailAgain" are simple text inputs. Here's the "email" field :

<input type="text" class="form-control" id="email" 
       name="demoForm.email" placeholder="Email"
       value="{{demoForm.email | default('')}}" />
{{validation['demoForm.email'] | validationMessages()}}

We use "{{demoForm.email | default('')}}" to fill the inital value [3] and "{{validation['demoForm.email'] | validationMessages()}}" to display the potential Validation Messages [4].

The tags section is more interesting :

<div class="col-sm-2 fieldGroupLabelAndMessages">
    <label class="control-label">Tags *</label>
    {{validation['demoForm.tags'] | validationGroupMessages()}}
</div>

<div class="col-sm-4">

    <input type="text" 
           class="form-control {{validation['demoForm.tags[0]'] | validationClass()}}" 
           id="tag1" name="demoForm.tags[0]" placeholder="Tag 1"
           value="{{demoForm.tags[0] | default('')}}" />
    {{validation['demoForm.tags[0]'] | validationMessages()}}
    
    <input type="text" 
           class="form-control {{validation['demoForm.tags[1]'] | validationClass()}}" 
           id="tag2" name="demoForm.tags[1]" placeholder="Tag 2"
           value="{{demoForm.tags[1] | default('')}}" />
    {{validation['demoForm.tags[1]'] | validationMessages()}}
    
    <input type="text" 
           class="form-control {{validation['demoForm.tags[2]'] | validationClass()}}" 
           id="tag3" name="demoForm.tags[2]" placeholder="Tag 3"
           value="{{demoForm.tags[2] | default('')}}" />
    {{validation['demoForm.tags[2]'] | validationMessages()}}
    
</div>

Those fields form a group. We can know this by looking at their "name" attributes : "demoForm.tags[0]", "demoForm.tags[1]" and "demoForm.tags[2]". This syntax indicates that those fields are part of the same group and have a specific position in it.

At the top of this code snippet, you can see that there is section which describe the section those fields are in : "Tags *". We use this zone to output the Validation Messages associated with the group itself :


{{validation['demoForm.tags'] | validationGroupMessages()}}

If at least one of the "tag" field is invalid, this filter will display an error : "Some tags are invalid.". This error is not associated with a particular field, but with the group itself.

Next, we have the "Favorite drink" radio buttons group.

In this section too we have a zone where some Validation Messages may be displayed for the group itself :

<div class="col-sm-2 fieldGroupLabelAndMessages">
    <label class="control-label">Favorite drink *</label>
    {{validation['demoForm.drink']  | validationGroupMessages()}}
</div>

Otherwise, every radio button of the group is listed using a code similar to this :

<div class="radio">
    <label for="drink0">
        <input type="radio" 
               id="drink0" 
               name="demoForm.drink"
               {{demoForm.drink | checked("tea")}}
               value="tea" /> Tea</label>
</div>

<div class="radio">
    <label for="drink1">
        <input type="radio" 
               id="drink1" 
               name="demoForm.drink"
               {{demoForm.drink | checked("coffee")}}
               value="coffee" /> Coffee</label>
</div>

//...

Explanation :

  • 5 : All the radio buttons of this group have the exact same "name" attribute! Not only does this make the fields being a group in the eyes of the browser (only one radio button of this group can be checked at a given time), but it also tells Spincast that those radio buttons are part of the same group.
  • 6 : We use the checked(...) filter to determine if a radio button must be checked or not. Learn more about this filter in the Provided functions and filters section.

The "Pick 2 numbers *" section allows more than one option to be picked, so checkboxes are used :

<div class="checkbox">
    <label for="num1">
        <input type="checkbox" 
               id="num1" 
               name="demoForm.numbers[0]"
               {{demoForm.numbers[0] | checked("1")}}
               value="1" /> 1</label>
</div>
<div class="checkbox">
    <label for="num2">
        <input type="checkbox" 
               id="num2" 
               name="demoForm.numbers[1]"
               {{demoForm.numbers[1] | checked("2")}}
               value="2" /> 2</label>
</div>

//...

Those checkboxes are part of the same group, so we want to receive them as an array when the form is submitted. For that reason, it's recommended to use brakets ("[]") at the end of their "name" attributes and, when possible, to specify the position of the element inside those brackets.

As for the "drink" radio buttons fields, we use the checked(...) filter to determine if an option must be checked or not.

Finally, the "Favourite music styles?" and the "Action on submit" are both <select> fields. The first one, "musicStyles", allow multiple options to be selected :


<select multiple id="musicStyles" name="demoForm.musicStyles[]"  
        class="form-control">
    <option value="rock" {{demoForm.musicStyles | selected("rock")}}>Rock</option>
    <option value="pop" {{demoForm.musicStyles | selected("pop")}}>Pop</option>
    <option value="jazz" {{demoForm.musicStyles | selected("jazz")}}>Jazz</option>
    <option value="metal" {{demoForm.musicStyles | selected("metal")}}>Metal</option>
    <option value="classical" {{demoForm.musicStyles | selected("classical")}}>Classical</option>
</select>

Since more than one option can be selected, we make the "name" attribute of this field end with "[]". That way, the selected options are always going to be grouped together as an array when the form is submitted.

Note that with <select> fields, we use the selected(...) filter to determine if an option must be selected or not. This filter is going to output a "selected" attribute, where required.

The last thing on the frontend we'll going to have a look at is how to display the status of the form itself. Notice that we display a message at the top of the form to show if it is valid or not :


{% if validation['demoForm._'] | validationSubmitted()%}

    {% if validation['demoForm._'] | validationHasErrors() %}
        {% set alertClass = "alert-danger" %}
        {% set status = "contains errors" %}
        
    {% elseif validation['demoForm._'] | validationHasWarnings() %}
        {% set alertClass = "alert-warning" %}
        {% set status = "contains warnings" %}
        
    {% elseif validation['demoForm._'] | validationIsValid() %}
        {% set alertClass = "alert-success" %}
        {% set status = "is valid" %} 
        
    {% endif %}

    <div class="row">
        <div class="col-sm-6">
            <div class="alert {{alertClass}}">
                <img src="/public/images/icons/info2.png" /> The Form <strong>{{status}}</strong> 
            </div>
        </div>
    </div> 
{% endif %}

Explanation :

  • 2 : This line validates that the form has been submitted. We don't want to display any message when the form is displayed for the first time! The "_" element is a special element which represents a Validation Set itself.
  • We use the validationHasErrors() filter [4], the validationHasWarnings() filter [8] and the validationIsValid() filter [12] to pick the appropriate CSS class and status to use for the message we're going to display.
  • 20 : We output the selected CSS class.
  • 21 : We output the selected status.

How to - Backend

On the backend, this demo shows how to run a validation or not depending on the result of a previous validation. For example :

String email = form.getString("email");

if (StringUtils.isBlank(email)) {
    form.addError("email",
                  "email_empty",
                  "The email is required");
}

if (form.isValid("email") && !form.validators().isEmailValid(email)) {
    form.addError("email",
                  "email_invalid",
                  "The email is invalid");
}

As you can see, we only perform the second validation if the first one is a success! This is often very useful since we do not want to display multiple error messages for a single field when, in fact, the field was simply left empty.

In this demo, we also see how to validate a field by comparing it's value to the value of another field. For example :

String email = form.getString("email");
// ...
String emailAgain = form.getString("emailAgain");
// ...
if (form.isValid("emailAgain") && !emailAgain.equals(email)) {
    form.addError("emailAgain",
                  "emailAgain_mustMatch",
                  "Must match the first email field.");
}

There are more validations performed in this demo! We suggest you have a look at DemoHtmlFormsMultipleFieldsController code to see all of them.

More info

Make sure you also try the first demo of this section, Introduction - Single field which introduces forms and validation using Spincast.

Otherwise, you can learn everything about forms and validation in the dedicated Forms section of the documentation.