{#========================================== Docs : "Forms" ==========================================#}

Forms

This section is about HTML Forms, as used on traditional websites. If you use a SPA client-side, you in general don't use such POSTed forms, you rather use javascript to send and receive Json objects. Both approach are supported out of the box by Spincast but this specific section is about traditional HTML forms and their validation!

We're going to learn :

The form model

You need to prepare a model to back the form you are going to display. This model is sometimes called "form backing object", "form backing bean" or "command object". It's the object used to transfert the values of a form from the server to the client (to populate the form's fields) and vice versa.

You create that form model as a JsonObject and you add it to the response model, the root object the Templating Engine has access to. For example :

public void displayUserForm(AppRequestContext context) {

    // Creates a JsonObject as the the model for the form
    JsonObject userForm = context.json().create();
    
    // ... populates it with inital values, 
    // if required
    userForm.put("email", "test@example.com");

    // Adds the form model to the response model
    context.response().getModel().put("userForm", userForm);
    
    // Displays the form using a HTML template
    context.response().sendTemplateHtml("/templates/userCreationTemplate.html");
}

You can then use this form model to populate fields, in the template. For example :

{% verbatim %}

<form method="post">
    <input type="text" 
           name="userForm.email"
           value="{{userForm.email}}" />
    //...
</form>

{% endverbatim %}

When the form is submitted, you get back its model using the context.request().getFormData() method, in your Route Handler. We will see this in details in the Getting the submitted form data section, but here's a quick example :

public void manageUserForm(AppRequestContext context) {

    // Gets the submitted form model
    JsonObject userForm = context.request()
                                 .getFormData()
                                 .getJsonObject("userForm");
                                 
   // ...
}

You may have noticed that we are not using a dedicated class to represent the form model (a "UserForm" class, for example) : we use a dynamic JsonObject. Spincast supports both approaches, but we think a dynamic JsonObject is better for the model of a form. Here's why :

In case you still want to use a dedicated class to back your form, you are free to do so, and here's a quick example.... First, you would create a dedicated class for the model :

public class UserCreationForm {
    
    private String username;
    private String email;
    private String captcha;
    
    //... Getters
    //... Setters
}

You would then create a model instance like so :

public void displayUserForm(AppRequestContext context) {

    // A typed form model
    UserCreationForm userForm = new UserCreationForm();
    
    // ... that is quickly converted to a 
    // JsonObject anyway when added to the response model!
    context.response().getModel().put("userForm", userForm);
    
    sendMyTemplate();
}

When the form is submitted, you would then convert context.request().getFormData(), which is a JsonObject, to an instance of your UserCreationForm class :

public void manageUserForm(AppRequestContext context) {

    // Back to a typed version of the form model!
    UserCreationForm userForm = context.request()
                                       .getFormData()
                                       .getJsonObject("userForm")
                                       .convert(UserCreationForm.class);
                                       
   // ...
}

Displaying the Form

By using a dynamic JsonObject as the form model, a benefit is that we don't have to create in advance all the elements required to match the fields of our HTML form. Simply by using a valid JsonPath as the "name" attribute of a field, the element will automatically be created on the form model...

As an example, let's again use a form dedicated to create a user. This form will display two fields : one for a username and one for an email. Our inital form model doesn't have to specify those two elements when it is first created :

public void myRouteHandler(AppRequestContext context) {

    // Empty model! No username and no email 
    // element specified...
    JsonObject userForm = context.json().create();
    
    // Adds the form model to the response model
    context.response().getModel().put("userForm", userForm);

    // Renders a template containing the user creation
    // form
    context.response().sendTemplateHtml("/templates/userCreationTemplate.html");
}

Here's what that HTML form may looks like (we are using the syntax for the default Templating Engine, Pebble) :

{% verbatim %}

<form method="post">
    <div class="form-group">
        <input type="text" 
               class="form-control" 
               name="userForm.username"
               value="{{userForm.username | default('')}}" />
    </div>
    <div class="form-group">
        <input type="text" 
               class="form-control" 
               name="userForm.email"
               value="{{userForm.email | default('')}}" />
    </div>
    <input type="submit" />
</form>

