§はじめての画面
はじめてのデータモデルを作ったので、いよいよこのアプリケーションのはじめての画面を作り始めます。この画面は、すべてのプロジェクトのサマリと、これらのプロジェクトにおいて成されるタスクを表示するダッシュボードにします。
以下は、実現したい画面のモックです:
§デフォルトデータでの起動
実は、はじめての画面をコーディングする前にやらなければならないことが、もうひとつあります。テストデータなしに web アプリケーションに関する作業をするのは楽しくありません。テストすることすらできません。しかし、まだタスク管理画面を開発していないので、自分自身でダッシュボードにタスクを追加することもできません。
タスク管理システムにデフォルトデータを投入するひとつの方法は、テストのときと同じようにアプリケーション起動時に YAML ファイルを読み込むことです。このために、アプリケーションがデータと共に起動するよう Play の起動処理をフックします。Play 起動処理のフックはシンプルで、ルートパッケージにある GlobalSettings
を実装する Global
と呼ばれるクラスを作成し、onApplicationStart()
メソッドをオーバーライドします。app/Global.java
ファイルを作成してみましょう:
import play.*;
import play.libs.*;
import com.avaje.ebean.Ebean;
import models.*;
import java.util.*;
public class Global extends GlobalSettings {
@Override
public void onStart(Application app) {
// Check if the database is empty
if (User.find.findRowCount() == 0) {
Ebean.save((List) Yaml.load("initial-data.yml"));
}
}
}
これで、このコードは Play の起動時に実行されることになります。
実際のところ、このジョブは開発モードと本番モードでは異なる動作をします。開発モードの場合、Play は最初のリクエストがあるまでアプリケーションの起動を待機します。このため、このジョブは最初のリクエストと同期して実行されます。この方法では、ジョブが失敗した場合、ブラウザにエラーメッセージが表示されます。一方、本番モードでは、このジョブはアプリケーションの起動時に (
start
コマンドと同期して) 実行され、エラーが発生した場合はアプリケーションの起動を停止します。
conf
ディレクトリに initial-data.yml
を作成する必要があります。もちろん、以前にテストで使用した test-data.yml
の内容を再利用することもできます。
それでは play run
を使ってアプリケーションを実行し、ブラウザでページ http://localhost:9000 を表示してみましょう。
§ダッシュボード
今度こそ本当にダッシュボードのコーディングを始められます。
最初の画面がどのようにして表示されるのか覚えていますか? まず最初に、routes
ファイルで /
という URL が controllers.Application.index()
というアクションメソッドを起動するよう指定します。次に、このメソッドが render()
を呼び出して app/views/Application/index.scala.html
テンプレートを実行します。
これらのコンポーネントを使いつつ、タスクリストをロードして表示するコードを追加します。
app/controllers/Application.java
ファイルを開いて、プロジェクトとタスクをロードするよう index()
アクションを以下のように変更します:
package controllers;
import play.*;
import play.mvc.*;
import play.data.*;
import static play.data.Form.*;
import models.*;
import views.html.*;
public class Application extends Controller {
public static Result index() {
return ok(index.render(
Project.find.all(),
Task.find.all()
));
}
}
どのようにして render
メソッドにオブジェクトを渡すか分かりましたか? 覚えているかもしれませんが、index テンプレートは String
であるひとつの引数だけを受付けるにも関わらず、ここでは Project
のリストと Task
のリストを渡しているので、ここでこのように変更して実行しようとするとコンパイルエラーが発生します。
app/views/Application/index.scala.html
テンプレート を開いて、これらのオブジェクトを表示するよう変更します:
@(projects: List[Project], todoTasks: List[Task])
@main("Welcome to Play") {
<header>
<hgroup>
<h1>Dashboard</h1>
<h2>Tasks over all projects</h2>
</hgroup>
</header>
<article class="tasks">
@todoTasks.groupBy(_.project).map {
case (project, tasks) => {
<div class="folder" data-folder-id="@project.id">
<header>
<h3>@project.name</h3>
</header>
<ul class="list">
@tasks.map { task =>
<li data-task-id="@task.id">
<h4>@task.title</h4>
@if(task.dueDate != null) {
<time datetime="@task.dueDate">
@task.dueDate.format("MMM dd yyyy")</time>
}
@if(task.assignedTo != null && task.assignedTo.email != null) {
<span class="assignedTo">@task.assignedTo.email</span>
}
</li>
}
</ul>
</div>
}
}
</article>
}
ここでテンプレート言語 について更に詳しく読むことができます。基本的に、テンプレートを使うと java オブジェクトに型安全にアクセスすることができます。その背後では Scala を使います。(map
, case
そして =>
のような) すてきな構成要素のほとんどは Scala から来たものです。とは言え、ほんの少量のことでほとんどのことは出来てしまうので、Play のテンプレートを書くために本気で Scala の勉強をする必要はありません。
OK, それではダッシュボードをリフレッシュしてみましょう。
きれいな画面ではありませんが、動いています!
より多くのページを書いていくと、またタスクを表示する必要に迫られるでしょうし、すると各タスクを表示するコードは最終的に全て重複になってしまうでしょう。以前に学習したテンプレート合成の概念を使って、いろいろなところから呼び出すことのできる関数のようなものに、このロジックを詰め込んでしまいましょう。
関数を作成するには、関数の名前をテンプレート名とした新しいテンプレートをファイルを作成します。ここでは、tasks
フォルダを名前空間としたいので、app/views/tasks/item.scala.html
を開いてください。他のテンプレートと同様、引数を宣言するところから始めます:
@(task: Task)
<li data-task-id="@task.id">
<h4>@task.title</h4>
@if(task.dueDate != null) {
<time datetime="@task.dueDate">
@task.dueDate.format("MMM dd yyyy")</time>
}
@if(task.assignedTo != null && task.assignedTo.email != null) {
<span class="assignedTo">@task.assignedTo.email</span>
}
</li>
これで、新しいテンプレートを呼び出すよう index
テンプレート中の一部のコードを置き換えることができます:
<ul class="list">
@tasks.map { task =>
@views.html.tasks.item(task)
}
</ul>
ページをリロードして、すべてがうまく行っていることを確認してください。
§レイアウトの改善
以前に考察した通り、index.scala.html
はその内容をラップするために main.scala.html
を使います。すべてのページに共通の、正しいタイトル、リンク、プロジェクトの一覧を表示するサイドバーを含むレイアウトを提供したいので、このファイルを編集する必要があります。
注意深く見てきたならば、index.scala.html
テンプレートは、読み込んで引き渡した projects
リストを使用していないことにも気付いたかもしれません。このリストもここに表示することにしましょう。
app/views/main.scala.html
ファイルを次のように編集します:
@(projects: List[Project])(body: Html)
<html>
<head>
<title>Zentasks</title>
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
<script type="text/javascript" src="@routes.Assets.at("javascripts/jquery-1.7.1.js")"></script>
</head>
<body>
<header>
<a href="@routes.Application.index" id="logo"><span>Zen</span>tasks</a>
</header>
<nav>
<h4 class="dashboard"><a href="#/">Dashboard</a></h4>
<ul id="projects">
@projects.groupBy(_.folder).map {
case (group, projects) => {
@views.html.projects.group(group, projects)
}
}
</ul>
</nav>
<section id="main">
@body
</section>
</body>
</html>
それぞれの Task
を index ページで表示したように、プロジェクトのグループも後から再利用できるよう、専用のテンプレートで表示するようにしました。これもここで実装してしまいましょう。app/views/projects/group.scala.html
を開いてください:
@(group: String, projects: List[Project])
<li data-group="@group">
<span class="toggle"></span>
<h4 class="groupName">@group</h4>
<span class="loader">Loading</span>
<dl class="options">
<dt>Options</dt>
<dd>
<button class="newProject">New project</button>
<button class="deleteGroup">Remove group</button>
</dd>
</dl>
<ul>
@projects.map { project =>
@views.html.projects.item(project)
}
</ul>
</li>
ここで再び、個々のプロジェクトは専用のテンプレート app/views/projects/item.scala.html
を使うよう実装しました:
@(project: Project)
<li data-project="@project.id">
<a class="name" href="#">@project.name</a>
<button class="delete" href="#">Delete</button>
<span class="loader">Loading</span>
</li>
これらのテンプレートは今のところシンプルですが、早い段階からテンプレートを再利用できるようになりました。テンプレートをより複雑にすれば、これらを利用するすべての箇所がその機能性を獲得することになります。
ページをリフレッシュしてください。
わあ! コンパイルエラーです。index
テンプレートから main
テンプレートを呼び出すときに、ひとつの文字列ではなくプロジェクトのリストを渡すように更新していませんでした。すぐに app/views/index.scala.html
を編集して修正しましょう:
@(projects: List[Project], todoTasks: List[Task])
@main(projects) {
...
ここでページをリフレッシュしてすべてがうまく動いていることを確認してください。画面上部、ダッシュボードの見出しの前にプロジェクトのリストがフォルダに区切られて表示されているはずです。
§いくつかのスタイルの追加
ここまででダッシュボードの最初のバージョンはほとんど出来上がりましたが、あまりきれいではありません。これをよりきらびやかにするいくつかのスタイルを追加します。ご覧になったとおり、main.scala.html
テンプレートファイルは public/stylesheets/main.css
を読み込みます。このスタイルシートは使わないので、まず最初にすることはこれを削除することです。その代わり、LESS を使ってスタイルシートを実装します。
スタイルシートに質素な CSS を使うことを妨げるものは何もありませんが、Play framework には変数やミックスイン、関数その他を使ってスタイルシートをもっとダイナミックなやり方で定義する LESS のサポートが組み込まれています。
CSS と LESS の説明はこのチュートリアルの範囲外なので、ここでは作成済みのスタイルシートをダウンロードするだけにしておきます。このスタイルシートには、このサイトの残りを構築するために必要なスタイルがすべて含まれています。これらのファイルの tar ボールは ここ からダウンロードすることができます。これをプロジェクトのルートフォルダで展開すると、いくつかの *.less
ファイルが app/assets/stylesheets
ディレクトリに配置されます。
LESS スタイルシートは、使用される前に CSS にコンパイルされる必要があります。Play が routes や Java コード、そしてテンプレートを自動的にコンパイルするように、Play はクラスパス上に LESS ファイルを見つけるとそれらをコンパイルし、それらが変更されるたびに再度コンパイルします。さらにここでも、コンパイルエラーに遭遇するとブラウザに美しいエラー画面を表示します。
app/assets/stylesheets/main.less
ファイルが存在するため、Play が古い public/assets/stylesheets/main.css
を置き換えるようにこれをコンパイルするので、テンプレートには一切変更を加える必要がありません (古い main.css
を削除したことだけは確認してください)。
このスタイルシートはいくつかの画像にも依存します。同じようにプロジェクトのルートフォルダから展開することのできる tar ボールを ここ からダウンロードすることができます。この tar ボールは、プロジェクトに必要なすべての画像と依存する Javascript と共に public
フォルダを作成します。
トップページを更新すると、今度はスタイルの適用されたページが表示されます。
§作業内容のコミット
これでタスクダッシュボードの最初のイテレーションは完了です。いつも通り、このバージョンを git にコミットすることができます:
$ git status
$ git add .
$ git commit -m "Dashboard"
次章 に進みましょう