Views

MVC Application using Java and Play Framework

MVC Application using Java and Play Framework
Page content

In this article, I will be developing a Model-View-Controller (MVC) application using Java and Play Framework.

By the end of this article, we will learn:

  • How to develop a Model-View-Controller (MVC) application using Java and Play Framework.
  • However we will not be persisting the data in the database, in this article. Instead we will use Hashmap to store the data.

Refactoring of the Views and Connecting to the Database, will be handled in the separate article here.

If you have no time to read this article, but want to try the code for yourself, GitHub location is provided here. Go ahead and clone the code.


Overview

We will build a MVC application using Java and Play Framework for a Todo Task Application in that:

  • Each Todo Task has id, title, description, creation date, due date, status and comments.
  • User can Create New Task, Update Existing Task, View All Tasks as List, View a Task and Delete a Task.

What is Play Framework?

Play is a high-productivity Java and Scala web application framework that integrates components and APIs for modern web application development. Play was developed by web developers for web application development.

You will find Play’s Model-View-Controller (MVC) Architecture familiar and easy to learn.

As a full-stack framework, Play includes all the components you need to build Web Applications and REST services, such as an integrated HTTP server, form handling, Cross-Site Request Forgery (CSRF) protection, a powerful routing mechanism, I18n support, and more.

Play’s lightweight, stateless, web-friendly architecture uses Akka and Akka Streams under the covers to provide predictable and minimal resource consumption (CPU, memory, threads).

Play is non-opinionated about database access, and integrates with many Object Relational Mapping (ORM) layers. It supports Anorm, Slick, and JPA out of the box, but many customers use NoSQL or other ORMs.


Play Framework Setup

A Play application only needs to include the Play JAR files to run properly. These JAR files are published to the Maven Repository, therefore you can use any Java or Scala build tool(sbt) to build a Play project.

However, Play provides an enhanced development experience (support for routes, templates compilation and auto-reloading) when using the sbt.

Play Requirements

Install Java

Play Framework suports Java versions SE 8 through SE 11, inclusive. Support for latest Java versions are experimental and can be used, by making some tweaks, as shown below.

Download the Java JDK from here. Click on the downloaded .exe and complete the installation.

Since it is an Oracle Proprietary product you will need to sign-in/create Oracle Account, before downloading.

Install sbt

Download and Install latest sbt from their official download page.


Create new Play Application

Navigate to the folder where you want to create the new Play Application, open Command Prompt and type the following:

sbt new playframework/play-java-seed.g8

The above command will prompt us for a name for the project first. Next, it will ask for the domain (in reverse, as is the package naming convention in Java) that will be used for the packages. We can press Enter without typing a name, if we want to keep the defaults which are given in square brackets.

create-play-application-using-sbt.jpeg
Play Application created using sbt

Tweaks for Java 17

If you have Java 17 installed, Play Framework will not work as expected and will require some tweaks, as mentioned below:

  • open the build.sbt and add the following lines:
libraryDependencies ++= Seq(
  "com.google.inject"            % "guice"                % "5.1.0",
  "com.google.inject.extensions" % "guice-assistedinject" % "5.1.0"
)

Running the Application

Now follow the below steps to run the project and allow Play Framework to download all required dependencies.

cd mvc-java-play-framework
sbt run

The above command, after completion of execution will start the server on port 9000 and you will see the below message on console:

(Server started, use Enter to stop and go back to the console...)

play-server-start.jpeg
Play Server Started

In a browser, enter http://localhost:9000/ to view the welcome page. We should see the message Welcome to Play! in the browser.

play-home-page-browser.jpeg
Browser displaying Welcome Page


Anatomy of Play application

The layout of a Play Application is standardized to keep things as simple as possible. After the first successful compilation, the project structure looks like this:

app                      → Application sources
 └ assets                → Compiled asset sources
    └ stylesheets        → Typically LESS CSS sources
    └ javascripts        → Typically CoffeeScript sources
 └ controllers           → Application controllers
 └ models                → Application business layer
 └ views                 → Templates
build.sbt                → Application build script
conf                     → Configurations files and other non-compiled resources (on classpath)
 └ application.conf      → Main configuration file
 └ routes                → Routes definition