{% endverbatim %} Notice that even if the form model doesn't contain any "username" or "email" elements, we still bind them to the fields using their JsonPaths [6] and here [12]. This is possible in part because we use the default("") filter : this filter tells Pebble to use an empty string if the element doesn't exist yet (this is required if strictVariables is on).

The "name" attributes of the fields are very important : they represent the JsonPaths that Spincast is going to use to dynamically create the required elements on the model, when the form is submitted.

Let's say this form is submitted. You would then access the values of the fields like so, in your Route Handler :

public void myRouteHandler(AppRequestContext context) {

    // Gets the form model
    JsonObject userForm = context.request()
                                 .getFormData()
                                 .getJsonObject("userForm");
    
    // The "username" and "email" elements have been
    // automatically created to represent the submitted
    // fields!
    String username = userForm.getString("username");
    String email = userForm.getString("email");                                
}

Without extracting the model of the form first, you could also reference those elements directly using their full JsonPaths :

public void myRouteHandler(AppRequestContext context) {

    String username = context.request().getFormData().getString("userForm.username");
    String email = context.request().getFormData().getString("userForm.email");
}

As you can see, Spincast uses the "name" attribute of a field as a JsonPath to dynamically create an element for the field. This gives you a lot of flexibility client-side since you can dynamically generate new fields or even entire new forms, using javascript.

Text based fields

Text based fields, such as text, password, email and textarea are very easy to manipulate :

Quick example :
{% verbatim %}

<input type="text" 
       name="user.email"
       value="{{user.email | default('')}}" />

{% endverbatim %}

Text based field groups

Sometimes we want multiple text fields to be grouped together. For example, let's say we want various "tags" to be associated with an "article" object. Each of those "tags" will have its own dedicated field on the form, but we want all the "tags" to be available as a single array when they are submitted. To achieve that :

What we are doing, in fact, is to use the JsonPath to target each element! For example :
{% verbatim %}

<form method="post">
    <input type="text" class="form-control" name="article.tags[0]"
           value="{{article.tags[0] | default('')}}" />
    
    <input type="text" class="form-control" name="article.tags[1]"
           value="{{article.tags[1] | default('')}}">
    
    <input type="text" class="form-control" name="article.tags[2]"
           value="{{article.tags[2] | default('')}}">
    <input type="submit" />
</form>

{% endverbatim %}

When this form is submitted, you have access to the three "tags" as a single JsonArray :

public void manageArticle(AppRequestContext context) {

    JsonObject model = context.request().getFormData();
    
    // Get all the tags of the article, as an array
    JsonArray tags = model.getJsonArray("article.tags");
    
    // You could also access one of the tag directly, using
    // its full JsonPath
    String thirdTag = model.getString("article.tags[2]");
    
    //...
}

Select fields

The select fields come in two flavors : single value or multiple values. To use them :

Here's an example for a single value select field :
{% verbatim %}


<select name="user.favDrink" class="form-control">
    <option value="tea" {{user.favDrink | selected("tea")}}>Tea</option>
    <option value="coffee" {{user.favDrink | selected("coffee")}}>Coffee</option>
    <option value="beer" {{user.favDrink | selected("beer")}}>WBeer</option>
</select>

{% endverbatim %}

In this example, the values of the option elements are hardcoded, they were known in advance : "tea", "coffee" and "beer". Here's a version where the option elements are dynamically generated :

{% verbatim %}

<select name="user.favDrink" class="form-control">
    {% for drink in allDrinks %}
        <option value="{{drink.id}}" {{user.favDrink | selected(drink.id)}}>{{drink.name}}</option>
    {% endfor %}
</select>

{% endverbatim %}

In this example, the selected(...) filter compares the current favorite drink of the user ("user.favDrink") to the value of every option element and outputs the "selected" attribute if there is a match.

Displaying a multiple values select field is similar, but :

For example :
{% verbatim %}


<select multiple name="user.favDrinks[]" class="form-control">
    <option value="tea" {{user.favDrinks | selected("tea")}}>Tea</option>
    <option value="coffee" {{user.favDrinks | selected("coffee")}}>Coffee</option>
    <option value="beer" {{user.favDrinks | selected("beer")}}>WBeer</option>
</select>

{% endverbatim %}

Radio Buttons

To display a radio buttons group :

