Copyright © Cay S. Horstmann 2015
This work is licensed under a Creative Commons Attribution 4.0 International License
java
is on the PATH
. Open a command shell. Type
javac -versionYou should be getting something like
javac 1.8.0_31
. If not, follow these directions. In a command shell, type
activator-1.3.2/activator new(adjusting the directory path and version number if necessary) Select the
play-scala
option from the menu, then give unit15
as application name.
cd unit15 activator-1.3.2/activator(without
new
)! You are now getting a prompt in the Activator shell. Type
~runPoint your browser to localhost:9000. What happens?
eclipse
unit15
directory. Select Finish.~run(note the ~).
unit15/target/scala-2.11/src_managed/main
and unit15/target/scala-2.11/twirl/main
to the source folders (see this post)index.scala.html
, replace the code inside { }
with
Hello @messageSave and reload the localhost:9000 page. What happened?
Application.scala
and change "Your application is ready"
to "Unit 15"
.
Save and reload the localhost:9000 page. What happened?conf/routes
)data
val hello = Action { request => Ok(s"Hello $request") }
object Application extends Controller { def hello(name: String) = Action { Ok(s"Hello $name") } def goodbye(name: String) = Action { NotFound(<h1>Page not found</h1>) } def index(name: String) = TODO }
Future
val futurePiValue: Future[Double] = computePiAsync() val futureResult: Future[Result] = futurePiValue.map(res => Ok(res))
Action.async
instead of Action.apply
:
def pi = Action.async { computePiAsync().map(res => Ok(res)) }
conf/routes
GET /tasks/all controllers.Tasks.list() GET /tasks/find controllers.Tasks.list(id: Long) // id must be in query string /tasks/find?id=...
GET /task/:id controllers.Tasks.find(id: Long)
GET /assets/*file controllers.Assets.at(path="/public", file)
@
to inject Scala expressions
<ul>@for (task <- tasks) { <li>@task.label</li> }</ul>
@(title: String, tasks: List[Task])
Form[T]
object converts between form contents and objects of type T
val loginForm = Form( tuple( "name" -> text, "age" -> number ) )Result object has type
(String, Int)
case class User(name: String, age: Int) val userForm = Form( mapping( "name" -> text, "age" -> number )(User.apply)(User.unapply) )
@import helper._
@form
generates HTML form
tag
@form(routes.Application.deleteTask(task.id)) { <input type="submit" value="Delete"> }
action
attribute taken from route argument (“reverse routing”)@form(routes.Application.newTask) { @inputText(taskForm("label")) ... }
@(tasks: List[Task], taskForm: Form[String])
SQL("INSERT INTO Task (label) VALUES ({label})").on( "label" -> label).executeUpdate()Here,
label
is
Connection
object—best passed “implicitly” (see lab)
DB.withConnection { implicit c => SQL(...).on(...).executeUpdate() }
"SELECT * FROM Task"
get[Type]("name")
~
, *
, like in Unit 12val task = { get[Long]("id") ~ get[String]("label") map { case id ~ label => Task(id, label) } }
val result: List[Task] = SQL("SELECT * FROM Task").as(task *)
conf/routes
and described, and check the compile-time error that tells you the routes error.Application
and verify the TODO behavior.models
inside app
and define the Task
class as specified.index.scala.html
page, task form, and the tasks
action as described. Make sure that http://localhost:9000/tasks looks like in the tutorial in the section "Rendering the first page". index.scala.html
page. What parameters does the page have? Where do they come from? (That is, who supplies them?) What happens with the first parameter? With the second?Form
in the Play API. What is the significance of bindFromRequest
? Explain the syntax of the stuff inside fold(...)
. db.default
. Make the file conf/evolutions/default/1.sql (and be careful that you use exactly that path—otherwise, it won't work). Verify that you get the evolution screen, and click Apply. task
parser. Which class does it go into?map
. What was it called there?all
method. That's a lot of strange syntax. We haven't covered implicit
yet, except for implicitly
in Unit 9. The idea is that a parameter labeled implicit
is set to the unique object of the appropriate type that has been defined as implicit
. Let's start figuring this out. What is DB
? (Look it up in the Play API.) withConnection
. Which one are we calling?db
. What type does that function return?database
. What type does it return? def withConnection[A](block: (Connection) ⇒ A): AClick on Database.scala and read the implementation (it's in
DefaultDatabase
). If you've ever done JDBC programming, this is straightforward:
implicit
. Start again with DB.withTransaction
. Notice the last (curried) parameter. It is declared as implicit
. That means, an object is silently passed to this function whenever it is called. What is the type of that object? db
. What happens to that special object?Application
object get in there in the first place? We can't (easily) answer that, but we can look at how another object is passed as an implicit object. Let's go back to
def all(): List[Task] = DB.withConnection { implicit c => SQL("select * from task").as(task *) }Ignoring the
implicit
for a minute, recall what happens here. A connection was found, and block(connection)
was called. That's just a normal function call. To see why c
is implicit
, we need to look further. Mouse over the as
method in Eclipse. What do you get?as
gets called, it wants some connection. Which one is it going to get?*
behind task
? Hint: Mouse over it.create
and delete
methods. Run the app and create a few tasks.create
and delete
. Notice the curious 'label
and 'id
. Those are Scala symbols—kind of like strings, but different. Replace them with "label"
and "id"
, and verify that you can still create and delete tasks.Sadly, we have reached the end of the rope with Codecheck. In this homework assignment, you will write a simple Play application. Zip up the entire directory and email it to Prof. Borran when you are done. Also, be prepared to demo your solution during the next class meeting.
Write a web app that can be used for online “clickers”, rather than the paper index cards that we have used in class. Here is the workflow.
quiz/new
and enters the question and four answer choices [Extra credit for variable numbers of choices]. When the professor hits submit, a number is displayed (just show the ID number in the database)quiz/reply/id
(with that ID) and select an answerquiz/results/id
, and the number of responses for each choice is displayed.Tip: If you want to make the app look prettier (and also for general hints), run activator new computer-database-scala
and have a look at the resulting app.