Documentation

You are viewing the documentation for Play 1. The documentation for Play 2 is here.

Setting up a basic administration area using CRUD

Currently, we have no way to create new blog posts and moderate comments using the blog UI. Play provides an out of the box CRUD module that will help quickly generate a basic administration area.

Enabling the CRUD module

A play application can be assembled from several application modules. This lets you reuse components across several applications or split a large application into several smaller ones.

The CRUD module is a generic application that introspects the model classes to create simple lists and forms.

To enable the CRUD module, add this line to the /yabe/conf/application.conf file:

# Import he crud module
module.crud=${play.path}/modules/crud

Then this module comes with a generic set of routes that we can reuse for now. To imports these routes just add this line to the /yabe/conf/routes file:

# Import CRUD routes
*      /admin              module:crud

This will import all the CRUD routes using the /admin prefix for the URL paths.

You have to restart the application to take the new module into account.

Declaring the CRUD controllers

For each model object that we want to integrate to the administration area, we have to declare a controller that extends the controllers.CRUD super controller. This is very easy.

Create one controller for each model object. For example, for the Post object create the Posts controller in the /yabe/app/controllers/Posts.java file.

package controllers;
 
import play.*;
import play.mvc.*;
 
public class Posts extends CRUD {    
}

The convention is to pluralize the model object name to create the corresponding controller. This way, play will automatically find the associated model object for each controller. If you need to use a different name, you can still use the @CRUD.For annotation. Check the manual page.

Create the same for all model objects:

package controllers;
 
import play.*;
import play.mvc.*;
 
public class Users extends CRUD {    
}
package controllers;
 
import play.*;
import play.mvc.*;
 
public class Comments extends CRUD {    
}
package controllers;
 
import play.*;
import play.mvc.*;
 
public class Tags extends CRUD {    
}

Now just open the http://localhost:9000/admin/ URL, and you should get to the administration area.

If you browse it a little, you will notice that the object names in the lists are a little rough. This is because the default is to use a simple toString() to get a readable representation of the model objects.

We can easily fix that, by providing correct implementations of the toString() method for all models. For example, for the User class:

...
public String toString() {
    return email;
}
...

Adding validation

The main problem of the generated administration area is that forms don’t contain any validation rules. But actually the CRUD module is able to extract the validation rules from the validation annotations if the model class is correctly annotated.

Let’s add some annotations to the User class:

package models;
 
import java.util.*;
import javax.persistence.*;
 
import play.db.jpa.*;
import play.data.validation.*;
 
@Entity
public class User extends Model {
 
    @Email
    @Required
    public String email;
    
    @Required
    public String password;
    
    public String fullname;
    public boolean isAdmin;
    
...

Now if you go to the edition or creation form for the User model object, you will see that validation rules are auto-magically added to the form:

Let’s do the same for the Post class:

package models;
 
import java.util.*;
import javax.persistence.*;
 
import play.db.jpa.*;
import play.data.validation.*;
 
@Entity
public class Post extends Model {
 
    @Required
    public String title;
    
    @Required
    public Date postedAt;
    
    @Lob
    @Required
    @MaxSize(10000)
    public String content;
    
    @Required
    @ManyToOne
    public User author;
    
    @OneToMany(mappedBy="post", cascade=CascadeType.ALL)
    public List<Comment> comments = new ArrayList();
    
    @ManyToMany(cascade=CascadeType.ALL)
    public Set<Tag> tags = new HashSet();
        
...

And check the result:

Here you see an interesting side effect: the @MaxSize validation rule has changed the way play displays the Post form. It now uses a large text area for the content field.

Finally we can add validation rules to the Comment and Tag classes as well:

package models;
 
import java.util.*;
import javax.persistence.*;
 
import play.db.jpa.*;
import play.data.validation.*;
 
@Entity
public class Tag extends Model implements Comparable<Tag> {
 
    @Required
    public String name;
 
...
package models;
 
import java.util.*;
import javax.persistence.*;
 
import play.db.jpa.*;
import play.data.validation.*;
 
@Entity
public class Comment extends Model {
 
    @Required
    public String author;
    
    @Required
    public Date postedAt;
     
    @Lob
    @Required
    @MaxSize(10000)
    public String content;
    