Let's first have a look at an example where the values of the radio buttons are hardcoded :

{% verbatim %}

<div class="form-group">
    <label for="drinkTea">
        <input type="radio" 
               id="drinkTea" 
               name="user.favDrink"
               {{user.favDrink | checked("tea")}}
               value="tea"/> Tea</label>
    
    <label for="drinkCoffee">
        <input type="radio" 
               id="drinkCoffee" 
               name="user.favDrink"
               {{user.favDrink | checked("coffee")}}
               value="coffee"> Coffee</label>
    
    <label for="drinkBeer">
        <input type="radio" 
               id="drinkBeer" 
               name="user.favDrink"
               {{user.favDrink | checked("beer")}}
               value="beer"> Beer</label>
</div>

{% endverbatim %}

Let's focus on the first radio button of that group. First, its "name" attribute :

{% verbatim %}

<label for="drinkTea">
    <input type="radio" 
           id="drinkTea" 
           name="user.favDrink"
           {{user.favDrink | checked("tea")}}
           value="tea"/> Tea</label>

{% endverbatim %}
As we already said, the "name" attribute of a field is very important. Spincast uses it to create the element on the form model, when the form is submitted. This "name" will become the JsonPath of the element on the form model. In our example, the model would contain a "user" root element with a "favDrink" element under it.

Let's now have a look at the checked(...) filter :

{% verbatim %}

<label for="drinkTea">
    <input type="radio" 
           id="drinkTea" 
           name="user.favDrink"
           {{user.favDrink | checked("tea")}}
           value="tea"/> Tea</label>

{% endverbatim %}

We don't know in advance if a radio button should be checked or not, this depends on the current value of the "user.favDrink" element. That's why we use "checked(...)". This filter will compare the current value of the "user.favDrink" model element to the value of the radio button ("tea" in our example). If there is a match, a "checked" attribute is printed!

Note that the parameter of the "checked(...)" filter can be an array. In that case, the filter will output "checked" if the current value matches any of the elements. For example :

{% verbatim %}

