JavaScript Routing with Play Framework
January 04, 2018
In this tutorial, we will create a Play Framework (Scala) application using JavaScript Routing (Ajax + jQuery) to communicate with a backend server without reloading the page .
Git Hub Project
The complete project can be cloned from here.
Prerequisites
Please make sure both Scala and SBT are installed on your local Linux system. Please follow my tutorial post here to install.
Reference
Overview
You can start from the scratch using the starter example from here or you can just download the completed project from here and play with it.
Briefly,
- We need a controller class to define controller actions
- We define these actions in
JavaScriptReverseRouter
- We will also need to define these actions in
routes
(just like any routes in Play framework) - We will use JavaScript functions to call these routes from a web page
- After successful call, we will display on the web page
Add Dependencies
We will be using scala version 2.12.4
. We will add guice
and play test
as per normal for the play framework. Since we will be calling Ajax
request using JavaScript
, we will just add jquery
to our dependencies to make our life easier.
scalaVersion := "2.12.4"
libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test
libraryDependencies += "org.webjars" % "jquery" % "2.1.3"
Home Controller
We define getName
and updateName
actions in HomeController
class. At getName
action, we get the name from the storage, add to the JSON and then make the response with this JSON. At updateName
action, we get the name
from our user and save it to our storage. Then we just again respond the result with JSON. In order to call these routes from JavaScript, we need to generate JavaScript Routing. These can be simply done by just adding in jsRoutes
action as shown in line 28
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package controllers
import javax.inject._
import play.api.libs.json.Json
import play.api.mvc._
import play.api.routing.JavaScriptReverseRouter
import services.NameStorage
import scala.concurrent.Future
@Singleton
class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def index = Action {
Ok(views.html.index("Ajax Play Application"))
}
def getName = Action.async { implicit request =>
Future.successful(Ok(Json.toJson(NameStorage.getName)))
}
def updateName(name: String) = Action.async { implicit request =>
NameStorage.setName(name)
Future.successful(Ok(Json.toJson(NameStorage.getName)))
}
def jsRoutes = Action { implicit request =>
Ok(
JavaScriptReverseRouter("jsRoutes")(
routes.javascript.HomeController.getName,
routes.javascript.HomeController.updateName
)).as("text/javascript")
}
}
Router
We will add all these actions to our router file routes
1
2
3
4
5
6
7
GET / controllers.HomeController.index
GET /person controllers.HomeController.getName
GET /person/:name controllers.HomeController.updateName(name: String)
GET /jsr controllers.HomeController.jsRoutes
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
Index HTML file
The important part of this HTML is that make sure these are added
- jQuery library (
jquery.min.js
) - JavaScript file that we gonna use (in this case:
hello.js
) - JavaScript Route (here:
@routes.HomeController.jsRoutes
) - Matching HTML ID with
hello.js
file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@(title: String)
<!DOCTYPE html>
<html lang="en">
<head>
<title>@title</title>
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
<script type="text/javascript" src="@routes.Assets.versioned("lib/jquery/jquery.min.js")"></script>
<script src="@routes.Assets.versioned("javascripts/hello.js")" type="text/javascript"></script>
<script type="text/javascript" src="@routes.HomeController.jsRoutes"></script>
</head>
<body>
<span> Current Name is: </span>
<span id="name"></span>
<br>
<input type="button" value="Set Name" id="set_name" >
<input type="text" value="Jane" id="input_name">
<br>
<input type="button" value="Get Name" id="get_name" >
</body>
</html>
JavaScript
You should take note how the ajax requests are called here.
jsRoutes.controllers.HomeController.getName().ajax(/*...*/)
jsRoutes.controllers.HomeController.updateName(name).ajax(/*...*/)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$(document).ready(function() {
$('#get_name').click(function() {
jsRoutes.controllers.HomeController.getName().ajax({
success: function(result) {
alert("Hello: " + result)
$("#name").text(result);
},
failure: function(err) {
var errorText = 'There was an error';
$("#name").text(errorText);
}
});
});
$('#set_name').click(function() {
var inputName = $("#input_name").val()
jsRoutes.controllers.HomeController.updateName(inputName).ajax({
success: function(result) {
$("#name").text(result);
},
failure: function(err) {
var errorText = 'There was an error';
$("#name").text(errorText);
}
});
});
});
Storage
This is just a dummy database to store a name.
1
2
3
4
5
6
7
8
9
10
11
package services
object NameStorage {
private var mName = "Bob"
def setName(name: String): Unit = {
mName = name
}
def getName: String = mName
}
Result
- Go to project folder and do
sbt run
. - Go to
localhost:9000
using any browser. - Click
GetName
Button. There will be an alert message withBob
name without refreshing the page. You can also change the name at input box. Clicking onSet Name
should change the current name automatically. Again, without refreshing the page.