Documentation

You are viewing the documentation for Play 1. The documentation for Play 2 is here.

コメントの閲覧と投稿

ここまででブログトップページが設置されたので、続けて投稿の詳細ページを書いていきます。このページはその投稿に関するすべてのコメントを表示し、また、新しいコメントを投稿するためのフォームを含みます。

‘show’ アクションの作成

投稿の詳細ページを表示するためには、 Application コントローラに新しいアクションが必要です。これを show() アクションと呼びましょう:

public static void show(Long id) {
    Post post = Post.findById(id);
    render(post);
}

見ての通り、このアクションはとてもシンプルです。HTTP の id パラメータを Java の Long オブジェクトとして自動的に検索できるよう、 id メソッド引数を宣言します。このパラメータは、クエリ文字列か URL パス、またはリクエストボディから抽出されます。

妥当な数値でない HTTP パラメータ id を送信しようとした場合、変数 idnull になり、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 を追加します。このスクリプトは JQueryJQuery 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').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 にコミットしてください。

次の章 に進みましょう。