dist                     → Arbitrary files to be included in your projects distribution
public                   → Public assets
 └ stylesheets           → CSS files
 └ javascripts           → Javascript files
 └ images                → Image files
project                  → sbt configuration files
 └ build.properties      → Marker for sbt project
 └ plugins.sbt           → sbt plugins including the declaration for Play itself
lib                      → Unmanaged libraries dependencies
logs                     → Logs folder
 └ application.log       → Default log file
target                   → Generated stuff
 └ resolution-cache      → Info about dependencies
 └ scala-2.13
    └ api                → Generated API docs
    └ classes            → Compiled class files
    └ routes             → Sources generated from routes
    └ twirl              → Sources generated from templates
 └ universal             → Application packaging
 └ web                   → Compiled web assets
test                     → source folder for unit or functional tests

Import Play Application to your preferred IDE

I will be using the IntelliJ IDEA Community Edition for development.

If you wish to use the IntelliJ IDEA Community Edition, download the latest version from here.

Click on the downloaded .exe and complete the installation.

Before importing the Play Application into IDE, make sure the latest Scala Plugin is installed and enabled in IntelliJ IDEA.

To import a Play Project:

  1. Click on Project wizard, select Open.
  2. In the window that opens, select a project you want to import and click OK.

Check the project’s structure, make sure all necessary dependencies are downloaded.


Actions and Controllers

A Java method inside a Controller Class that processes request parameters and produces a result to be sent to the client is called an action.

A Controller is a Java class that extends play.mvc.Controller that logically groups together actions that may be related to results they produce for the client.

How the Routing works

Hit the URL http://localhost:9000/ on the browser

When we hit the url http://localhost:9000/ on the browser, Play framework will check for the routes that are present in the routes file available at /conf/routes.

1# Routes
2# This file defines all application routes (Higher priority routes first)
3# ~~~~
4
5# An example controller showing a sample home page
6GET     /                           controllers.HomeController.index()
7
8# Map static resources from the /public folder to the /assets URL path
9GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)
  • As you can see there is a route present for / with HTTP request GET.
  • This points to the index() action/method in the HomeController Class.

HomeController

controller.HomeController- The HomeController’s index action returns a web page available at /views/index.scala.html.

