In this section we will learn the basics of how to implement the MVC components of a typical Play application.
Introducing our sample application
Our sample here is a prototype of a phone book application. We are maintaining our contacts with it, where the contacts have a name and a phone number. We are able to add, remove, and edit contacts. Additionally, we will implement a search for contacts.
All source code needed to run the example is found in this book. However, it is also available online at http://packtpub.com and http://bit.ly/PhoneBookSample for each step that we perform.
Defining the domain model
We already know that a Play application follows the MVC architectural pattern. It is up to us which component we implement first. We decide to start implementing the model. The model reflects the business requirements, in that it contains the business logic and our entities.
First of all, we have to ensure our applications are started in development mode with the ~run
command, as described previously.
We create the package models
in the app/
folder of our Java application and add a new entity class Entry.java
to it. As mentioned before, the package name models
can be changed if needed.
The Entry
class represents phone book entries. Objects of this class contain a specific name
and phone
entity. Also we need a unique identifier, id
, to be able to distinguish phone book entries. Especially, it is allowed to create multiple entries with the same name and phone number. Please note that phone numbers are of type String
, because we also want to allow characters other than digits.
For the sake of simplicity, the visibility of the properties is public
to omit the implementation of getters and setters.
Next, we need to create our data access object (DAO). We choose to name it Entries.java
. To keep it simple, we place it in the same package:
The DAO consists of static methods only and we will not create instances of that class. This pattern is also found in controllers and reflects a fundamental idea of the Play framework; to encourage us to follow a stateless programming model.
Phone book entries can be deleted by an entry id
entity. We search entries by their ID and by their name. The findByName
implementation returns a list of entries (of type Seq
) that contain a specific string in their name, ignoring the case. If the search string is empty, all entries are returned. The order of list items is undetermined here.
Tip
We use the Scala collection type Seq
here because it is shared by the views of our Java and Scala examples. Java developers typically use List
instead.
Finally, the entries can be saved. The save
method equips new entries with an ID. Existing entries are updated in the underlying storage. Our storage is a map associating IDs with the corresponding entries.
Our Scala implementation of the model resides in app/models/
in the file Entries.scala
:
This contains both the entry
entity and the entries
DAO. Entry
is a case class, which allows us to implement data beans in a very concise way, literally as a one-liner.
The functionality of our Java and Scala DAO implementations is the same here. The entity is containing the data and will be part of our interface between view and controller. Only the controller is permitted to use the DAO to access the data layer.
Designing the HTTP interface with the routes file
HTTP requests and responses are first-class citizens of Play. The HTTP interface forms the core of a Play application. It is defined in the conf/routes
file and contains a mapping of HTTP requests to the corresponding method calls. An entry of this file is called route and looks like this:
In particular, the left-hand side of the route consists of two parts: the HTTP method and the request path, which is relative to the base URL of the application.
Here, the Application.index
method is called when we open a browser and visit the base URL, that is, the request path /
, of our application. Such a request is sent with the GET
method.
Valid methods are GET
, POST
, PUT
, DELETE
, OPTIONS
, HEAD
, and WS
.
Defining the application routes
In our example the GET
and POST
methods are sufficient. Described in a nutshell, we use the GET
method to load a specific web page without modifying a resource and the POST
method to change a resource and navigate to a result page.
Please insert the following code lines behind the first route definition of the conf/routes
file of both the projects (Java and Scala):
Routes are processed in the order of their occurrence until a match is found. If no route matches the actual URL, Play returns a HTTP 404 Not Found page.
The first line reflects that we want to retrieve a list of phone book entries filtered by a specific search string. We are planning to add controls to the phone book list that will allow us to add new phone book entries, edit existing ones, and remove entries.
If we add or edit an entry, we navigate to a phone book entry details page. After the user has finished editing the entry on the client side, it can be saved or canceled. In both cases, we want to return back to the entry list page.
Dynamic request paths and variables
There is one thing we have not explained yet regarding our new routes. Play allows a special notation of the request path of the route. There are three kinds of placeholders for path segments we can use:
$name<regex>
: The dollar sign $
marks a placeholder for the match of a regular expression regex
. The current value can be accessed by name
. The path segments are processed case sensitive.
:name
: The colon :
matches exactly one request path segment. It is internally mapped to the regular expression $name<[^/]+>
.
*name
: The asterisk *
matches the remaining path segments and can be used to express whole paths. It is internally mapped to the regular expression $name<.+>
.
The first two placeholders can be combined in a request path, such as:
Given that, /shop/socks/1234/details
is a valid request path, where product
is socks
and number
is 1234
. We pass these placeholders to a controller method, say:
Also, please note that Play takes care of converting the text content of the extracted path segments to the appropriate parameter types declared in the controller method. The trailing parentheses are optional if the controller method has no arguments.
If we declare a controller method parameter that has no corresponding request path placeholder, Play tries to find that value in the HTTP request header. In other words, the parameter can be specified as a URL query string parameter shop?product=socks
.
Additionally, we can define a special handling for controller method parameters:
We need to get back to our phone book example if we want to test our newly created routes. The routes file is compiled and executed when we visit localhost:9000
.
Play provides us with a hint that the Entries controller is missing. So, we will implement it next.
Handling HTTP requests by controller actions
A controller
is a subclass of the Controller
class. It consists of a set of (generally static) methods that process an HTTP request by computing an HTTP response and sending it back to the web browser. The controller methods are called actions.
The Java API has no special representation of actions. In Scala, an action can be basically seen as a function of the following type:
The Java classes Controller
, Request
, and Result
that are used in the request-response cycle, are located in the package play.mvc
. The Scala API is located at play.api.mvc
.
Providing dummy implementations
Apparently, the routes file has errors because of the missing controller Entries
(not to be confused with models.Entries
).
To fix that, we create app/controllers/Entries.java
with the following content:
The corresponding Scala code looks almost the same:
We actually don't have provided real
implementations of the controller methods. Instead, we return Play's empty result (Java) and action (Scala) implementation, TODO. Now the controller is syntactically correct and can be compiled.
To trigger the routes generator, we are reloading the web pages of our Java and Scala applications. So now, the error should disappear.
Of course we can already test what happens when we visit a specific URL listed in our routes file, say localhost:9000/entries?filter=test
. According to the route, the controller method controllers.Entries.list("test")
is called, which returns the TODO dummy page:
URL redirection with reverse routes
The router can also be used to programmatically generate an URL. Each controller has a so-called reverse controller in a sub-package routes
of the corresponding controller class. For example, the controller controllers.Entries
has a reverse controller controllers.routes.Entries
, which contains (almost) the same methods. The difference is that the reverse controller methods return a Call instead of a Result, where a call corresponds to an URL.
As an example, we will redirect our base URL localhost:9000
to the list of phone book entries located at localhost:9000/entries
.
Technically, the Play server performs the redirect by sending an HTTP 302 response to the client, where the response header contains the new URL. In contrast to directly calling the controller method Entries.list("")
, the browser location is changed.
In Java, we replace the body of the Application
controller's index
method with:
The Scala solution allows us to omit the argument of the list
method and use the default method defined in the routes
definition:
The routes
file tells us that a request of localhost:9000/
invokes Application.index
, which in turn invokes Entries.list
with a redirect to localhost:9000/entries
.
We will also use reverse controllers to generate links in our view templates. We have all our URI patterns centralized in the routes file. Using reverse controllers instead of hard-coded links provides us with the certainty that all links are correct—which is one of the several advantages of using Play.
Implementing controller actions
Now we have met all the requirements to implement our first two controller methods, list
and remove
.
We edit app/controllers/Entries.java
and insert the following method bodies. Please note that we will adjust the index
view template accordingly:
The list
method first calls the findByName
method of our DAO models.Entries
, which returns all phone book entries where the given filter
is part of the entry name.
Then the views.html.index
view template renders the list of phone book entries. The result is an HTML page, which is returned by the action with the HTTP 200 OK
status and the correct content type text/html
. The content type is inferred based on the value passed to the status function (but could be changed by calling .as(…)
).
The implementation of remove
looks very similar to that of the list
method:
First, the DAO is called to delete the entry by it's ID. Then a redirect to the list of phone book entries is returned.
Because Entries.findByName
returns a sequence of phone book entries of type Seq<Entry>
, we additionally need to import the following classes:
We edit app/controllers/Entries.scala
and insert the implementation of list
:
The implementation of remove
is also analogous to Java:
Our DAO models.Entries
has the same name as our current controller. To resolve this name clash, we had to provide the fully-qualified name of the DAO in the Java implementation of the list
action. In Scala we have the ability to substitute the imported class name with a shortcut by specifying the following import:
When we reload the page, the compiler complains that the index
view template currently expects a string instead of a list of entries. We will fix that when implementing our view templates.
Composing the UI from view templates
The views of a Play application are based on a safe template engine. View templates (views) are HTML files with dynamic parts using Scala as an expression language. Compared to other template languages there are no special tags or surrounding blocks for an embedded template syntax. Template expressions begin with the @
character and are simply mixed in to your HTML code.
With the help of Play's template parser, a Scala version of the template is generated. For example, we use this in our controllers. When a view is rendered, the compiled template expressions are evaluated. The occurrence of a template expression is replaced by its value. The result is a static HTML page.
Physically, templates are located in the folder app/views
or one of its subfolders. After views are compiled they are located in the package views.html
or one of its sub packages. For example, the fully-qualified name of app/views/index.scala.html
is views.html.index
.
The syntax of view templates are basically a combination of HTML for the static parts and Scala for the dynamic parts.
The first line of a view is mandatory and declares the template parameters. It starts with an @
character followed by one or more parameter blocks. A parameter block is denoted in parentheses ()
and contains a comma-separated list of parameter declarations.
Like in Scala, a parameter has a name followed by a colon :
and a type. Generic parameter types are denoted with []
instead of <>
. For example:
The declaration of view parameters is optionally followed by import declarations. Please remember that the underlying syntax is that of Scala. Use _
instead of *
as a wildcard. For example:
Template expressions are Scala expressions, with one exception—a simplified for
expression. Strictly speaking, the HTML code is also a template expression, as follows:
But it is not considered as such in our short overview, as follows:
@expression
: It is a simple expression such as @list.getSize()
@(complex expression)
: It indicates multiple tokens such as @(e1 + e2)
@{block expression}
: It represents block of expressions such as @{e1; e2}
@*comment*@
: It indicates a comment, but unlike <!--comment-->
it is not rendered
@@
: It denotes a single @
character has to be escaped
Additionally, there are the following control structures:
@if(condition) {…} else {…}
: This is the if-expression, the else
part is optional
@for(to <- from; …) {…}
: This is the for
loop, which returns the evaluated block
Sometimes it may also be helpful to declare the reusable blocks without introducing a new template:
For a complete list of template expressions, please refer to the Play framework documentation.
A view can be seen as a function. It computes an HTML fragment based on specific parameters. In that manner, views are reusable building blocks. They are referenced by their fully-qualified name, for example, @viewName(params){param}{…}
.
Parameters are of an arbitrary Java or Scala type. Especially, blocks of HTML can be passed to views. The type of an HTML block is play.api.templates.Html
for both Java and Scala.
When a view is called, it is evaluated. This is done by evaluating all of its contained template expressions and subsequently concatenating the results. The result is of type play.api.templates.Html
.
A new Play application has two views, main
and index
. The index
view calls the main
view, passing the required parameters. In general, templates are called to have the same content on all the pages of a website:
The main
view takes two parameters, which are used as simple template expressions:
Visiting the base URL calls the Application.index
action, which renders the index
view.
The fact that functions such as the main
view can have multiple parameter blocks is new to programmers. This allows us to denote a multi-line block using {}
instead of ()
if there is only one parameter.
The main
template is a reusable building block, which is used within the index
template. The index
view renders some HTML code and passes it as parameter to the main
view. In general, the most specific parts of a page are rendered first and passed as an argument to more general and reusable building blocks (such as a menu bar) of a web page.
Implementing the phone book views
Let's get started with implementing the views of our phone book sample.
We use the CSS framework Twitter Bootstrap to style our application. These styles are annotated in class="…"
attributes of the HTML elements. The styles are not explained in detail here because they do not affect the functionality of the phone book application.
To install Twitter Bootstrap, please download it at http://twitter.github.com/bootstrap/ and extract its contents to the public/
folder of our Java and Scala application. The result should look similar to this:
Currently, the compiler complains about the wrong parameter type of the index
view located at app/views/index.scala.html
. We fix that by introducing the entries
parameter of type Seq[Entry]
. Also we replace the content passed to the main
view:
The main
view does not take the title parameter any more. We replaced the welcome message of the initial view with the following:
<form>…</form>
: This is an input field to search phone book entries
<a>…</a>
: This is a link to add new phone book entries, displayed as a button
@list(entries)
: This is a list of current phone book entries
As we already stated, we use reverse controllers instead of hard-coded URLs.
The HTTP method of the form is GET
and it is used in order to append the form data to the request query string. This makes searches bookmarkable.
Instead of @views.html.list(entries)
, we can write @list(entries)
. The packages views.html
and models
are in scope by default.
The list of entries is rendered by a separate view app/views/list.scala.html
. We create it with the following content for our Java application:
Our Entry
implementations slightly differ in Java and Scala. In Scala, we avoid them to cope with null
value handling. Instead, we use the generic Option[T]
type, which has two implementations, Some[T]
and None[T]
.
Here we get Some(id)
if entry.id
is defined, otherwise, we get None
. The Option
type plays well together with the for
expression. This is our Scala implementation:
According to the call of the list
view in the index
view, there is one parameter entries
of type Seq[Entry]
. The phonebook entries are aligned in a table by iterating the given sequence and producing a table row for each Entry
.
In Scala we do not expect None
values here. Anyway, if entry.id
is None
, no table row is generated for that entry.
A table row consists of the phone book entry name, the phone number, and two buttons for editing and removing phone book entries. The phone number is displayed as a click-to-call link for mobile browsers. We use the reverse controllers for linking our edit
and remove
actions.
Finally, we have to provide a proper implementation of the main
view:
We removed the title
parameter of the initial main
view implementation and declared the style
function, which we use in the HTML head
element to include our CSS stylesheets.
The body consists of the popular Twitter Bootstrap navbar
, which displays our brand, Phonebook
, and the content
argument of the main
view.
Fine-tuning the CSS style
We have to customize the Twitter Bootstrap style for our design. Please replace the content of public/stylesheets/main.css
with the following:
We ensure correct alignment to the top of big screens. The margin of tables and forms is also tweaked a little bit. Also, we align our Entry
buttons to the right-hand side of a table row.
Now reload our page. This is what it looks like:
Unfortunately, our data store is empty and we are currently unable to add entries. Nevertheless, to test the functionality we need to add some test data.
One easy way to achieve this is to intercept the application start-up and create some test data by implementing app/Global.java
in the Java project:
The onStart
method is called by Play on the application startup. So we have the opportunity to initialize our data store by programmatically saving phone book entries.
For the Scala project, we create app/Global.scala
:
Feel free to play around with our first version of the phone book prototype!