    @ManyToOne
    @Required
    public Post post;
 
...

Better form labels

As you can see the form labels are a little rough. Play uses the Java field name as form label. To customize it, we just have to provide better labels in the /yabe/conf/messages file.

In fact, you can have a separate messages file for each language supported by your application. For example, you could put French messages in the /yabe/conf/messages.fr file.

Add these label to the messages file:

title=Title
content=Content
postedAt=Posted at
author=Author
post=Related post
tags=Tags set
name=Common name
email=Email
password=Password
fullname=Full name
isAdmin=User is admin

Then refresh any form, and you will see the new form labels:

Customizing the ‘Comments’ data list

The CRUD module is made to be fully customizable. For example if you look at the comments list page, the way that data is listed is not great. We would like to add more columns, especially the ‘related post’ column to help us filter the list easily.

In fact as your applications keep the master, you can really override any action or template provided by the CRUD module. For example if we want to customize the ‘comments list’ view, we just have to provide another /yabe/app/views/Comments/list.html template.

The CRUD module provides more play commands when it’s enabled. The crud:ov command helps you override any template. From a command line, type:

$ play crud:ov --template Comments/list

Now you have a new template at /yabe/app/views/Comments/list.html:

#{extends 'CRUD/layout.html' /}
 
<div id="crudList" class="${type.name}">
	
	<h2 id="crudListTitle">&{'crud.list.title', type.name}</h2>
 
	<div id="crudListSearch">
		#{crud.search /}
	</div>
 
	<div id="crudListTable">
		#{crud.table /}
	</div>
 	
	<div id="crudListPagination">
		#{crud.pagination /}
	</div>
	
	<p id="crudListAdd">
		<a href="@{blank()}">&{'crud.add', type.modelName}</a>
	</p>
 
</div>

The #{crud.table /} actually generates the table. We can customize it using the fields parameter to add more columns. Try this:

#{crud.table fields:['content', 'post', 'author'] /}

And now we have 3 columns in the table:

The problem is that the content field could be very long for some comments. We will specialize the way that the #{crud.table /} handles it to be able to truncate it if needed.

We can specify a custom way to display each field using the #{crud.custom /} tag as is:

#{crud.table fields:['content', 'post', 'author']}
 #{crud.custom 'content'}
  <a href="@{Comments.show(object.id)}">
   ${object.content.length() > 50 ? object.content[0..50] + '…' : object.content}
  </a>
 #{/crud.custom}
#{/crud.table}

Yes there is some groovy syntaxic sugar at work here.

Customizing the ‘Post’ form

We can customize the generated forms as well. For example, the way we enter tags in the Post form is not really easy. We could build something better. Let’s override the Posts/show template:

$ play crud:ov --template Posts/show

Now you have a new template at /yabe/app/views/Posts/show.html:

#{extends 'CRUD/layout.html' /}
 
<div id="crudShow" class="${type.name}">
	
<h2 id="crudShowTitle">&{'crud.show.title', type.modelName}</h2>
 
<div class="objectForm">
#{form action:@save(object.id), enctype:'multipart/form-data'}
    #{crud.form /}
    <p class="crudButtons">
        <input type="submit" name="_save" value="&{'crud.save', type.modelName}" />
        <input type="submit" name="_saveAndContinue" value="&{'crud.saveAndContinue', type.modelName}" />
    </p>
#{/form}
</div>
 
#{form @delete(object.id)}
    <p class="crudDelete">
        <input type="submit" value="&{'crud.delete', type.modelName}" />
    </p>
#{/form}
 
</div>

You can hack the #{crud.form /} tag the make the tags field custom:

#{crud.form}
    #{crud.custom 'tags'}
        <label for="tags">
            &{'tags'}
        </label>
        <style type="text/css">
	        .tags-list .tag {
	            cursor: pointer;
	            padding: 1px 4px;
	        }
	        .tags-list .selected {
	            background: #222;
	            color: #fff;
	        }
	    </style>
	    <script type="text/javascript">
	        var toggle = function(tagEl) {
	            var input = document.getElementById('h'+tagEl.id);
	            if(tagEl.className.indexOf('selected') > -1) {
	                tagEl.className = 'tag';
	                input.value = '';
	            } else {
	                tagEl.className = 'tag selected';
	                input.value = tagEl.id;
	            }
	        }
	    </script>
	    <div class="tags-list">
	        #{list items:models.Tag.findAll(), as:'tag'}
	           <span id="${tag.id}" onclick="toggle(this)" 
	                class="tag ${object.tags.contains(tag) ? 'selected' : ''}">
	               ${tag}
	           </span> 
	           <input id="h${tag.id}" type="hidden" name="${fieldName}" 
	                    value="${object.tags.contains(tag) ? tag.id : ''}" />
	        #{/list}
	    </div>
    #{/crud.custom}
#{/crud.form}

This is a little hacky and we could do better here, but we have now a simpler tags selector using a little bit of javascript:

This is a good start for the administration area!

Go to the next part.