Documentation

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

Viewing and posting comments

The blog home page is now set, and we will continue by writing the post details page. This page will show all the comments about the current post, and will include a form to post new comments.

Creating the ‘show’ action

To display the post details page, we will need a new action method to the Application controller. Let’s call it show():

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

As you can see this action is pretty simple. We declare the id method parameter to retrieve automatically the HTTP 'id' parameter as a Long Java object. This parameter will be extracted either from the queryString, from the URL path or from the request body.

If we try to send an id HTTP parameter that is not a valid number, the id variable value will be null and play will automatically add a validation error to the errors container.

This action will display the /yabe/app/views/Application/show.html template :

#{extends 'main.html' /}
#{set title:post.title /}
 
#{display post:post, as:'full' /}

Because we’ve already written the display tag, this page is really simple to write.

Adding links to the details page

In the display tag we’ve left all links empty (using #). It’s now time to make these links pointing to the Application.show action. With play you can easily build links in a template using the @{...} notation. This syntax uses the router to ‘reverse’ the URL needed to call the specified action.

Let’s edit the /yabe/app/views/tags/display.html tag :

...
<h2 class="post-title">
    <a href="@{Application.show(_post.id)}">${_post.title}</a>
</h2>
...

You can new refresh the home page, and click on a post title to display it.

It’s great, but it lacks a link to go back to the home page. Edit the /yabe/app/views/main.html template to complete the title link:

...
<div id="title">
    <span class="about">About this blog</span>
    <h1><a href="@{Application.index()}">${blogTitle}</a></h1>
    <h2>${blogBaseline}</h2>
</div>
... 

We can now navigate between the home page and the post detail pages.

Specifying a better URL

As you can see, the post detail page URL looks like

/application/show?id=1

This is because play has used the default ‘catch all’ route.

*       /{controller}/{action}                  {controller}.{action}

We can have a better URL by specifying a custom path for the Application.show action. Edit the /yabe/conf/routes file and add this route after the first one:

GET     /posts/{id}                             Application.show

This way the id parameter will be extracted from the URL path.

Refresh the browser and check that it now uses the correct URL.

Adding some pagination

To allow users to navigate easily through posts, we will add a pagination mechanism. We’ll extends the Post class to be able to fetch previous and next post as required:

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();
}

Since we will call these methods several times during a request it could be optimized, but it’s good enough for now. Also, add the pagination links in top of the show.html template (before the #{display/} tag):

<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>

It’s better now.

Adding the comment form

Now it’s time to set up a comments form. We’ll start by adding the postComment action method to the Application controller.

public static void postComment(Long postId, String author, String content) {
    Post post = Post.findById(postId);
    post.addComment(author, content);
    show(postId);
}

As you see we just reuse the addComment() method we previously added to the Post class.

Let’s write the HTML form in the show.html template (after the #{display /} tag in fact):

<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}

You can try to post a new comment. It should just work.

Adding validation

Currently we don’t validate the form content before creating the comment. We would like to set both fields as required. We can easily use the play validation mechanism to ensure that HTTP parameters are correctly filled. Modify the postComment action to add validation annotations and check that no error occurs:

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);
}

Don’t forget to import play.data.validation.* as well.

As you see, in case of validation errors, we re-display the post detail page. We have to modify the form code to display the error message:

<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}

Note that we reuse the posted parameters to fill the HTML input values.

To make the UI feedback more pleasant for the poster, we will add a little Javascript to automatically focus the comment form in case of an error. As this script uses JQuery and JQuery Tools as support libraries, you have to include them. Download these 2 libraries to the /yabe/public/javascripts directory and modify the main.html template to include them:

...
    <script src="@{'/public/javascripts/jquery-1.3.2.min.js'}"></script>
    <script src="@{'/public/javascripts/jquery.tools.min.js'}"></script>
</head>

Now you can add this script to the show.html template (add it at the end of the page):

<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>

The comment form looks pretty cool now. We will add two more things.

First we will display a success message after a comment is successfully posted. For that, we use the flash scope that allows to dispatch messages from one action call to the next one.

Modify the postComment action to add a success message:

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);
}

and display the success message in the show.html if present (add it at the top the page):

...
#{if flash.success}
    <p class="success">${flash.success}</p>
#{/if}
 
#{display post:post, as:'full' /}
...

The last thing we will adjust in this form is the URL used for the postComment action. As always it uses the default catch all route because we didn’t define any specific route. So add this route to the application routes file:

POST    /posts/{postId}/comments                Application.postComment

That’s done. As always, commit the version to bazaar.

Go to the next part.