DEV Community

Cover image for Building a form in Play Framework
pazvanti
pazvanti

Posted on • Updated on • Originally published at petrepopescu.tech

Building a form in Play Framework

Almost all websites require a form in one way or another. It can be as simple as a search field or a more complex form with many inputs and validation mechanisms. Either way, the main principles are the same and in this post, I will show you how to build a simple user registration form using Play Framework Java. We will cover all aspects, including validation of input data, the Twirl template, and displaying of errors.

Article originally published on my personal website: How to build a form in Play Framework

Play Framework Java Course

Step 1: Let’s prepare our model

First, we need to prepare the model class that will hold the user-submitted data. This will be a simple Java data class with constraints added to the fields. Play has an easy to use constrains mechanism for validating the form’s data which is based on annotations. We also need to decide on the needed fields. For this example I will keep it simple: username, email, full name, password and retyped password.

public class NewUserDTO {
    @Constraints.Required
    private String username;

    @Constraints.Required
    @Constraints.MinLength(8)
    private String password;

    @Constraints.Required
    private String passwordre;

    @Constraints.Required
    @Constraints.Email
    private String email;

    @Constraints.Required
    private String fullName;

    // Getters and Setters
}
Enter fullscreen mode Exit fullscreen mode

This class will be used to create objects that store user input and the annotation will be used for validating it. The ones included for now are pre-defined (and self-explanatory), however, you can create your own if needed and add even complex logic.

Step 2: Prepare our Twirl template

Next, we need to prepare the Twirl template that will render the HTML page with the form. We will need to import the needed classes: the NewUserDTO as well as form and CSFR helpers provided by Play. The form needs to be added as a parameter and, if you want to provide translation, the I18N message translator as well.

@import helper._
@import play.mvc.Http.Request
@import data.dto.NewUserDTO

@(newUserForm: play.data.Form[NewUserDTO])(implicit messages: play.i18n.Messages)
Enter fullscreen mode Exit fullscreen mode

Next we will write our HTML page (which I will mostly omit) and render the form. The form helper provides the needed tools for this. We will provide the controller and method that will handle the request and it will automatically do reverse routing to determine the endpoint. We will also include a CSFR hidden field for security and all the fields we need.

@import helper._
@import play.mvc.Http.Request
@import data.dto.NewUserDTO

@(newUserForm: play.data.Form[NewUserDTO])(implicit messages: play.i18n.Messages)
<!-- HTML Head -->
<body>
    @helper.form(action = routes.RegisterController.register()) {
        @helper.CSRF.formField
        @helper.inputText(newUserForm("username"))
        @helper.inputText(newUserForm("email"))
        @helper.inputPassword(myForm("password"))
        @helper.inputPassword(myForm("passwordre"))
        @helper.inputText(newUserForm("fullName"))
        <button type="submit" class="btn btn-primary btn-block">Register</button>
    }
</body>
Enter fullscreen mode Exit fullscreen mode

As we can see, we can include HTML code just like in any part of the Twirl template. This is very useful since we can do complex styling and organize the form into different divs so that it will also look good. We can easily add additional information to the input fields if needed. Below, we added more for the Username field and the rest can be done in a similar fashion:

@import helper._
@import play.mvc.Http.Request
@import data.dto.NewUserDTO

@(newUserForm: play.data.Form[NewUserDTO])(implicit messages: play.i18n.Messages)
<!-- HTML Head -->
<body>
    @helper.form(action = routes.RegisterController.register()) {
        @helper.CSRF.formField
        @helper.inputText(newUserForm("username"),  Symbol("id") -> "username", Symbol("class") -> "form-control", Symbol("placeholder") -> "Username")
        @helper.inputText(newUserForm("email"))
        @helper.inputPassword(myForm("password"))
        @helper.inputPassword(myForm("passwordre"))
        @helper.inputText(newUserForm("fullName"))
        <button type="submit" class="btn btn-primary btn-block">Register</button>
    }
</body>
Enter fullscreen mode Exit fullscreen mode

If you prefer, we can ditch the helper for the input fields and write the HTML code ourselves. There is no real harm in doing this, since the helper does the same thing actually. Depending on the actual design, it may be easier this way. Still, I would recommend at least using the @helper.form and @helper.CSFR since those save a lot of time in the long run, especially if the endpoint or method change.

Step 3: Display errors

Since we added constraints to our data class, the form can return errors. We need to be able to properly handle those errors and to be able to display them on the page. This is done using the actual newUserForm object. Again, we can do complex styling if needed, but I will be keeping things simple here.

@import helper._
@import play.mvc.Http.Request
@import data.dto.NewUserDTO

@(newUserForm: play.data.Form[NewUserDTO])(implicit messages: play.i18n.Messages)
<!-- HTML Head -->
<body>
    @helper.form(action = routes.UserRegistrationController.register()) {
        @helper.CSRF.formField
        @helper.inputText(newUserForm("username"),  Symbol("id") -> "username", Symbol("class") -> "form-control", Symbol("placeholder") -> "Username")
        @helper.inputText(newUserForm("email"))
        @helper.inputPassword(myForm("password"))
        @helper.inputPassword(myForm("passwordre"))
        @helper.inputText(newUserForm("fullName"))
        <button type="submit" class="btn btn-primary btn-block">Register</button>

        @if(newUserForm.hasErrors) {
            @for(error <- newUserForm.errors) {
                <span class="error-message">@error.format(messages)</span>
            }
        }
    }
</body>
Enter fullscreen mode Exit fullscreen mode

Step 4: Handling the form input on the server

Once we have our Twirl template prepared we can start work on the controller that will handle the request. Here we will receive the form data, do the needed validations and return the response. The controller will also have a method for rendering the initial view.

public class UserRegistrationController extends Controller {
    @Inject
    private FormFactory formFactory;

    @Inject
    private MessagesApi messagesApi;

    // the service that will handle registration of the user, the actual business logic that is needed
    @Inject
    private UserService userService; 

    /**
     * Render the HTML page
     */
    public Result registerView(Http.Request request) {
        return ok(views.html.register.render(formFactory.form(NewUserDTO.class), messagesApi.preferred(request));
    }

    /**
     * Handle the form submission
     */
    public Result register(Http.Request request) {
        Form<NewUserDTO> form = formFactory.form(NewUserDTO.class).bindFromRequest(request);

        if (form.hasErrors()) {
            return ok(views.html.register.render(form, messagesApi.preferred(request)));
        }

        // Do any additional logic that is needed
        userService.registerNewAdminUser(form.get());
        return redirect(routes.AdminController.actionSuccessful());
    }
}
Enter fullscreen mode Exit fullscreen mode

The first method is not that interesting, but let’s take a look at the second one. We first retrieve the form and the data using the built-in form factory. This can bind the data received in the HTTP Request to our internal data transfer object. At this step any validation that was added using the constraints are done and the form will have errors on the next line if violations are encountered.

If we get any errors we render the page again, but this time we provide the existing form as a parameter. This way we pass the errors to the Twirl template so it can display the errors. Using a more complex Twirl template, we can even pre-fill any data, since we have access to the data previously submitted.

After that, we call our service that does the business logic and DB insertion and if everything was successful, we redirect to the results page.

Top comments (0)