Skip to content

Playframework: handling of validated objects

11 November 2011

In my previous blog (see validations for the play framework) I showed you how easy it can be to handle validations with the play framework. This time I will try to explain how this can be combined with the controllers in the playframework to handle these errors.

The controllers in the playframework are the central entry point from the web to your domain. Each controller should extend playframework’s Controller and have a static void method for each action that can be done on the controllers. These actions are static since they are supposed to be a binding point between the stateless aspect of http towards the statefull objects of your models. The actions are bound to urls and type of request (POST or GET) through the routes configuration file. Using the routes configuration file you can create nice looking rest-like url’s that are bound to the controllers. Also this routes file will be used to generate the urls for you if you add them to your view template files. For instance, given that your routes file contains the following route:

GET /person/{id}      Persons.edit

And your Persons controller contains a method edit that takes as parameter a field with the name id:

public static void edit(Long id)

If you use the correct syntax for displaying the url in your view like:

#{a @Persons.edit(person.id)}Edit person#{/a}

Then your url will be looked up through the routes file and changed to the syntax that was defined in there. Resulting in this case into:

<a href="/person/1">Edit person<a>

Once the end user clicks that url, the action in the persons controller will be invoked, so the show method will retrieve as parameter the id for the given person, after which the system will fetch the person and put this person in the view for displaying.

However you can streamline this process even more by changing the controller to accept a person object instead of a person id. If you add a parameter with the name person.id to the request and your action expects a person, then the playframework will automatically fetch this person from the database for you and hand it over to the action controller. This can be even more convenient when using a save action. If you display the person in a form, and have a hidden field called person.id, then the playframework will :

  • fetch the person from the database for your
  • and update the fields with the given http parameters for you
  • as long as those fields are allowed to be changed
    • after which you will only have to call the person.save() method to store the updated person.

      There are troubles lurking ahead though with using this approach, which brings me back to the validations aspect of this blog. What to do if there are errors on your object?

      So after some wacky user posted the person object back again to the server which resulted in a number of errors, you have to tell the end user what and where he or she made a booboo. To show the errors again you can do the following:

      params.flash();
      Validation.keep();
      edit(person);

      What this does is:

      • params.flahs() method will store all the entered content in the browser’s cookie. This is the only way that the playframework will store non-persisted content. So there is no server side session where the data is temporarily stored.
      • Validation.keep() will store all the validation messages in the browser’s cookie as well.
      • edit(person) is a controller method that will show the edit screen again to the end user.

      Once you have the errors, you can easy display them using the predefined error tags of play. These are:

      • #{errors}, this is an iterator that will iterate over all of the errors
      • #{ifErrors}, return either true or false if there are errors found
      • #{ifError ‘key’}, return either true or false if there is an error for the given key.

      As stated above, play will store all the content that must be resend in a cookie. A cookie can only store up to 4k of data though, so if the user was entering a lot of data, this might be not completely re-send. The way to prevent this is to skip the redirect.

      Skipping the redirect can be done by changing the edit and save method to invoke a private static method that will do the actual displaying:

      public static void add() {
        editPerson(new Person());
      }
      public static void edit(Long id) {
        Person person = Person.findById(id);
        notFoundIfNull(person);
        editPerson(person);
      }
      private static void editPerson(person) {
        render("Persons/edit.html", person);
      }
      public static void save(@Valid Person person) {
        if (Validation.hasErrors()) {
           editPerson(person);
        }
        person.save();
        flash.put("message", "person.saved");
        list();
      }
      

      What happens here is that using the first add or edit method, the playframework will actually render the page of Persons/edit.html but since this is a private method, the re-direct will not happen. This also happens in the save method if there is an error found. Since there is no re-direct, the errors do not need to be stored in the cookie and therefor there is no call to the params.flash() or the validations.keep() method anymore. If you did add those methods, the next call after the redirect, would re-display the same errors again, even if they were not valid anymore, because they will be stored in the cookie.

      If the person data was valid, the last call is a call to list(). This is another action in this controller and thus this will result in a redirect. Therefor the success message is stored in the flash scope and this is then displayed in the page.

      All code:

      models/Person.java:

      package models;
      
      import play.data.validation.Email;
      import play.data.validation.Required;
      import play.db.jpa.Model;
      
      import javax.persistence.Entity;
      
      @Entity
      public class Person extends Model {
        @Required
        @Email
        public String email;
      
        @Required
        public String name;
      
        @Required
        public String password;
      }
      

      controllers/Persons.java

      package controllers;
      
      import models.Person;
      import play.data.validation.Valid;
      import play.data.validation.Validation;
      import play.mvc.Controller;
      
      import java.util.List;
      
      public class Persons extends Controller {
        public static void list() {
          List persons = Person.findAll();
          render(persons);
        }
      
        public static void add() {
          Person person = new Person();
          edit(person);
        }
      
        private static void edit(Person person) {
          render("Persons/edit.html", person);
        }
      
        public static void edit(Long id) {
          Person person = Person.findById(id);
          notFoundIfNull(person);
          edit(person);
        }
      
        public static void save(@Valid Person person) {
          if (Validation.hasErrors()) {
            edit(person);
          }
          person.save();
          flash.put("message", "saved");
          list();
        }
      }
      

      views/Persons/list.html

      #{if flash['message']}${flash['message']}#{/if}
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th> </th>
          </tr>
        </thead>
        <tbody>
          #{list items:persons, as:'person'}
            <tr>
              <td>${person.name}</td>
              <td>${person.email}</td>
              <td>#{a @Persons.edit(person.id)}Edit#{/a}</td>
            </tr>
          #{/list}
        </tbody>
        <tfoot>
          <tr>
            <td colspan="3">#{a @Persons.add()}Add#{/a}</td>
          </tr>
        </tfoot>
      </table>
      

      views/Persons/edit.html

      #{form @save()}
      <input type="hidden" name="person.id" value="${person.id}"/>
      <p>
      #{field 'person.name'}
      Name: <input type="text" name="${field.name}" id="${field.id}" value="${field.value}"/>#{if field.error}${field.error}#{/if}
      #{/field}
      </p><p>
      #{field 'person.email'}
      Email: <input type="text" name="${field.name}" id="${field.id}" value="${field.value}"/>#{if field.error}${field.error}#{/if}
      #{/field}
      </p><p>
      #{field 'person.password'}
      Password: <input type="password" name="${field.name}" id="${field.id}" value="${field.value}"/>#{if field.error}${field.error}#{/if}
      #{/field}
      </p><p>
      <input type="submit" value="save"/>
      </p>
      #{/form}
      

      conf/routes:

      GET   /              Application.index
      
      GET  /persons        Persons.list
      GET  /person/{id}    Persons.edit
      GET  /person/add     Person.add
      POST /person/save    Person.save
      

      And finally in conf/application.conf, uncomment the database line:

      db = mem
      Advertisements

From → Uncategorized

2 Comments
  1. Great article, I wrote about the very same subject here (in spanish , sorry) https://github.com/opensas/RedirectAfterPost/blob/master/README.md

    There’s no need to declare another method in this case, because it’s really simple…

    it’s the same approach used in zencontact sample app
    https://github.com/playframework/play/blob/master/samples-and-tests/zencontact/app/controllers/Application.java

    but it’s a nice tip to use a private method to avoid play’s automatic redirect…

    • Ah, indeed, that makes it even more simpler. Especially with the render(“@…”) syntax. I had not used that one before. Thx for the comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: