{#========================================== Demos - HTML Forms - Multiple Fields ==========================================#} {% extends "../htmlForms.html" %} {% block demoSectionClasses %}demo_html_forms_multiple{% endblock %} {% block meta_title %}Demos - HTML Forms - Multiple Fields{% endblock %} {% block meta_description %}Multiple Fields HTML Forms with complex validation demo{% endblock %} {% set demoId = "multiple" %} {% block scripts %} {% endblock %} {% block demoBody %}
Please note :
Here, 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 inputs in the following form and submit it to see the validations in action!
You can also try the tests suggested on the right...
HTML template (the frontend part) :
multiple.html
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 their Validation Messages...
The "email" and "emailAgain" are simple text inputs.
Here's the "email" field :
{% verbatim %}
<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 :
{% verbatim %}
<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
(learn more).
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 :
{% verbatim %}
{{validation['demoForm.tags'] | validationGroupMessages()}}
"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. You can learn more about this
in the Predefine Validation options section.
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 :
{% verbatim %}
<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 : {% verbatim %}
<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 :
"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 (learn more).
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 :
{% verbatim %}
<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.
Make sure you read the Getting the submitted form data
sectiton to learn why.
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 :
{% verbatim %}
<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 :
{% verbatim %}
{% if validation._ | validationSubmitted()%}
{% if validation._ | validationHasErrors() %}
{% set alertClass = "alert-danger" %}
{% set status = "contains errors" %}
{% elseif validation._ | validationHasWarnings() %}
{% set alertClass = "alert-warning" %}
{% set status = "contains warnings" %}
{% elseif validation._ | 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 :
"_" element is a special element which
represents a Validation Set itself (more info)
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.
CSS class.
On the backend, this demo first shows how to run a validation or not depending on the result of a previous validation. For example :
ValidationSet lastResult =
validationSet.validationNotBlank().jsonPath("email").failMessageText("Please enter the email").validate();
if(lastResult.isValid()) {
validationSet.validationEmail().jsonPath("email").failMessageText("Please enter a valid email").validate();
}
As you can see, we only perform the second validation if the first one is valid! This is often very useful since we do not want to display 5 different 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 :
lastResult = validationSet.validationEquivalent(form.getString("email"))
.jsonPath("emailAgain")
.failMessageText("Must match the first email field.")
.validate();
In this snippet, we retrieve the "email" element from the
form object using form.getString("email"). We
then compare this value to the "emailAgain" validated element using the
validationEquivalent(...) predefined validation.
If they are not equivalent (which is a loose version of an equality check), an
Error Validation Message is recorded for the validated "emailAgain" field.
You can test this by yourself by entering two valid
but different emails in the demo form!
Another interesting validation in this demo is :
if("beer".equals(form.getString("drink")) && validationSet.isWarning("action")) {
validationSet.addError("drink",
"BEER_AND_WARNING_ACTION",
"'Beer' is invalid if the 'Action on submit' field is a Warning!");
}
This custom validation doesn't use any Predefined Validation and the resulting
Error Validation Message, when the validation fails, is added manually to the
Validation Set, using the "drink"
validation key.
There are more validations performed in this demo! We suggest you have a look
at DemoHtmlFormsMultipleFieldsController
code to see them all.
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 those topics in the Validation and Forms sections of the documentation.
{% endblock %}