コメントの閲覧と投稿
ここまででブログトップページが設置されたので、続けて投稿の詳細ページを書いていきます。このページはその投稿に関するすべてのコメントを表示し、また、新しいコメントを投稿するためのフォームを含みます。
‘show’ アクションの作成
投稿の詳細ページを表示するためには、 Application コントローラに新しいアクションが必要です。これを show() アクションと呼びましょう:
public static void show(Long id) {
Post post = Post.findById(id);
render(post);
}
見ての通り、このアクションはとてもシンプルです。HTTP の id パラメータを Java の Long オブジェクトとして自動的に検索できるよう、 id メソッド引数を宣言します。このパラメータは、クエリ文字列か URL パス、またはリクエストボディから抽出されます。
妥当な数値でない HTTP パラメータ id を送信しようとした場合、変数 id は null になり、Play は自動的に errors コンテナにバリデーションエラーを追加します。
このアクションは /yabe/app/views/Application/show.html テンプレートを表示します:
#{extends 'main.html' /}
#{set title:post.title /}
#{display post:post, as:'full' /}
すでに display タグを作成してあるので、このページはとてもシンプルに書くことができます。
詳細ページへのリンクの追加
display タグのすべてのリンクは ( # を使うことで) 空のままにしてあります。ここで、これらのリンクが Application.show アクションを指すようにします。Play ではテンプレートにおいて @{...} 識別子 を使うことで容易にリンクを構築することができます。この構文は、ルータを使って指定したアクションを呼び出すために必要な URL を ‘リバース’ します。
/yabe/app/views/tags/display.html タグを編集しましょう:
...
<h2 class="post-title">
<a href="@{Application.show(_post.id)}">${_post.title}</a>
</h2>
...
トップページを更新したら、投稿のタイトルをクリックして詳細ページを表示してください。
よく出来ていますが、トップページへ戻るためのリンクがありません。 /yabe/app/views/main.html テンプレートを編集して、タイトルへのリンクを完成させます:
...
<div id="title">
<span class="about">About this blog</span>
<h1><a href="@{Application.index()}">${blogTitle}</a></h1>
<h2>${blogBaseline}</h2>
</div>
...
これで、トップページと投稿詳細ページ間を遷移することができるようになりました。
より良い URL の指定
見ての通り、投稿詳細画面の URL は以下のようになっています:
/application/show?id=1
これは Play が ‘catch all’ ルートを使ったためです。
* /{controller}/{action} {controller}.{action}
Application.show アクションのためのカスタムパスを指定することで、より良い URL にすることができます。 /yabe/conf/routes ファイルを編集して、先頭行の後に以下のルートを追加してください:
GET /posts/{id} Application.show
この方法では、 id パラメータは URL パスから抽出されます。URI パターンについては、 ルートファイル構文 に関するマニュアルページで、より詳しく読むことができます。
ブラウザを更新して、今度は適切な URL が使用されることを確認してください。
ページングの追加
ユーザが投稿を通じて容易に遷移できるようにするために、ページング機能を追加します。必要に応じて前や後の投稿を取って来られるよう Post クラスを拡張します:
public Post previous() {
return Post.find("postedAt < ? order by postedAt desc", postedAt).first();
}
public Post next() {
return Post.find("postedAt > ? order by postedAt asc", postedAt).first();
}
あるリクエストにおいて、これらのメソッドを何度か呼ぶことになるため、これらを最適化するのもいいのですが、今のところはこれで充分です。さて、 show.html テンプレートの上部 ( #{display/} タグの前) にページングのリンクを追加してください:
<ul id="pagination">
#{if post.previous()}
<li id="previous">
<a href="@{Application.show(post.previous().id)}">
${post.previous().title}
</a>
</li>
#{/if}
#{if post.next()}
<li id="next">
<a href="@{Application.show(post.next().id)}">
${post.next().title}
</a>
</li>
#{/if}
</ul>
ぐっと良くなりました。
コメントフォームの追加
いよいよコメントフォームを設置します。Application コントローラに postComment アクションメソッドを追加することから始めましょう。
public static void postComment(Long postId, String author, String content) {
Post post = Post.findById(postId);
post.addComment(author, content);
show(postId);
}
見ての通り、以前に Post クラスに追加した addComment() メソッドを再利用しただけです。
show.html テンプレート ( #{display /} タグのすぐ後) に HTML フォームを書きましょう:
<h3>Post a comment</h3>
#{form @Application.postComment(post.id)}
<p>
<label for="author">Your name: </label>
<input type="text" name="author" id="author" />
</p>
<p>
<label for="content">Your message: </label>
<textarea name="content" id="content"></textarea>
</p>
<p>
<input type="submit" value="Submit your comment" />
</p>
#{/form}
これで、新しいコメントの投稿を試すことができるようになりました。ばっちり動くはずです。
バリデーションの追加
現状、コメントを作成する前にフォームの内容の妥当性を確認していません。いずれのフィールドも入力必須にしたいと思います。Play のバリデーション機能を使って、HTTP パラメータが適切に入力されていることを簡単に保証することができます。 postComment アクションに @Required アノテーションを追加して、エラーが発生していないことを確認するよう変更します:
public static void postComment(Long postId, @Required String author, @Required String content) {
Post post = Post.findById(postId);
if (validation.hasErrors()) {
render("Application/show.html", post);
}
post.addComment(author, content);
show(postId);
}
play.data.validation.* をインポートすることも 忘れないでください 。
見ての通り、バリデーションエラーが発生した場合、投稿詳細画面を再度表示します。フォームのコードを、エラーメッセージを表示するよう変更しなければなりません:
<h3>Post a comment</h3>
#{form @Application.postComment(post.id)}
#{ifErrors}
<p class="error">
All fields are required!
</p>
#{/ifErrors}
<p>
<label for="author">Your name: </label>
<input type="text" name="author" id="author" value="${params.author}" />
</p>
<p>
<label for="content">Your message: </label>
<textarea name="content" id="content">${params.content}</textarea>
</p>
<p>
<input type="submit" value="Submit your comment" />
</p>
#{/form}
HTML input の値を設定するために、投稿されたパラメータを再利用していることに注意してください。
投稿者により親切な UI フィードバックを提供するために、エラーが発生した場合には、自動的にコメントフォームにフォーカスをセットする、ちょっとした JavaScript を追加します。このスクリプトは JQuery と JQuery Tools を補助ライブラリとして使用するので、これらを include する必要があります。これら 2 つのライブラリを /yabe/public/javascripts にダウンロードしたら、これらを include するよう main.html を変更してください:
...
<script src="@{'/public/javascripts/jquery-1.3.2.min.js'}"></script>
<script src="@{'/public/javascripts/jquery.tools.min.js'}"></script>
</head>
これで、 show.html テンプレートに以下のスクリプトを追加することができます (ページの最後に追加してください):
<script type="text/javascript" charset="utf-8">
$(function() {
// Expose the form
$('form').click(function() {
$('form').expose({api: true}).load();
});
// If there is an error, focus to form
if($('form .error').size()) {
$('form').expose({api: true, loadSpeed: 0}).load();
$('form input[type=text]').get(0).focus();
}
});
</script>
これでコメントフォームはずいぶんかっこよくなりました。あと 2 つ追加しましょう。
最初に、コメントが正常に投稿されたあとに成功メッセージを表示しましょう。このためには、あるアクションから次のアクションにメッセージを引き渡すことができる flash スコープを使います。
postComment アクションを変更して、成功メッセージを追加してください:
public static void postComment(Long postId, @Required String author, @Required String content) {
Post post = Post.findById(postId);
if(validation.hasErrors()) {
render("Application/show.html", post);
}
post.addComment(author, content);
flash.success("Thanks for posting %s", author);
show(postId);
}
そして、もし成功メッセージが存在すれば、これを show.html に表示します (画面上部に追加してください):
...
#{if flash.success}
<p class="success">${flash.success}</p>
#{/if}
#{display post:post, as:'full' /}
...
このフォームについて最後に調整するのは、 postComment アクションに使用する URL です。特別なルート指定を何も定義していないので、このアクションは、いつも通りデフォルトの catch all ルートを使用します。アプリケーション routes ファイルに、以下のルートを追加してください:
POST /posts/{postId}/comments Application.postComment
これで完了です。いつも通り、このバージョンを bazaar にコミットしてください。
次: キャプチャの設定