<label for="drinkTea">
    <input type="radio" 
           id="drinkTea" 
           name="user.favDrink"
           {{user.favDrink | checked(["tea", "ice tea", chai"])}}
           value="tea"/> Tea</label>

{% endverbatim %}

This feature is mainly useful when the radio buttons are dynamically generated.

Speaking of dynamically generated radio buttons, let's see an example of those! The creation of the response model, in your Route Handler, may look like this :

public void myRouteHandler(AppRequestContext context) {

    // Gets the response model
    JsonObject model = context.response().getModel();

    // Creates the available drink options
    JsonArray allDrinks = context.json().createArray();

    JsonObject drink = context.json().create();
    drink.put("id", 1);
    drink.put("name", "Tea");
    allDrinks.add(drink);

    drink = context.json().create();
    drink.put("id", 2);
    drink.put("name", "Coffee");
    allDrinks.add(drink);

    drink = context.json().create();
    drink.put("id", 3);
    drink.put("name", "Beer");
    allDrinks.add(drink);

    // Creates a "user" object and specifies 
    // his favorite drink
    JsonObject user = context.json().create();
    user.put("favDrink", 2);

    // Adds the drinks options and the user object 
    // to the response model
    model.put("allDrinks", allDrinks);
    model.put("user", user);

    // Renders an HTML template containing the form
    // to display
    context.response().sendTemplateHtml("/templates/userTemplate.html");
}

With this response model in place, we can dynamically generate the radio buttons group and check the current favorite one of the user :

{% verbatim %}

<div class="form-group">
    {% for drink in allDrinks %}
        <label for="drink_{{drink.id}}">
            <input type="radio" 
                   id="drink_{{drink.id}}" 
                   name="user.favDrink"
                   {{user.favDrink | checked(drink.id)}}
                   value="{{drink.id}}"/> {{drink.name}}</label> 
    {% endfor %}
</div>

{% endverbatim %}

You may notice that, in this example, we haven't scoped the elements of the form under a dedicated "userForm" parent element as we did in other examples. We added the "allDrinks" and "user" objects as root elements (instead of naming them "userForm.allDrinks" and "userForm.user"). You don't have to scope the elements of a form inside a parent element when you are working with a simple template... But as soon as your template is somewhat complex, for example if it contains more than one form, then scoping elements is a good idea.

Checkboxes

Checkboxes are often used in one of those two situations :

First, let's look at a single checkbox field :

{% verbatim %}

<label for="tosAccepted">
    <input type="checkbox" 
           id="tosAccepted" 
           name="myForm.tosAccepted"
           {{myForm.tosAccepted | checked(true)}}
           value="true" /> I agree to the Terms of Service</label>

{% endverbatim %}

Note that, even if the value of the checkbox is "true" as a string, you can use true as a boolean as the filter parameter. This is possible because the checked(...) filter (and the selected(...) filter) compares elements using equivalence, not equality. So "true" would match true and "123.00" would match 123.

When this field is submitted, you would be able to access the boolean value associated with it using :

public void myRouteHandler(AppRequestContext context) {

    JsonObject model = context.request().getFormData();
    
    boolean tosAccepted = model.getBoolean("myForm.tosAccepted");
    
    //...
}

Now, let's see an example of a group of checkboxes :

{% verbatim %}

<div class="form-group">
    <label for="drinkTea">
        <input type="checkbox" 
               id="drinkTea" 
               name="user.favDrinks[0]"
               {{user.favDrinks[0] | checked("tea")}}
               value="tea"/> Tea</label>
    
    <label for="drinkCoffee">
        <input type="checkbox" 
               id="drinkCoffee" 
               name="user.favDrinks[1]"
               {{user.favDrinks[1] | checked("coffee")}}
               value="coffee"> Coffee</label>
    
    <label for="drinkBeer">
        <input type="checkbox" 
               id="drinkBeer" 
               name="user.favDrinks[2]"
               {{user.favDrinks[2] | checked("beer")}}
               value="beer"> Beer</label>
</div>

{% endverbatim %}
Here, the checkboxes are grouped together since they share the same "name" attribute, name that is suffixed with the position of the element in the group. In fact, their "name" is the JsonPath of their associated element on the form model.

With this in place, we can access all the checked "favorite drinks" as a single array, in our Route Handler :

public void myRouteHandler(AppRequestContext context) {

    JsonObject model = context.request().getFormData();
    
    // The checked favorite drinks, as an array!
    JsonArray favDrinks = model.getJsonArray("user.favDrinks");
    
    //...
}

Note that the positions used in the "name" attributes are kept when we receive the array! This means that if the user only checked "beer" for example (the last option), the array received in our Route handler would be [null, null, "beer"], not ["beer"]! This is a good thing because the JsonPath we use for an element always stays valid ("user.favDrinks[2]" here).

File upload

Uploading a file is very easy using Spincast. The main difference between a "file" field and the other types of fields is that the uploaded file will not be available on the model when the form is submitted. You'll have to use a dedicated method to retrieve it in your Route handler.

The HTML part is very standard :

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" class="form-control" name="fileToUpload">
    <button type="submit">Submit</button>
</form>

To retrieve the uploaded file, you use one of the getUploadedFileXXX(...) methods on the request() add-on. For example :

public void myRouteHandler(AppRequestContext context) {

    File uploadedFile = context.request().getUploadedFileFirst("fileToUpload");
}

Note that even if the uploaded file is not part of the form data, you can still add Validation Messages for it, if the file is not valid.

Form validation introduction

Validating a submitted form involves three main steps :

Make sure you read the dedicated Validation section! Here, we are going to take for granted that you already know how to create a Validation Set from a JsonObject and how to validate that JsonObject.

Getting the submitted form data

When a HTML form is submitted, Spincast threats the "name" attributes of the fields as JsonPaths in order to create a JsonObject representing the form model. In other words, Spincast converts the submitted form to a JsonObject so you can easily validate and manipulate it.

You access that JsonObject representing the submitted form using the getFormData() method of the request() add-on. For example :

public void myRouteHandler(AppRequestContext context) {

    JsonObject myForm = context.request().getFormData();
    
    //... validates the form
}

If you have more than one form on the same HTML page, you will probably want to scope the names of every field. For example, a text field of a form named "myFirstForm" could be :

{% verbatim %}

<input type="text" 
       class="form-control" 
       name="myFirstForm.email"
       value="{{myFirstForm.email | default('')}}" />

{% endverbatim %}
And you can then retrieve the JsonObject representing this particular "myFirstForm" form model using :

public void myRouteHandler(AppRequestContext context) {

    // Gets the "myFirstForm" form model
    JsonObject myFirstForm = context.request()
                                    .getFormData()
                                    .getJsonObject("myFirstForm");
                               
    // Gets the value of the "email" field from 
    // the "myFirstForm" form model
    String email = myFirstForm.getString("email");
}

Instead of "extracting" the form model first, you can also select the value of a field directly from the root of the form data :

public void myRouteHandler(AppRequestContext context) {

    String email = context.request().getFormData().getString("myFirstForm.email");
}

Spincast supports arrays to group a bunch of fields together. When multiple fields have the same "name" attribute, they are automatically grouped together when a form is submitted. For example :

{% verbatim %}

<div class="form-group">
    <label for="drinkTea">
        <input type="checkbox" 
               id="drinkTea" 
               name="user.favDrinks"
               {{user.favDrinks[0] | checked("tea")}}
               value="tea"/> Tea</label>
    
    <label for="drinkCoffee">
        <input type="checkbox" 
               id="drinkCoffee" 
               name="user.favDrinks"
               {{user.favDrinks[1] | checked("tea")}}
               value="coffee"> Coffee</label>
</div>

{% endverbatim %}
When submitted, this form would result in the two "user.favDrinks" fields to be grouped in a single array. We could access this array with :

public void myRouteHandler(AppRequestContext context) {

    JsonArray tags = context.request().getFormData().getJsonArray("user.favDrinks");
    
    //...
}

There are two problems with this approach though :

The first improvement we can make, is to make sure we always receive an array, never a single element alone! To achieve that, we can use a "name" attribute ending with "[]". For example :

{% verbatim %}

<div class="form-group">
    <label for="drinkTea">
        <input type="checkbox" 
               id="drinkTea" 
               name="user.favDrinks[]"
               {{user.favDrinks[0] | checked("tea")}}
               value="tea"/> Tea</label>
    
    <label for="drinkCoffee">
        <input type="checkbox" 
               id="drinkCoffee" 
               name="user.favDrinks[]"
               {{user.favDrinks[1] | checked("tea")}}
               value="coffee"> Coffee</label>
</div>

{% endverbatim %}
This guarantees that an array will always be created to group the "user.favDrinks[]" fields, even if only one of those fields is actually submitted. But this approach still doesn't guarantee the order of the elements in the resulting array, when multiple fields are submitted!

The third and best method, is to suffix the "names" attribute not only with "[]", but with "[X]", where "X" is the position of the element in the group! For example :

{% verbatim %}

<div class="form-group">
    <label for="drinkTea">
        <input type="checkbox" 
               id="drinkTea" 
               name="user.favDrinks[0]"
               {{user.favDrinks[0] | checked("tea")}}
               value="tea"/> Tea</label>
    
    <label for="drinkCoffee">
        <input type="checkbox" 
               id="drinkCoffee" 
               name="user.favDrinks[1]"
               {{user.favDrinks[1] | checked("tea")}}
               value="coffee"> Coffee</label>
</div>

{% endverbatim %}
Now, the order is also guaranteed : the "user.favDrinks[0]" field will always be at the first position of the resulting array and "user.favDrinks[1]" always be at the second position. Note that since the positions are now guaranteed, Spincast will automatically adds null elements if some indexes are not associated with a submitted field.

In this third solution, notice that the "name" attribute is the actual JsonPath of the associated element. We like that!

Validating the form

Once you we the JsonObject representing the model of the submitted form, we can start validating it. To do so, we create a Validation Set from this object. For example :

public void myRouteHandler(AppRequestContext context) {

    // Gets the form model
    JsonObject myForm = context.request().getFormData().getJsonObject("myForm");
    
    // Gets a Validation Set for the form model
    JsonObjectValidationSet formValidationSet = myForm.validationSet();
    // ...
}

We are going to use this Validation Set object to store the Validation Messages resulting from the validation we're about to perform.

Here's a quick example where we make sure that an "email" field is valid and, if it is, that the email is not already used by another user in our system :

public void myRouteHandler(AppRequestContext context) {

    JsonObject myForm = context.request().getFormData().getJsonObject("myForm");
    JsonObjectValidationSet formValidationSet = myForm.validationSet();

    ValidationSet lastResult = formValidationSet.validationEmail().jsonPath("email").validate();
    if(lastResult.isValid()) {

        boolean emailIsAvailable = isEmailAvailable(myForm.getString("email"));
        if(!emailIsAvailable) {
            formValidationSet.addError("email",
                                       "EMAIL_ALREADY_EXISTS",
                                       "This email is already used.");
        }
    }
    // ...
}

Explanation :

When we are done validating a form, we usually have two choices :

In order to prepare a form to be redisplayed with the Validation Messages resulting from its validation, we need to add the form model and the resulting Validation Set back to the response model! This is required so the Templating Engine can redisplay the fields with their submitted (but potentially invalid values) and the various Validation Messages. For example :

public void myRouteHandler(AppRequestContext context) {

    JsonObject myForm = context.request().getFormData().getJsonObject("myForm");
    JsonObjectValidationSet formValidationSet = myForm.validationSet();
    
    // ... Perfoms validation and saves Validation Messages
    // on the Validation Set
    
    if(formValidationSet.isValid()) {
        
        // ... Calls services / business logic / data sources
        // to process the submitted data
        
        // Redirects to a confirmation page
        context.response().redirect(FlashMessageLevel.SUCCESS,
                "The form has been processed successfully.");
    } else {
        
        // Adds the form model back to the response model
        context.response().getModel().put("myForm", myForm);
        
        // Adds the Validation Set to the response model
        // using a key of our choice
        context.response().getModel().put("validation", formValidationSet);
        
        // Redisplays the form using the Templating Engine
        context.response().sendTemplateHtml("/templates/myTemplate.html");
    }
}

Explanation :

In this example, we added the Validation Set to the response model using a "validation" key. This would result in the Validation Messages to be accessible in the template using validation['some.validation.key']. For example : {% verbatim %}


<div>{{validation['myForm.email']}}</div>

{% endverbatim %}

Now, everything required by our Templating Engine to be able to redisplay the form with the Validation Messages has been made available.

Displaying Validation Messages

At the end of the previous section, we showed that two main elements have to be added to the response model in order to redisplay an invalid form :

If you followed the "Use the JsonPath of a validated element as its validation key" convention as suggested in the Validation Keys section, the final model your templates will have access to will look like this :

{
    "myForm" : {
        "name" : "Stromgol"
        "email" : "abc"
        "books": [
            {
                "title" : "Dune",
                "author": "Frank Herbert"
            },
            {
                "title" : "The Hitchhiker's Guide to the Galaxy",
                "author" : ""
            }
        ]
    },
    
    "validation" : {
        "_" : {
            "hasErrors" : true,
            "hasWarnings" : false,
            "isValid" : false,
            "hasSuccesses" : false
        },
        "myForm.email" : {
            "level" : "ERROR",
            "code" : "VALIDATION_TYPE_EMAIL",
            "text" : "Invalid email address" 
        },
        "myForm.books[1].author" : {
            "level" : "ERROR",
            "code" : "VALIDATION_TYPE_NOT_BLANK",
            "text" : "The author can't be empty" 
        }
    }
    
    // ...
}

This structure of the templating model makes the Validation Messages very easy to be retrieved and displayed. For example :

{% verbatim %}

<div class="form-group">
    <input type="text" 
           class="form-control" 
           name="myForm.email"
           value="{{myForm.email | default('')}}" />
    {{validation['myForm.email'] | validationMessages()}}
</div>

{% endverbatim %} Here, validationMessages() is a Pebble filter provided by Spincast that can be used to display the Validation Messages associated with a field. Since our validation keys are in fact the string representation of the JsonPaths of the validated elements, it's very clear how to retrieve the Validation Messages for a particular field : it is, in most cases, the same as the "name" attribute of that field [4].

Validation Filters

Spincast provides utilities to display the Validation Messages when the default Templating Engine is used, Pebble. But, as we saw, the template model is pretty much a simple Map<String, Object> so no magic is involved and any other Templating Engine could be used!

Have a look at the Forms + Validation demos section to see the following validation filters in action!