Play 2.0 demo - live coding script

08 December 2011

Peter Hilton

by Peter Hilton

--- layout: article drupal-format: Unfiltered HTML, with syntax highlighter title: "Play 2.0 demo - live coding script" tags: playframework author: Peter Hilton summary: "The best way to introduce Play to an audience is to show them how it works in the kind of live-coding demo that lets people see for themselves how easy it is. Here is a Play 2.0 (Java) version of last year’s [Play 1.x live coding script](https://blog.lunatech.com/posts/2010-06-14-how-demo-play-framework-live-coding-script), in which you will code a simple but functional to-do list application, using Play 2.0, Java and Ebean.

Getting started

Download Play 2.0. This version of the script is based on the 2.0-beta release.

$ wget http://download.playframework.org/releases/play-2.0-beta.zip
$ unzip -q play-2.0-beta.zip
$ export PATH=$PATH:`pwd`/play-2.0-beta

Note: at this point you would normally add the play command to the path more permanently.

Create the application:

$ play new tasks

What is the application name?
> tasks

Which template do you want to use for this new application?

  1 - Create a simple Scala application
  2 - Create a simple Java application
  3 - Create an empty project

> 2

See which files were generated:

$ find tasks -type f
tasks/.gitignore
tasks/app/controllers/Application.java
tasks/app/views/index.scala.html
tasks/app/views/main.scala.html
tasks/conf/application.conf
tasks/conf/routes
tasks/project/build.properties
tasks/project/Build.scala
tasks/project/plugins.sbt
tasks/public/images/favicon.png
tasks/public/javascripts/jquery-1.6.4.min.js
tasks/public/stylesheets/main.css

Run the application:

$ cd tasks
$ play run

Open the welcome page: http://localhost:9000/

Compilation errors and dynamic data

Edit app/views/index.scala.html and replace the contents with the following (with a bogus variable name):

@(items: String)

@main("Tasks") {
    <h1>@item</h1>
}

Reload the page, which shows a template compilation error: not found: value item.

The compilation error shows that template parameters must be declared.

In the console, type Control-D to stop the application, then start the Play console and compile the application:

$ play
[tasks] $ compile

You can also discover template compilation errors without running the application.

Start the application again:

[tasks] $ run

In app/views/index.scala.html fix the error: change @item to @items.

Edit app/controllers/Application.java and change the index() method body to the following line (with a missing semi-colon):

return ok(index.render("Things"))

Reload the page, which shows a Java compilation error: ';' expected.

In app/controllers/Application.java fix the error: add the missing semi-colon.

Reload the page, which shows the heading ‘Things’.

In public/stylesheets/main.css, add some CSS to make things less ugly:

