Ratpacked: Validating Forms
Ratpack is a lean library to build HTTP applications. Ratpack for example doesn't include functionality to validate forms that are submitted from a web page. To add form validation to our Ratpack application we must write our own implementation.
Let's write a simple application with a HTML form. We will use Hibernate Validator as a JSR 303 Bean Validation API implementation to validate the form fields. IN our application we also use the MarkupTemplateModule
so we can use Groovy templates to generate HTML. We have a simple form with two fields: username and email. The username field is required and the email field needs to have a valid e-mail address. The following class uses annotations from Hibernate Validator to specify the constraints for these two fields:
// File: src/main/groovy/com/mrhaki/ratpack/validation/User.groovy
package com.mrhaki.ratpack.validation
import groovy.transform.CompileStatic
import groovy.transform.ToString
import org.hibernate.validator.constraints.Email
import org.hibernate.validator.constraints.NotEmpty
import javax.validation.constraints.NotNull
@CompileStatic
@ToString(includeNames = true)
class User implements Serializable {
@NotEmpty
String username
@Email
String email
}
Now we write our Ratpack application to handle a form submitted to /submit
. To validate our User
class we must get a java.validation.Validator
instance. We use the buildDefaultValidatorFactory
method of the javax.validation.Validation
class to create a Validator
instance. We then add it to the registry, so handlers can use it when needed.
Next we use the parse
method of Context
to parse the input to a Form
instance. We then use the values from the Form
instance to populate our properties in the User
class. We validate the User
instance and get back a set of ConstraintViolation
objects if there any validation errors. In our logic we display the validation errors on the HTML page, if there are no errors a next page is shown.
// File: src/ratpack/Ratpack.groovy
import com.mrhaki.ratpack.validation.User
import ratpack.form.Form
import ratpack.groovy.template.MarkupTemplateModule
import javax.validation.ConstraintViolation
import javax.validation.Validation
import javax.validation.Validator
import static ratpack.groovy.Groovy.groovyMarkupTemplate
import static ratpack.groovy.Groovy.ratpack
ratpack {
bindings {
// Add module to support Groovy templates.
module(MarkupTemplateModule)
// Create Validator instance to be used
// in the handlers.
bindInstance(Validator, Validation.buildDefaultValidatorFactory().validator)
}
handlers {
// Handle submitted data. The closure
// argument has the Validator instance
// we created in the bindings{} block.
post('submit') { Validator validator ->
// Parse the values that are submitted to
// a Form object.
parse(Form)
.map { Form form ->
// Transform Form to a User object
// and set the username and email
// properties.
new User(
username: form.username,
email: form.email)
}
.then { User user ->
// Validate the User object.
final Set\> constraintViolations =
validator.validate(user)
if (constraintViolations.size() > 0) {
// If there are violations we convert them
// to a map where the keys are the field names
// and the value the ConstraintViolation object.
// We use this in our template to display the errors.
final Map\> formErrors =
constraintViolations.collectEntries { violation ->
\[(violation.propertyPath.toString()): violation\]
}
// Render page so we can display errors.
render groovyMarkupTemplate('index.gtpl', user: user, formErrors: formErrors)
} else {
// Everything was ok, redirect to
// different page.
redirect("thankyou?username=${user.username}")
}
}
}
get('thankyou') {
// Show page with the value of request parameter username.
final String username = request.queryParams.username
render groovyMarkupTemplate('thankyou.gtpl', username: username)
}
get {
// Show default page.
render groovyMarkupTemplate('index.gtpl', user: new User())
}
files {
dir "public"
}
}
}
With the following two Groovy templates we can render the HTML we want:
// File: src/ratpack/template/index.gtpl
yieldUnescaped ''
html {
head {
meta(charset:'utf-8')
title("Ratpack: Form validation")
link(href: '/images/favicon.ico', rel: 'shortcut icon')
}
body {
section {
h2 'Form'
form(action: 'submit', method: 'POST') {
def inputFields = \[
username: \[type: 'text', value: user.username\],
email: \[type: 'email', value: user.email\]
\]
inputFields.each { fieldName, fieldConf ->
div(style: formErrors?.get(fieldName) ? 'color: red' : '') {
label(for: fieldName, "${fieldName.capitalize()}: ")
input(
type: fieldConf.type,
value: fieldConf.value,
name: fieldName,
id: fieldName)
if (formErrors?.get(fieldName)) {
yield(formErrors\[fieldName\].message)
}
}
}
div {
input(type: 'submit', value: 'Submit')
}
}
}
}
}
// File: src/ratpack/template/thankyou.gtpl
yieldUnescaped ''
html {
head {
meta(charset:'utf-8')
title("Ratpack: Thank you")
link(href: '/images/favicon.ico', rel: 'shortcut icon')
}
body {
section {
h2 'Thank you'
p "You have used our Ratpack application to register as ${username}."
}
}
}
What is left is to add the dependencies for Hibernate Validator to our project.
// File: build.gradle
plugins {
id 'io.ratpack.ratpack-groovy' version '1.1.1'
id 'com.github.johnrengelman.shadow' version '1.2.2'
}
repositories {
jcenter()
}
dependencies {
compile 'org.hibernate:hibernate-validator:5.2.2.Final'
runtime 'javax.el:javax.el-api:2.2.4'
runtime 'org.glassfish.web:javax.el:2.2.4'
}
We are ready to run our application. Once we have started the application we can open the default page in our browser. If we leave the username empty and use a invalid email value we get the following response when we submit the form:
Written with Ratpack 1.1.1.