1public class HomeController extends Controller {
2  public Result index() {
3    return ok(views.html.index.render());
4  }

Index Html page

views.index.scala.html- This web page is the default index template in the views package and returns a simple welcome message, by calling the main template.

1@main("Welcome to Play") {
2  <h1>Welcome to Play!</h1>
3}
  • main template takes 2 arguments(see below) - title and html body.

Main Template page

views.main.scala.html- The index page calls the main template.

The main template then handles the rendering of the page header and body tags.

It takes two arguments:

  • a String for the title of the page and
  • an Html object to insert into the body of the page
 1@(title: String)(content: Html)
 2
 3<html lang="en">
 4    <head>
 5        @* Here's where we render the page title `String`. *@
 6        <title>@title</title>
 7        <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
 8        <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
 9    </head>
10    <body>
11        @* And here's where we render the `Html` object containing
12         * the page content. *@
13        @content
14
15        <script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
16    </body>
17</html>

This image illustrates the Play MVC at work.

play-data-flow-diagram.jpeg
Play MVC Data Flow Diagram


Enhancing the Vanilla Application

With our application scaffolding in place, let us start building our Todo Task Tracker Application using Play Framework.

Right now, I will be using HashMap for storing the data and in the next article, I will connect it to the database.

Display All Tasks as List

Let us first build the capability to display/view all tasks as list.

Add Route for Display All Tasks

Let us first add route in the conf/routes file.

 1# Routes
 2# This file defines all application routes (Higher priority routes first)
 3# ~~~~
 4
 5# An example controller showing a sample home page
 6GET     /                           controllers.HomeController.index()
 7GET     /todo-app/tasks             controllers.TasksController.getAllTasks
 8
 9# Map static resources from the /public folder to the /assets URL path
10GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

Controller

  • As explained above, A Controller is a Java class that extends play.mvc.Controller
  • Create new Controller - controllers.TaskController
1public class TasksController extends Controller {
2  private TasksService tasksService = new TasksService();
3
4  public Result getAllTasks(){
5      return ok(views.html.tasks.home.render(tasksService.getAllTasks()));
6  }
7}

Services

  • Create a service class for performing all Operations on our Model Class - services.TasksService
  • I will be using HashMap for storing the data for now.
1public class TasksService {
2  private Map<Long, Tasks> tasksMap = new HashMap<>();
3  private Map<Long, TodoTaskComments> todoTaskCommentsMap = new HashMap<>();
4
5  public List<Tasks> getAllTasks(){
6    return new ArrayList<>(tasksMap.values());
7  }
8}
  • Create HashMaps for storing the data.
  • Display the HashMap values.

Models

  1. Create a POJO Java Model Class - models.Tasks
 1package models;
 2
 3import java.time.LocalDate;
 4
 5public class Tasks {
 6    private Long systemTaskId;
 7    private String title;
 8    private String description;
 9    private String status;
10    private Date dueDate;
11    private String dueDateStr;
12    private Date creationDate;
13    private String creationDateStr;
14
15    @JsonManagedReference
16    private Set<TodoTaskComments> todoTaskCommentsSet;
17
18    //bolierplate Constructors, Getters and Setters, toString methods
19}
  1. Create a POJO Java Model Class - models.TodoTaskComments
 1public class TodoTaskComments {
 2  private Long todoTaskCommentsId;
 3  private String taskComments;
 4  private Date creationDate;
 5  private String creationDateStr;
 6
 7  @JsonBackReference
 8  private Tasks todoTask;
 9
10  //bolierplate Constructors, Getters and Setters, toString methods
11}

Views

Create new view - views.tasks.home.scala.html

 1@(tasksList : List[Tasks])
 2<html>
 3<head>
 4  <title>Todo Tracker Application</title>
 5</head>
 6<body>
 7  <h2 align="center">Todo Tracker Application</h2>
 8  <table align="center" border="1">
 9    <thead align="center">
10      <tr>
11        <th>Title</th>
12        <th>Description</th>
13        <th>Due Date</th>
14        <th>Status</th>
15        <th>No. Of Comments</th>
16      </tr>
17    </thead>
18    <tbody align="center">
19      @for(task <- tasksList) {
20      <tr>
21        <td>@task.getTitle()</td>
22        <td>@task.getDescription()</td>
23        <td>@task.getDueDateStr()</td>
24        <td>@task.getStatus()</td>
25        <td>@task.getTodoTaskCommentsSet().size()</td>
26      </tr>
27      }
28    </tbody>
29  </table>
30</body>
31</html>

Since we have created new views template file, we will have to stop the running application and compile it. Until we compile, Controller will not recognize this new file and throws error.

sbt compile

After successful compilation, restart the application using the command sbt run and server would be up and running.

In the browser, type http://localhost:9000/todo-app/tasks and we would see something similar as shown below:

play-display-all-tasks.jpeg
Display All Tasks as List

  • You have successfully created a complete data flow to Display All Tasks as List.

However, since we do not have any data, it is showing blank.


Create New Task

Now let us go ahead and build view for creating new task.

Add Route for Create Action

Add two routes.

  1. GET /create - For getting the Create Task Page.
  2. POST /create - For submitting the created task in the Create Task Page.
1GET     /todo-app/tasks/create      controllers.TasksController.getCreateForm(request: Request)
2+ nocsrf
3POST    /todo-app/tasks/create      controllers.TasksController.createTask(request: Request)

Update Controller- controllers.TaskController

  • We will inject the FormFactory object and use the Form object from the play.data package. This will help us in passing the Form to/from Views.
  • formFactory.form(.class) binds the empty Form Object to the Views.
  • formFactory.form(.class).bindFromRequest(request).get() helps in receiving the Class Object back from Form Object through Views.
 1@Inject
 2private FormFactory formFactory;
 3
 4@Inject
 5private MessagesApi messagesApi;
 6
 7public Result getCreateForm(Http.Request request){
 8    Form<Tasks> tasksForm = formFactory.form(Tasks.class);
 9    Seq<Tuple2<String, String>> statusListSeq = getTodoStatusAsList();
10    return ok(views.html.tasks.create.render(tasksForm, statusListSeq, messagesApi.preferred(request)));
11}
12
13public Result createTask(Http.Request request){
14    Tasks task = formFactory.form(Tasks.class).bindFromRequest(request).get();
15    tasksService.createTask(task);
16    return redirect(routes.TasksController.getAllTasks());
17}
18
19private Seq<Tuple2<String, String>> getTodoStatusAsList() {
20    return tasksService.getTodoStatusAsList();
21}

Update Service- services.TasksService

  • select form helper in the Views requires options attribute to be in Seq[(String, String)]) format. Hence we will convert the status list map from Java to Scala Seq using CollectionConverters.asScala(statusListMap).toSeq()
  • Date is stored as java.util.Date and hence provides the full format. To provide the simple required format, I am using dateFormatter method.
 1enum TASK_STATUS {NOT_STARTED, IN_PROGRESS, ON_HOLD, COMPLETED, DEFERRED}
 2
 3private String dateFormatter(Date originalDate){
 4    String formattedDate;
 5    try {
 6        DateFormat originalFormat = new SimpleDateFormat("E MMM dd HH:mm:ss Z yyyy", Locale.US);
 7        DateFormat targetFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
 8        formattedDate = targetFormat.format(originalFormat.parse(originalDate.toString()));
 9    } catch (ParseException e) {
10        throw new RuntimeException(e);
11    }
12    return formattedDate;
13}
14
15public void createTask(Tasks task){
16    Long id = (long) tasksMap.size();
17    task.setSystemTaskId(id);
18    task.setDueDateStr(dateFormatter(task.getDueDate()));
19    task.setCreationDate(new Date());
20    task.setCreationDateStr(dateFormatter(task.getCreationDate()));
21    task.setTodoTaskCommentsSet(new HashSet<>());
22    tasksMap.put(id,task);
23}
24
25public Seq<Tuple2<String, String>> getTodoStatusAsList() {
26    List<String> statusList = Stream.of(TASK_STATUS.values()).map(TASK_STATUS::name).toList();
27    Map<String, String> statusListMap = new HashMap<>();
28    for (String status: statusList){
29        statusListMap.put(status,status);
30    }
31    return CollectionConverters.asScala(statusListMap).toSeq();
32}