body { font-family:"Helvetica Neue"; padding:2em; background: #B2EB5A url("/assets/images/play20header.png") no-repeat top center ; }
body:before { content:'Play 2.0 task list demo'; color:rgba(255,255,255,0.7); font-size:150%; text-transform:uppercase; letter-spacing:0.4em; }
ul { padding:0; list-style:none; }
li, form { width:30em; background:white; padding:1em; border:1px solid #ccc; border-radius:0.5em; margin:1em 0; position:relative; }
li a { text-decoration:none; color:transparent; position:absolute; top:1em; right:1em; }
li a:after { content:'❎'; color:#aaa; font-size:120%; font-weight:bold; }
form * { font-size:120%; }
input { width:16em; }
button { cursor:pointer; color: white; background-color: #3D6F04; background-image: -webkit-linear-gradient(top, #5AA706, #3D6F04); text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); border: 1px solid #CCC; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border-radius:4px; }
p.error { margin:0; color:#c00; }

In app/controllers/Application.java replace "Things" with a String items method parameter.

public static Result index(final String items) {
   return ok(index.render(items));
}

In conf/routes, replace the first route with (with a bogus lower-case string type):

GET /   controllers.Application.index(i: string)

Open http://localhost:9000/?i=Tasks, which shows a routes compilation error: not found: type string.

The routes file is compiled, and HTTP parameters must be declared. HTTP parameter names do not have to match the action method names.

In conf/routes, correct the error: change the parameter type to String.

Reload the web page.

Undo the last changes in app/controllers/Application.java – remove the index method parameter:

public static Result index() {
   return ok(index.render("Tasks"));
}

Undo the change in conf/routes – remove the method parameter:

GET /   controllers.Application.index()

Set-up IntelliJ IDEA

This section is optional, for IntelliJ IDEA users.

In the console, type Control-D to stop the application, and create the IntelliJ project:

[tasks] $ gen-idea

This currently uses a separate sbt plug-in, but something like this will be built in to Play 2.0.

Open the project, containing the generated .idea directory, in IntelliJ IDEA.

Ebean entity

Create a new models.Task class in app/models/Task.java, either by hand or using IntelliJ IDEA:

package models;

import play.db.ebean.Model;
import javax.persistence.Id;
import javax.persistence.Entity;

/**
 * A human-task, e.g. 'Get the presenter a beer'.
 */
@Entity
public class Task extends Model {

    @Id
    public Long id;

    public String title;

    public static Finder<Long, Task> find = new Finder<Long, Task>(Long.class, Task.class);
}

The ‘finder’ is more explicit than the methods added by byte code enhancement in Play 1.

In app/controllers/Application.java, import models.Task and replace "Things" with a call to the finder:

return ok(index.render(Task.find.orderBy("title").findList()));

Open http://localhost:9000/, which shows a Java compilation error: render(java.lang.String) in views.html.index cannot be applied to (java.util.List<models.Task>).

Template parameters are type safe.

In app/views/index.scala.html change the type in the template parameter declaration:

@(tasks: List[models.Task])

Also, change the HTML block to:

<h1>@tasks.size task@(if(tasks.size != 1) "s")</h1>
<ul>
    @for(task <- tasks) {
        <li>@task.title</li>
    }
</ul>

Reload the page, which shows a data source error: [RuntimeException: DataSource user is null?].

In conf/application.conf, uncomment the default values for the in-memory database and Ebean configuration:

db.default.driver=org.h2.Driver
db.default.url=jdbc:h2:mem:play
ebean.default=models.*

Reload the web page, which shows the ‘Database ‘default’ needs evolution!’ page.

The db/evolutions/default/1.sql database evolution script is generated for you.

Click the ‘Apply this script now!’ button.

In templates, dynamic content and control structures start at @ and continue until the end of the statement or expression.

Open http://localhost:9000/: there are no tasks.

HTML form

In app/views/index.scala.html, add a form after the list.

<form method="post" action="@routes.Application.add()">
   <input name="title" placeholder="Enter a task description…">
   <button type="submit">Add Task</button>
</form>

In app/controllers/Application.java, import play.data.Form and add the add method:

public static Result add() {
   final Form<Task> taskForm = form(Task.class).bindFromRequest();
   final Task task = taskForm.get();
   task.save();
   return redirect(routes.Application.index());
}

In conf/routes, add the new HTTP mapping:

POST /  controllers.Application.add()

Reload http://localhost:9000/, enter a value in the text input and click the Add button.

In app/views/index.scala.html, inside the <li>, add a link:

<a href="@routes.Application.delete(task.id)">delete</a>

In conf/routes, add the new HTTP mapping:

GET /delete/:id controllers.Application.delete(id: Long)

In app/controllers/Application.java, add the delete method:

public static Result delete(final Long id) {
   Task.find.ref(id).delete();
   return redirect(routes.Application.index());
}

Reload http://localhost:9000/ and delete some tasks, showing the link URLs.

Form validation

In app/models/Task.java, import play.data.validation.Constraints and annotate the title field with @Constraints.Required.

@Constraints.Required
public String title;

In app/controllers/Application.java, import java.util.List and extract the list of tasks to a new method:

private static List<Task> tasks() {
   return Task.find.orderBy("title").findList();
}

Add validation to the add method:

public static Result add() {
   final Form<Task> taskForm = form(Task.class).bindFromRequest();
   if(taskForm.hasErrors()) {
       return badRequest(index.render(tasks(), taskForm));
   } else {
      taskForm.get().save();
      return index();
   }
}

The play.data.Form is an explicit wrapper for HTTP form data and validation errors, used for binding. This is more explicit and more structured than the Play 1.2 validation data.

Change the index method to add an empty form to the template call:

return ok(index.render(tasks(), form(Task.class)));

In app/views/index.scala.html, add the new form parameter on the first line:

@(tasks: List[models.Task], form:play.data.Form[models.Task])

After the form’s submit button, add a line to display error messages:

<p class="error">@form("title").errors.map(_.message).map(Messages(_))</p>

The play.api.i18n.Messages Scala object is being used for message key look-up.