新版本的 Scala Play 又有些不同,軟體的更新帶來了新的功能,新的想法跟做法,但就得花一些時間去了解,一個沒有變化的專案除了表示穩定,也可能是維護的人太少,沒辦法推出新功能,一個常常更新的專案,一般都是希望能夠相容舊版,無痛升級,雖然對開發者來說是福音,但也可能因為相容而自我設限,造成整個專案越來越龐大,該怎麼做沒有定論,Play Framework 就曾因為升級到 2 之後,不跟 1 相容,造成專案開發者的流失,不過都已經是過去的歷史了。
書本的內容也沒辦法隨著軟體更新就馬上更新,所幸 Play Framework 官方文件很完整,跟著文件的說明測試一次,應該就能大致掌握新版的功能。
安裝 Play
- 安裝 Java SDK
- 下載 Activator 1.3.10 minimal
- 將 typesafe-activator-1.3.10-minimal 資料夾的 bin 目錄放到環境變數的 PATH 中
- 確認可以在 console 執行 activator
產生 project
在 console 產生 project 有兩種方式,command line 或是 web ui。
- command line
使用 activator 就可以產生 project
activator new playtest1 play-scala
切換到 playtest project folder,就可以用 activator 執行project
cd playtest1
activator
- activator ui
執行 activator ui,就可以使用 activator 內建的網頁管理介面
activator ui
連結到 http://localhost:8888 就可以看到管理介面
選擇 Lightbend 的 Play Scala Seed 專案 template 及路徑,就可以產生 project
Play 支援了 Eclipse, IDEA, NetBeans, ENSIME 這些 IDE,跟著 Setting up your preferred IDE 的說明,就可以利用 IDE 直接產生一個新的 project。
以下記錄 IDEA 的步驟:
如果要產生一個新的 Play application
- New Project
- 選擇 Scala -> Activator
- 選擇一個適當的 template,最簡單的就是選擇 Lightbend 提供的 Play Scala Seed
- Finish
如果要 import 一個已經產生的 Play application
- File -> New -> Project from existing sources
- 選擇 project folder
- 選擇 Import project from external model -> SBT
- Finish
activator command line
cd playtest1
# 進入 activator 互動介面
activator
編譯 project
[playtest1] $ compile
執行 project,在這個模式下會啟用 auto-reload 功能,也就是 Play 會自動檢查 project source 有沒有更新,並自動重新編譯
[playtest1] $ run
執行測試程式
[playtest1] $ test
進入 scala console mode,按 Ctrl-D 就可以跳出 console mode
[playtest1] $ console
在 scala console mode,可以直接使用 play 的 classes
scala> views.html.index("Hello")
res0: play.twirl.api.HtmlFormat.Appendable =
<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome to Play</title>
以下這段 code 在文件中提到是可以在 console mode 中啟動 application
import play.api._
val env = Environment(new java.io.File("."), this.getClass.getClassLoader, Mode.Dev)
val context = ApplicationLoader.createContext(env)
val loader = ApplicationLoader(context)
val app = loader.load(context)
Play.start(app)
如果 debug 就要在啟動 activator 加上參數,JDPA 就會運作在 Port 9999 上
activator -jvm-debug 9999
Play console 其實就是一個 sbt console,因此我們可以用以下的方式,透過 sbt 進行 triggered execution
[playtest1] ~ compile
[playtest1] ~ run
[playtest1] ~ test
Play application file layout
app → app 原始程式
└ assets → 程式資源資料夾
└ stylesheets → LESS CSS 原始程式
└ javascripts → CoffeeScript 原始程式
└ controllers → app controllers
└ models → app business layer
└ views → 網頁 templates
build.sbt → sbt build script
conf → 設定檔及其他不需要編譯的資源
└ application.conf → app 主設定檔
└ routes → 路由跟程式的對應設定
dist → 專案發布的資料夾
public → 對外開放的資源資料夾
└ stylesheets → CSS files
└ javascripts → Javascript files
└ images → Image files
project → sbt 設定檔
└ build.properties → sbt 專案的設定檔
└ plugins.sbt → sbt plugins
lib → 不在 lib 倉庫中受管理的 libraries
logs → logs 資料夾
└ application.log → 預設的 log file
target → 編譯後的檔案資料夾
└ resolution-cache → 相關 libraries 的資料
└ scala-2.11
└ api → API 文件
└ classes → 編譯後的 class files
└ routes → routes 編譯後的結果
└ twirl → templates 編譯後的結果
└ universal → app 封裝
└ web → 編譯後的網頁資源
test → 單元測試的資料夾
Task sample project
以剛剛的 test1 為基礎,修改成一個簡單的 Task 網頁
- 修改 conf/routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
# Home page
GET / controllers.TaskController.index
# Tasks
GET /tasks controllers.TaskController.tasks
POST /tasks controllers.TaskController.newTask
DELETE /tasks/:id controllers.TaskController.deleteTask(id: Int)
- 新增 models/Task.scala
package models
case class Task(id: Int, name: String)
object Task {
private var taskList: List[Task] = List()
def all: List[Task] = {
// 回傳整個 taskList
taskList
}
def add(taskName: String) = {
// task id 由 0 開始,如果 list 為 nonEmpty,就使用 list 中最後一個元素的 id
val lastId: Int = if (taskList.nonEmpty) taskList.last.id else 0
taskList = taskList ++ List(Task(lastId + 1, taskName))
}
def delete(taskId: Int) = {
// filter 可取得滿足條件的所有元素
// filterNot 可取得 不滿足 條件的所有元素
taskList = taskList.filterNot(task => task.id == taskId)
}
}
- 新增 app/controllers/TaskController
package controllers
import models.Task
import play.api.mvc._
class TaskController extends Controller {
def index = Action {
// 將 request 由 http://localhost:9000/ redirect 到 http://localhost:9000/tasks
Redirect(routes.TaskController.tasks)
}
def tasks = Action {
// Task.all 取得整個 taskList,送入 views.html.index 產生網頁
Ok(views.html.index(Task.all))
}
def newTask = Action(parse.urlFormEncoded) {
implicit request =>
// 取得 request 裡面的 taskName, 新增到 Task 中
Task.add(request.body.get("taskName").get.head)
// redirect 到 http://localhost:9000/tasks
Redirect(routes.TaskController.index)
}
def deleteTask(id: Int) = Action {
// 刪除 task id
Task.delete(id)
Ok
}
}
- views/index.scala.html
@(tasks: List[Task])
@main("Task Tracker") {
<h2>Task</h2>
<div>
<form action="@routes.TaskController.newTask()" method="post">
<input type="text" name="taskName" placeholder="Add a new Task" required>
<input type="submit" value="Add">
</form>
</div>
<div>
<ul>
@tasks.map { task =>
<li>
@task.name <button onclick="deleteTask ( @task.id) ;">Remove</button>
</li>
}
</ul>
</div>
<script>
function deleteTask ( id ) {
var req = new XMLHttpRequest ( ) ;
req.open ( "delete", "/tasks/" + id ) ;
req.onload = function ( e ) {
if ( req.status = 200 ) {
document.location.reload ( true ) ;
}
} ;
req.send ( ) ;
}
</script>
}
- views/main.scala.html
@(title: String)(content: play.twirl.api.Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
</head>
<body>
@content
</body>
</html>
沒有留言:
張貼留言