Update the views.tasks.home.scala.html

 1<body>
 2  <h2 align="center">Todo Tracker Application</h2>
 3  <div  align="center">
 4    <a class="btn btn-success" href="@routes.TasksController.getCreateForm()">Create</a>
 5  </div>
 6
 7  <table align="center" border="1">
 8    ...
 9  </table>
10</body>

Create new template - views.tasks.create.scala.html

 1@(tasksForm : Form[Tasks])(statusList : Seq[(String, String)])(implicit messages: play.i18n.Messages)
 2@import helper._
 3
 4<html>
 5<head>
 6  <title>Todo Tracker Application</title>
 7</head>
 8<body>
 9  <h2 align="center">Todo Tracker Application</h2>
10  @helper.form(action = routes.TasksController.createTask()){
11  <table border="1" align="center">
12    <tbody>
13    <tr>
14      <td>
15        @helper.inputText(
16        tasksForm("title"),
17        Symbol("_label") -> "Title",
18        Symbol("class") -> "form-control")
19      </td>
20    </tr><tr>
21      <td>
22        @helper.textarea(
23        tasksForm("description"),
24        Symbol("_label") -> "Description",
25        Symbol("class") -> "form-control")
26      </td>
27    </tr><tr>
28      <td>
29        @helper.inputDate(
30        tasksForm("dueDate"),
31        Symbol("_label") -> "Due Date",
32        Symbol("class") -> "form-control")
33      </td>
34    </tr><tr>
35      <td>
36        @select(
37        field = tasksForm("status"),
38        options = statusList,
39        Symbol("_default") -> "--Select One--",
40        Symbol("_label") -> "Status",
41        Symbol("class") -> "form-control")
42      </td>
43    </tr><tr>
44      <td align="center">
45        <button class="btn btn-success" type="submit">
46          Create Task
47        </button>
48      </td>
49    </tr>
50    </tbody>
51  </table>
52  }
53</body>
54</html>

