MVC Application using Java and Play Framework
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.
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...)
In a browser, enter http://localhost:9000/ to view the welcome page. We should see the message Welcome to Play! in the browser.
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:
- Click on Project wizard, select Open.
- 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 requestGET
. - This points to the
index()
action/method in theHomeController
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
andhtml 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.
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
- 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}
- 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:
- 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.
GET /create
- For getting the Create Task Page.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 theForm
object from theplay.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 requiresoptions
attribute to be inSeq[(String, String)])
format. Hence we will convert the status list map from Java to ScalaSeq
usingCollectionConverters.asScala(statusListMap).toSeq()
Date
is stored asjava.util.Date
and hence provides the full format. To provide the simple required format, I am usingdateFormatter
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:
When we click on Create Link, we are directed to the Create Task Page.
Once user completes the form, click on Submit and Home page will be displayed with the data.
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. Click on the row and the Task will be displayed.
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.
When we click on Edit Link, we are directed to the Update Task Page.
Once user updates the form, click on Update Task button and Home page will be displayed with the updated data.
If we click on the row now, we will see the updated data with comments.
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.
When we click on Delete Link, the task will be deleted.
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.