As an example we will create simple web application with person editing form. Edited Person object will have first name, last name, e-mail and nested address object which will be represented by immutable Address class.
In this guide we will use Maven as dependency management system but feel free to use other build tool like Gradle or SBT. Refer to Starting Maven web project in Eclipse to create and configure new web application project with managed dependencies.
Add dependency on formio library to pom.xml:
<dependency> <groupId>net.formio</groupId> <artifactId>formio</artifactId> <version>1.6.0</version> </dependency>
and implementation of bean validation API such a Hibernate Validator (you can use any other implementation of bean validation API, but some must be present in project's classpath):
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.4.Final</version> </dependency>
Formio is not necessarily bound to servlet API, you can retrieve request data from another abstraction of request, but in this project we will concentrate on classic HttpServletRequest from servlet API.
Create new servlet using menu New - Other... - Web - Servlet, set java package to com.examples, class name to PersonController, edit URL mapping for the servlet to just / (root of the application), generate doGet and doPost methods. The generated controller should look like this (add an additional processRequest method that was not generated by the wizard):
@WebServlet("/") public class PersonController extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request, response); } }
Method processRequest is called from the both doGet and doPost methods. Also create the template /WEB-INF/jsp/index.jsp with the following content:
<html> <body> <h1>Formio Getting Started</h1> </body> </html>
You can run the application using Run As - Run on Server or just start the Tomcat server shown in the Servers view.
Open http://localhost:8080/formio-getstarted/ in your web browser and you should see the content of index template.
Let's create edited Person class:
package com.examples.domain; public class Person { private String firstName; private String lastName; private String email; private Address contactAddress; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Address getContactAddress() { return contactAddress; } public void setContactAddress(Address contactAddress) { this.contactAddress = contactAddress; } }
and immutable Address class for contactAddress property:
package com.examples.domain; public class Address { private final String street; private final String city; private final Integer zipCode; public Address( @ArgumentName("street") String street, @ArgumentName("city") String city, @ArgumentName("zipCode") Integer zipCode) { this.street = street; this.city = city; this.zipCode = zipCode; } public String getStreet() { return street; } public String getCity() { return city; } public Integer getZipCode() { return zipCode; } }
To make binding of the data to the non-default constructor possible, the constructor arguments must be marked using @ArgumentName annotation that specifies name of the bound property.
As a next step, we can create form definition - immutable object which can be freely shared and cached. For example in a static variable of a Controller. Formio currently offers two kinds of mappings for construction of a form:
We can define our person-editing form for e.g. as a field of the PersonController:
private static final FormMapping<Person> personForm = Forms.automatic(Person.class, "person").build();
That's it. Using the automatic mapping, all the properties will be automatically transformed to definitions of form fields. Also properties of nested contactAddress will be processed to a nested mapping (and its form fields). We are entitling the form using "person" name.
Next we can fill the form with the initial data and render it in a processRequest method that is called from both doGet/doPost methods of the servlet:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); Person person = new Person(); person.setFirstName("John"); FormData<Person> formData = new FormData<Person>(person, ValidationResult.empty); FormMapping<Person> filledForm = personForm.fill(formData); request.setAttribute("personForm", filledForm); request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request, response); }
We have filled the form definition with the initial data and validation messages and achieved new instance of form filled with data. Our original personForm variable with form definition is not affected by this - it is immutable, hence remains unchanged.
Then we have pushed the filled form to the rendered template as the request attribute named "personForm" (which is standard practice in JSP).
Now we can render the form using the index.jsp template. To make work in JSP more well-arranged, we will use JSTL tags and expression language. Include JSTL library in your pom.xml (and save it, new dependency should occur in Maven Dependencies classpath container in Eclipse):
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
We must include tag libraries in the beginning of the index.jsp file:
<%@page contentType="text/html; charset=UTF-8" %> <%@page pageEncoding="UTF-8" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
And render the form using available properties of the person form (personForm request attribute):
<fmt:bundle basename="com.examples.domain.Person"> <html> <body> <h1>Formio Getting Started</h1> <c:set var="fields" value="${personForm.fields}"/> <form action="<c:url value="/"/>" method="post"> <div> <label><fmt:message key="${fields.firstName.labelKey}"/> <input type="text" name="${fields.firstName.name}" value="${fields.firstName.value}"/> </label> </div> <div> <label><fmt:message key="${fields.lastName.labelKey}"/> <input type="text" name="${fields.lastName.name}" value="${fields.lastName.value}"/> </label> </div> <div> <label><fmt:message key="${fields.email.labelKey}"/> <input type="text" name="${fields.email.name}" value="${fields.email.value}"/> </label> </div> <p><fmt:message key="${personForm.nested['contactAddress'].labelKey}"/>:</p> <c:set var="addressFields" value="${personForm.nested['contactAddress'].fields}"/> <div> <label><fmt:message key="${addressFields.street.labelKey}"/> <input type="text" name="${addressFields.street.name}" value="${addressFields.street.value}"/> </label> </div> <div> <label><fmt:message key="${addressFields.city.labelKey}"/> <input type="text" name="${addressFields.city.name}" value="${addressFields.city.value}"/> </label> </div> <div> <label><fmt:message key="${addressFields.zipCode.labelKey}"/> <input type="text" name="${addressFields.zipCode.name}" value="${addressFields.zipCode.value}"/> </label> </div> <button name="submit" type="submit">Submit</button> </form> </body> </html> </fmt:bundle>
As you can see, we can use properties "fields" and "nested" of the form. Property "fields" is a mapping from property name to FormField (we can use dot notation to access an element of the map in JSP). FormField has its own properties like "name" (full name of the field, used to bind data back to edited object), "labelKey" (full name of the field without possible indexes) or "value" (edited value formatted as a string). We can use labelKey as a key to localization bundle.
We are using Person.properties localization bundle created next to Person class:
person-firstName=First name person-lastName=Last name person-email=E-mail person-contactAddress=Contact address person-contactAddress-street=Street person-contactAddress-city=City person-contactAddress-zipCode=ZIP code
Rendering various form fields can seem repetitive, but it can be actually easily automated using reusable JSP tags. Form mappings and fields already contain all necessary data for rendering the whole form including various flags like required, visible, enabled, readonly, global/field validation messages, custom attributes...
Also, the whole form markup can be generated automatically using FormRenderer class or its extension:
String formMarkup = new FormRenderer(new RenderContext(locale)).renderElement(filledForm);
Rendered markup can be pushed into a template and displayed within a form element. By default, FormRenderer renders form markup using Bootstrap. You can implement your custom subclass of FormRenderer and override "renderMarkup..." methods to implement custom markup/style of rendering common form fields. FormRenderer is intended to be subclassed by implementations that can use your favorite template system to render form elements. Also FormRendererWrapper is available for you to wrap already existing renderer with additional functionality.
When the form is submitted, data is sent back to our PersonController mapped to "/" URL address. We can bind data from the request back to the edited person using bind method of form definition.
Let's expand processRequest method of the controller:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); FormData<Person> formData = null; if (request.getParameter("submit") != null) { // Form was submitted (parameter with the name of submit button is present) RequestParams params = new ServletRequestParams(request); formData = personForm.bind(params); if (formData.isValid()) { // Store edited person and redirect to some "other" page: Person person = formData.getData(); // store this person... response.sendRedirect(response.encodeRedirectURL(request.getContextPath() + "/?success=1")); return; } // else show form with validation errors - formData already holds the current edited person in // formData.getData() and validation messages in formData.getValidationResult() } else { // no submission, render form with initial data Person person = new Person(); person.setFirstName("John"); formData = new FormData<Person>(person, ValidationResult.empty); } // Fill form definition with data and push the resulting filled form to the template FormMapping<Person> filledForm = personForm.fill(formData); request.setAttribute("personForm", filledForm); request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request, response); }
Method bind() accepts preprocessed parameters from the request and returns filled edited object along with validation messages. Class ServletRequestParams is responsible for preprocessing of request parameters when dealing with HttpServletRequest.
Overloaded variant of bind() method can also accept an existing person instance if you want to fill an existing one rather than create a new one (this can be possibly useful when a loaded database entity should be filled from HTTP request). But filling of already existing instance is not recommended because the provided instance is filled from request even when the form is not valid (first it is filled and then validated). You are encouraged to use the default "functional" variant od bind method without providing your instance and copy/store resulting data only when the form is in valid state (use FormData.isValid to check this).
As a quick introduction to form validation possibilities, we will add simple e-mail validator to the email property of Person class. Default implementation of validator uses bean validation API and its (javax.validation.constraint) annotations to define validated constraints. Formio also provides own annotations for additional constraints. Alternatively, validators implementing net.formio.validation.Validator can be used and specified on form field or form mapping level. This works also together with bean validation API annotations.
We can use net.formio.validation.constraints.Email constraint to validate an e-mail:
@Email private String email;
We must add listing of validation messages to the template:
<c:forEach var="message" items="${fields.email.validationMessages}"> <div class="${message.severity.styleClass}"><c:out value="${message.text}" /></div> </c:forEach>
Validation messages can be localized in file ValidationMessages.properties (placed in root of the source codes /src/main/java):
constraints.Email.message=Invalid e-mail
or in file Person.properties we have already created. Person.properties (properties corresponding to edited class) is the first location that is searched for localized validation messages. If the message key is not found there (or the file does not exist), ValidationMessages.properties will be searched as the next location. Also custom message bundle can be configured.
You have seen the steps needed to create and process a form using the Formio library:
Note that form definition is immutable (and so freely reusable) and edited objects can also be immutable. Two-way automatic data binding simplifying tedious and error-prone manipulation with data is performed.
You can prepare template helpers to simplify rendering of the form in your favorite template system (rendering of various types of the form fields from self-contained form definition filled with data is an easily automatable task). Formio library itself does not come with template helpers for any particular template system, but you can look to Formio demo example for an inspiration how to achieve this on the example of JSP tags. Also take a look at FormRenderer class to automatically render the whole forms or their parts - its API can be extended/overriden so it meets your design needs (e.g. template framework can be used to customize default rendering of various types of form fields).
Validation (using bean validation API or net.formio.validation.Validator(s)) is performed and you will simply obtain the validation messages together with the bound data. The library does the hard work and let you fully concentrate on your application logic for processing resulting edited data.