Play provides several helpers to help you render form fields in HTML templates.

  • The first helper(in line#10) creates the <form> tag. It is a pretty simple helper that automatically sets the action and method tag parameters according to the reverse route you pass in.
@helper.form(action = routes.TasksController.createTask()){

}
  • There are several input helpers in the views.html.helper package. You feed them with a form field, and they display the corresponding HTML form control, with a populated value, constraints and errors. The most helpful are:
    • form: renders a form element.
    • inputText: renders a text input element.
    • inputPassword: renders a password input element.
    • inputDate: renders a date input element.
    • inputFile: renders a file input element.
    • inputRadioGroup: renders a radio input element.
    • select: renders a select element.
    • textarea: renders a textarea element.
    • checkbox: renders a checkbox element.
    • input: renders a generic input element (which requires explicit arguments).

Get more information about the Play Form Helpers here.

Since we have created new views template file, we will have to stop the running application and compile it, using sbt compile. After successful compilation, restart the application using the command sbt run and server would be up and running.

In the browser, type http://localhost:9000/todo-app/tasks and we would see something similar as shown below:

play-home-page-with-create-button.jpeg
Home page with Create Task Link

When we click on Create Link, we are directed to the Create Task Page.

create-task-page.jpeg
Create Task Page

Once user completes the form, click on Submit and Home page will be displayed with the data.

play-home-page-after-create.jpeg
Home page After Create Action

Remember, this data is coming from the Hashmap and this is not the persisted data(data stored in database).


Display One Item

Add Route for View Action

1GET     /todo-app/tasks/view/:id    controllers.TasksController.view(id : Long)

Update Controller- controllers.TaskController

1public Result view(long id){
2    Tasks task = tasksService.getTaskById(id);
3    return ok(views.html.tasks.view.render(task));
4}

Update Service- services.TasksService

1public Tasks getTaskById(long id){
2    return tasksMap.get(id);
3}

Update the views.tasks.home.scala.html

 1<table align="center" border="1">
 2  <caption style="caption-side: top;font-style:italic;text-align:center;">Click on the row(s) to view the details</caption>
 3  <thead align="center">
 4    <tr>
 5      <th>Title</th>
 6      <th>Description</th>
 7      <th>Due Date</th>
 8      <th>Status</th>
 9      <th>No. Of Comments</th>
10    </tr>
11  </thead>
12  <tbody align="center">
13  @for(task <- tasksList) {
14  <tr onclick="rowClick('@routes.TasksController.view(task.getSystemTaskId())');">
15    <td>@task.getTitle()</td>
16    <td>@task.getDescription()</td>
17    <td>@task.getDueDateStr()</td>
18    <td>@task.getStatus()</td>
19    <td align="center">@task.getTodoTaskCommentsSet().size()</td>
20  </tr>
21  }
22  </tbody>
23</table>
24<script>
25  //To enable the click on the row functionality, in the Homepage
26  function rowClick(iUrl){
27      document.location.href = iUrl;
28  }
29</script>

Create new template - views.tasks.view.scala.html

 1@(task : Tasks)
 2<html>
 3  <head>
 4    <title>Todo Tracker Application</title>
 5  </head>
 6  <body>
 7    <h2 align="center">Todo Tracker Application</h2>
 8    <table align="center" border="1">
 9      <tbody>
10        <tr>
11          <td>Title</td>
12          <td>@task.getTitle()</td>
13        </tr><tr>
14          <td>Description</td>
15          <td>@task.getDescription()</td>
16        </tr><tr>
17          <td>Creation Date</td>
18          <td>@task.getCreationDateStr()</td>
19        </tr><tr>
20          <td>Due Date</td>
21          <td>@task.getDueDateStr()</td>
22        </tr><tr>
23          <td>Status</td>
24          <td>@task.getStatus()</td>
25        </tr>
26        @if(task.getTodoTaskCommentsSet().size() > 0){
27        <tr>
28          <td colspan="2">
29            <table class="table table-bordered" style="text-align:center;">
30              <caption style="caption-side: top;font-weight:bold;text-align:center;">Task Comments</caption>
31              <thead>
32              <tr>
33                <th>Creation Date</th>
34                <th>Description</th>
35              </tr>
36              </thead>
37              <tbody>
38              @for(taskComment <- task.getTodoTaskCommentsSet()) {
39              <tr>
40                <td>@taskComment.getCreationDateStr()</td>
41                <td>@taskComment.getTaskComments()</td>
42              </tr>
43              }
44              </tbody>
45            </table>
46          </td>
47        </tr>
48        }
49      </tbody>
50    </table>
51  </body>
52</html>

Since we have created new views template file, we will have to Stop, Compile and Run the Application Server.

In the browser, type http://localhost:9000/todo-app/tasks, Home page with data is available.

home-page-with-view-button.jpeg
Homepage with View Link
Click on the row and the Task will be displayed.
play-view-page.jpeg
View page after View Action


Update One Task

Add Route for View Action

1GET     /todo-app/tasks/edit/:id    controllers.TasksController.edit(id : Long, request: Request)
2+ nocsrf
3POST    /todo-app/tasks/update      controllers.TasksController.updateTask(request: Request)

Update Controller- controllers.TaskController

  • In order to update the Task, the existing data should be passed onto the Form, as is. This is where formFactory.form(.class).fill comes in handy.
 1public Result edit(long id, Http.Request request){
 2  Tasks task =tasksService.getTaskById(id);
 3  Form<Tasks> tasksForm = formFactory.form(Tasks.class).fill(task);
 4
 5  Set<TodoTaskComments> todoTaskCommentsSet = task.getTodoTaskCommentsSet();
 6  Form<TodoTaskComments> todoTaskCommentsForm = formFactory.form(TodoTaskComments.class).bindFromRequest(request);
 7
 8  Seq<Tuple2<String, String>> statusListSeq = getTodoStatusAsList();
 9  return ok(views.html.tasks.edit.render(tasksForm, statusListSeq, todoTaskCommentsSet,
10          todoTaskCommentsForm, messagesApi.preferred(request)));
11}
12
13public Result updateTask(Http.Request request){
14    Form<Tasks> tasksForm = formFactory.form(Tasks.class).bindFromRequest(request);
15    Tasks task = tasksForm.get();
16    
17    Form<TodoTaskComments> todoTaskCommentsForm = formFactory.form(TodoTaskComments.class).bindFromRequest(request);
18    TodoTaskComments todoTaskComments = todoTaskCommentsForm.get();
19
20    tasksService.updateTask(task, todoTaskComments);
21    return redirect(routes.TasksController.getAllTasks());
22}

Update Service- services.TasksService

  • For updating the task with new data, we will first get he old task and then update with new data.
 1private Set<TodoTaskComments> todoTaskCommentsSet = new HashSet<>();
 2
 3public void updateTask (Tasks task, TodoTaskComments todoTaskComments) {
 4  Tasks oldTask = tasksMap.get(task.getSystemTaskId());
 5  if(null != oldTask) {
 6    oldTask.setTitle(task.getTitle());
 7    oldTask.setDescription(task.getDescription());
 8    oldTask.setDueDate(task.getDueDate());
 9    oldTask.setDueDateStr(dateFormatter(task.getDueDate()));
10    oldTask.setCreationDate(task.getCreationDate());
11    oldTask.setCreationDateStr(dateFormatter(task.getCreationDate()));
12    oldTask.setStatus(task.getStatus());
13
14    if(null != oldTask.getTodoTaskCommentsSet())
15        todoTaskCommentsSet = oldTask.getTodoTaskCommentsSet();
16
17    if(todoTaskComments.getTaskComments() != null && !todoTaskComments.getTaskComments().isEmpty()) {
18        todoTaskComments.setCreationDate(new Date());
19        todoTaskComments.setCreationDateStr(dateFormatter(todoTaskComments.getCreationDate()));
20        todoTaskComments.setTodoTask(oldTask);
21        todoTaskCommentsSet.add(todoTaskComments);
22    }
23    oldTask.setTodoTaskCommentsSet(todoTaskCommentsSet);
24  }
25}

Update the views.tasks.home.scala.html

 1<table align="center" border="1">
 2  <caption style="caption-side: top;font-style:italic;text-align:center;">Click on the row(s) to view the details</caption>
 3  <thead align="center">
 4    <tr>
 5      <th>Title</th>
 6      <th>Description</th>
 7      <th>Due Date</th>
 8      <th>Status</th>
 9      <th>No. Of Comments</th>
10      <th>Edit</th>
11    </tr>
12  </thead>
13  <tbody align="center">
14  @for(task <- tasksList) {
15  <tr onclick="rowClick('@routes.TasksController.view(task.getSystemTaskId())');">
16    <td>@task.getTitle()</td>
17    <td>@task.getDescription()</td>
18    <td>@task.getDueDateStr()</td>
19    <td>@task.getStatus()</td>
20    <td align="center">@task.getTodoTaskCommentsSet().size()</td>
21    <td><a href="@routes.TasksController.edit(task.getSystemTaskId())">Edit</a></td>
22  </tr>
23  }
24  </tbody>
25</table>

Create new template - views.tasks.edit.scala.html

  1@(tasksForm : Form[Tasks])(statusList : Seq[(String, String)])(todoTaskCommentsSet : Set[TodoTaskComments])(todoTaskCommentsForm : Form[TodoTaskComments])(implicit messages: play.i18n.Messages)
  2@import helper._
  3
  4<html>
  5<head>
  6  <title>Todo Tracker Application</title>
  7</head>
  8<body>
  9<h2 align="center">Todo Tracker Application</h2>
 10  @helper.form(action = routes.TasksController.updateTask()){
 11<table border="1" align="center">
 12    <tbody>
 13    <tr>
 14      <td>
 15        @helper.inputText(
 16        tasksForm("title"),
 17        Symbol("_label") -> "Title",
 18        Symbol("class") -> "form-control")
 19      </td>
 20    </tr><tr>
 21      <td>
 22        @helper.textarea(
 23        tasksForm("description"),
 24        Symbol("_label") -> "Description",
 25        Symbol("class") -> "form-control")
 26      </td>
 27    </tr><tr>
 28      <td>
 29        @helper.inputDate(
 30        tasksForm("creationDate"),
 31        Symbol("_label") -> "Creation Date",
 32        Symbol("readonly") -> "readonly",
 33        Symbol("class") -> "form-control")
 34      </td>
 35    </tr><tr>
 36      <td>
 37        @helper.inputDate(
 38        tasksForm("dueDate"),
 39        Symbol("_label") -> "Due Date",
 40        Symbol("class") -> "form-control")
 41      </td>
 42    </tr><tr>
 43      <td>
 44        @select(
 45        field = tasksForm("status"),
 46        options = statusList,
 47        Symbol("_default") -> "--Select One--",
 48        Symbol("_label") -> "Status",
 49        Symbol("class") -> "form-control")
 50      </td>
 51    </tr>
 52    @if(todoTaskCommentsSet.size() > 0){
 53    <tr>
 54      <td colspan="2">
 55        <table class="table table-bordered" style="text-align:center;">
 56          <caption style="caption-side: top;font-weight:bold;text-align:center;">Task Comments</caption>
 57          <thead>
 58          <tr>
 59            <th>Creation Date</th>
 60            <th>Description</th>
 61          </tr>
 62          </thead>
 63          <tbody>
 64          @for(taskComment <- todoTaskCommentsSet) {
 65          <tr>
 66            <td>@taskComment.getCreationDateStr()</td>
 67            <td>@taskComment.getTaskComments()</td>
 68          </tr>
 69          }
 70          </tbody>
 71        </table>
 72      </td>
 73    </tr>
 74    }
 75    <tr>
 76      <td colspan="2">
 77        <table class="table table-borderless">
 78          <tr>
 79            <td>
 80              <input type="button" class="btn btn-primary" onclick=addNewComments() value="Add New Comments"/>
 81            </td>
 82            <td id="commentsTable" style="display:none;">
 83              @helper.textarea(
 84              tasksForm("taskComments"),
 85              Symbol("_label") -> "Description",
 86              Symbol("class") -> "form-control")
 87            </td>
 88          </tr>
 89        </table>
 90      </td>
 91    </tr>
 92    <tr>
 93      <td align="center">
 94        <input type="hidden" id="systemTaskId" name="systemTaskId" value="@tasksForm.get().getSystemTaskId()"/>
 95        <button class="btn btn-success" type="submit">
 96          <i class="fa fa-edit" aria-hidden="true"></i> Update Task
 97        </button>
 98      </td>
 99    </tr>
100    </tbody>
101  </table>
102  }
103  <script>
104    function addNewComments(){
105      var lTable = document.getElementById("commentsTable");
106      lTable.style.display = (lTable.style.display == "table") ? "none" : "table";
107    }
108  </script>
109</body>
110</html>

Since we have created new views template file, we will have to Stop, Compile and Run the Application Server.

In the browser, type http://localhost:9000/todo-app/tasks, Home page with Edit link is available.

home-page-with-edit-button.jpeg
Homepage with Edit Link

When we click on Edit Link, we are directed to the Update Task Page.

edit-task-page.jpeg
Update Task Page

Once user updates the form, click on Update Task button and Home page will be displayed with the updated data.

home-page-after-update.jpeg
Home page After Update Action

If we click on the row now, we will see the updated data with comments.

view-page-after-update.jpeg
View page After Update


Delete Task

Add Route for View Action

1GET  /todo-app/tasks/delete/:id  controllers.TasksController.deleteTask(id : Long)

Update Controller- controllers.TaskController

1public Result deleteTask(long id){
2  tasksService.deleteTask(id);
3  return redirect(routes.TasksController.getAllTasks());
4}

Update Service- services.TasksService

1public void deleteTask(long systemTaskId){
2  tasksMap.remove(systemTaskId);
3}

Update the views.tasks.home.scala.html

 1<table align="center" border="1">
 2    <caption style="caption-side: top;font-style:italic;text-align:center;">Click on the row(s) to view the details</caption>
 3    <thead align="center">
 4      <tr>
 5        <th>Title</th>
 6        <th>Description</th>
 7        <th>Due Date</th>
 8        <th>Status</th>
 9        <th>No. Of Comments</th>
10        <th>Edit</th>
11        <th>Delete</th>
12      </tr>
13    </thead>
14    <tbody align="center">
15    @for(task <- tasksList) {
16    <tr onclick="rowClick('@routes.TasksController.view(task.getSystemTaskId())');">
17      <td>@task.getTitle()</td>
18      <td>@task.getDescription()</td>
19      <td>@task.getDueDateStr()</td>
20      <td>@task.getStatus()</td>
21      <td align="center">@task.getTodoTaskCommentsSet().size()</td>
22      <td><a href="@routes.TasksController.edit(task.getSystemTaskId())">Edit</a></td>
23      <td><a href="@routes.TasksController.deleteTask(task.getSystemTaskId())">Delete</a></td>
24    </tr>
25    }
26    </tbody>
27  </table>

In the browser, type http://localhost:9000/todo-app/tasks, Home page with Delete Link is available.

home-page-with-delete-button.jpeg
Homepage with Delete Link

When we click on Delete Link, the task will be deleted.

home-page-after-delete.jpeg
Home page After Delete Action


Conclusion

With this setup complete, we have come to the end of this article, where we have learnt:

  • How to develop a Model-View-Controller (MVC) application using Java and Play Framework.
  • However this was done without persisting the data in the database. Instead we used Hashmap to store the data.

In the next article, I will be showing you:

  • How we can Refactor the Views using Bootstrap and jQuery, so that the Views are presented in a better way.
  • How to Connect to the Database to persist the data, using Ebean ORM.

Complete code for this project can be found at GitHub here. Go ahead and clone it.

Instructions on how to clone the code repository and run the project are provided on the GitHub project page.