My second step with Thymeleaf (migrating JSPX generated by Spring Roo)

((this is the second half of a two-part exercise, the first can be read here)

4         Navigation

4.1       Where’s the menu?

Right, the original application had a menu as well. What’s wrong with such a fragment to be added to blocks.html?

<div th:fragment="menu">
    <p>Please select an option</p>
    <ol>
        <li><a href="/" th:href="@{/manythings}">People list</a></li>
    </ol>
</div>

Reference it in index.html and see what happens when you click on the link.

        <tr>
            <td><div th:include="blocks :: menu">Menu</div></td>
            <td>
                <h2 th:text="#{welcome_titlepane(#{application_name})}">Welcome dear</h2>

Well, nothing happens – you get an empty page. What about the console? Again nothing at all – you will have to open your log4j.properties file and enable shortly debug logging.

log4j.rootLogger=debug, stdout

restart your application and try again clicking on the link. The console now says this:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping 
- Did not find handler method for [/manythings]

4.2       Understanding the redirect

Uh, could be. I didn’t write any handler method at all, did you? But we created a Roo MVC application, so this must be Roo doing it for us – of course in the controller (indeed, the C in MVC). The root mapping is done to “/people”, notice the class annotation in the PersonController.java. But how about the other mappings? They are in the aspects Roo created for us, namely in PersonController_Roo_Controller.aj To find it just open your Java file, go to the Cross References view and click on the list(…) function, otherwise you won’t see the file in Package Explorer (and rightly so, as it’s a generated file). The most important code right now is:

@RequestMapping(produces = "text/html")
public String PersonController.list(@RequestParam(value = "page", required = false) Integer page, 
    @RequestParam(value = "size", required = false) Integer size, Model uiModel) {

This means, the “default“ GET request to /people will return the people/list template – which teaches us two things:

a)      The menu anchor should be <a href=”/” th:href=”@{/people}”>People list</a> (this is what the “default” mapping expects for listing the entries

b)      We need a list.html template in a new thymeleaf/people directory (this is where the Roo generated controller will take us)

Let’s proceed with these changes. After fixing the menu entry in the blocks.html, create the “people” folder under “thymeleaf” and an empty list.html in it. Because we want all our pages to look similar, copy the index.html contents, but change the table body like this:

<tbody>
    <tr>
        <td>
            <h2 th:text="#{entity_list_all(#{label_com_thymetest_domain_person_plural})}">
                Listing persons</h2>
        </td>
    </tr>
</tbody>

Oops, the web page editor of your STS is not looking that nice anymore. That’s because the HTML link to the CSS file is not valid anymore – we’re one directory level deeper now. Change it like this – the resource link for runtime is still the same:

href="../../../styles/standard.css"

Run it and don’t be surprised: it’s not actually LISTING the entries, but notice the changed text resources?

4.3       Now the real list

Here’s the list of persons you could place directly below the “Listing persons” header:

<table id="display-data">
    <tbody>
        <tr>
            <th th:text="#{label_com_thymetest_domain_person_id}">Id</th>
            <th th:text="#{label_com_thymetest_domain_person_fullname}">Full Name</th>
            <th th:text="#{label_com_thymetest_domain_person_socialid}">Social Id</th>
            <th th:text="#{label_com_thymetest_domain_person_email}">Email</th>
        </tr>
        <tr th:each="guy : ${people}" th:object="${guy}">
            <td th:text="${guyStat.Count}">1</td>
            <td th:text="*{fullName}">Geppetto Au</td>
            <td th:text="*{socialId}">2349876234</td>
            <td th:text="*{email}">get@dis.rd</td>
        </tr>
    </tbody>
</table>

If you really read the Thymeleaf guide you’ll immediately understand its points:

a)      It’s reusing the text resources Roo already created in application.properties

b)      Iterates over the list of person objects called “people” – look again at the PersonController_Roo_Controller.java:list(…) and notice how the “people” attribute gets populated and added

c)       Defines the iteration object called “guy”

d)      …and decides to use it also as reference object in order to enable the *- asterisk syntax

e)      Uses the automatically generated by Thymeleaf object “guyStats” (it added the Stats-suffix) for getting iteration information, in this case the row number

The web page editor will list however only one row, so let’s add another two row as Thymeleaf recommends, with the remove attribute (otherwise you’ll see them also at runtime…). You know where to add these two rows in the HTML table, right?

<tr th:remove="all">
    <td>2</td>
    <td>Jiminy Cricket</td>
    <td>c-2394876234897</td>
    <td>lilhelper@fairy.org</td>
</tr>
<tr th:remove="all">
    <td>3</td>
    <td>Pino</td>
    <td>------</td>
    <td>pino@dis.rd</td>
</tr>

Run it and notice that the CSS table styling is (still) recognized, really nice. Hopefully you remembered to create some records when you still had the Tiles application running, right? If not you’ll have to get your hands dirty with SQL, otherwise you’ll have a depressing empty list to look at.

5         Time for actions

5.1       Show record

So we want to be able to show each person, right? Let’s add a column to the table where users can click to (hopefully) show the respective entry. In the data-containing row you will insert a new column, say in the second place, with the following contents:

<td>
    <a href="/thymetest" th:href="@{'/people/'+${guy.id}}"> 
        <img src="../../../images/show.png" alt="Show"        
            th:src="@{/images/show.png}" th:alt="#{button_show}" />
    </a>
</td>

The web page editor will look understandably broken until you add new columns also to the header row and to the two fake data rows. Do that asap.

What is this new column doing? It’s adding a clickable image from the resources. And where is the click taking you? Well, as the Roo generated aspect controller says… (now you go again to it and look at the show(…) method)… to a people/show template. By seeing its request mapping you’ll understand why that particular th:href anchor was used.

thyme4

Let’s build the new show.html template, again starting from index.html and inserting the following code which uses the asterisk notation to access the “person” object (you know, the one placed by the controller…)

<h3 th:text="#{entity_show(#{label_com_thymetest_domain_person})}">Show person</h3>
<table id="fields-person" th:object="${person}">
    <tbody>
        <tr>
            <th th:text="#{label_com_thymetest_domain_person_fullname}">full name</th>
            <td><p th:text="*{fullName}">name</p></td>
        </tr>
        <tr>
            <th th:text="#{label_com_thymetest_domain_person_socialid}">social id</th>
            <td><p th:text="*{socialId}">social Id</p></td>
        </tr>
        <tr>
            <th th:text="#{label_com_thymetest_domain_person_email}">and email</th>
            <td><p th:text="*{email}">email</p></td>
        </tr>
    </tbody>
</table>

Nothing special, really. Just run it and you’ll have the respective person nicely shown.

thyme5

5.2       Create person

Let’s get also a form running, shall we? That’ll work with a link in the left column menu – like the original JSPX application right. But what link, I may ask? Let’s go back to the Roo controller and check its mapping… to a HTTP POST. See it here:

@RequestMapping(method = RequestMethod.POST, produces = "text/html")
public String PersonController.create(@Valid Person person, BindingResult bindingResult, 
    Model uiModel, HttpServletRequest httpServletRequest) {
    if (bindingResult.hasErrors()) {
        populateEditForm(uiModel, person);
        return "people/create";
    }

That was obvious. Just as obvious is the fact that we’ll need a template called create.html in the thymeleaf/people folder. Obviously, again created from the good ole’ index.html for keeping similar looks. But… how about reusing the above create table? We could just replace the texts with input fields, and disable them when we’re in a predefined “show” mode. This should look the same, right? Then we can even place the common table fragment in the blocks.html and include it in our new create.html.

Here’s an idea for a table row:

<tr>
    <th th:text="#{label_com_thymetest_domain_person_socialid}">social id</th>
    <td><input type="text" id="socialId" th:field="*{socialId}" value="New social Id"
        th:class="${#fields.hasErrors('socialId')}? 'errors'" 
        th:readonly="${!#strings.isEmpty(show)}" /></td>
</tr>

I know there’s much stuff here, let’s have a look:

–          Table header – we know that already don’t we

–          Regular input field, checked – notice how it’s mapped to the Thymeleaf form field

–          Validation will change its CSS class (thus, the color) if it has an error – for this the special Thymeleaf function will check whether this particular field has any input error

–          And finally, if a special variable called “show” is not around, it will be writable

Huh, but who’s going to tell us whether we’re in show mode or not? That variable called “show”, of course. Here’s how we will include the table fragment in our create.html file, and at the same time will pass the freshly created “show” variable.

<div th:include="blocks :: header">Header</div>
<div id="container-show">
    <table>
        <tbody>
            <tr>
                <td><div th:include="blocks :: menu">Menu</div></td>
                <td><div th:include="blocks :: person">Person</div></td>
            </tr>
        </tbody>
    </table>
</div>

I called the table fragment “person”, it’s your task to complete it. Remember that you will need to include it also in the show.html, but this time also with a freshly created “show” variable to make the fields read-only. Remove the table from the show.html and use this include line with the variable:

<td th:with="show='true'"><div th:include="blocks :: person">Person</div></td>

Run the application now. Can you create new records?

Of course not. We placed the input fields, great, but there’s no form to send them down… Go to your nice table fragment in blocks.html and surround the table with something like this:

<div th:fragment="person">
    <h3 th:text="${show} ? #{entity_show(${typeName})} : #{entity_create(${typeName})}">
        Create/show person</h3>
    <form action="#" th:action="@{/people}" method="post" th:object="${person}" 
        th:method="’post’">
        <table id="fields-person">
            <tbody>....

The new thing I slipped in is the dynamic title text, depending on the operation (the “show” variable). Otherwise it’s just regular form submitting, while also telling Thymeleaf what object it should actually submit (guess where I got the expected name? Right, by looking at the Roo controller code)

Are we done yet? Not quite – there’s the need for a submit button, which will be shown only when we’re in “create” mode (not “show”, that is). See it below.

<tr>
    <td colspan="2"><input type="submit" th:if="${#strings.isEmpty(show)}" 
        th:value="#{button_save}" value="Submit" />
        <a href="people/list.html" th:href="@{/people}">
            <input type="button" th:value="#{button_cancel}" value="Cancel" />
        </a>
    </td>
</tr>

Although not necessary, I put also a “cancel” button with a nice anchor. The only still missing thing is the… create link in the left-side menu:

<li><a href="people/create.html" th:href="@{/people(form)}">Create new</a></li>

Indeed, the href format should match as always what is our Roo-generated controller expecting:

@RequestMapping(params = "form", produces = "text/html")
public String PersonController.createForm(Model uiModel) {

If you enter some bogus values in the entry creation dialog, you’ll also see validation at work: you will land back in the creation dialog with the wrong fields in red. The CSS formatting needs some tinkering, but that’s why you have Firebug, right?

thyme6

5.3       Error messages

So we have the field showing its contents in red and bold when something goes wrong. The framework offers however the possibility to show the whole error messages, why not using it then? I will add another table row, between the fields and the buttons, which will show for each field the error message in red. Of course, only when there’s an error message.

<tr>
    <td colspan="2"><ul th:if="${#fields.hasErrors('*')}">
            <li th:if="${#fields.hasErrors('fullName')}" th:with="err=${#fields.errors('fullName')}"
                th:text="#{label_com_thymetest_domain_person_fullname} + ': ' + 
                ${#strings.arrayJoin(err,', ')}">Full name is incorrect!</li>
            <li th:if="${#fields.hasErrors('socialId')}" th:with="err=${#fields.errors('socialId')}"
                th:text="#{label_com_thymetest_domain_person_socialid} + ': ' + 
                ${#strings.arrayJoin(err,', ')}">Social ID is incorrect!</li>
            <li th:if="${#fields.hasErrors('email')}" th:with="err=${#fields.errors('email')}"
                th:text="#{label_com_thymetest_domain_person_email} + ': ' + 
                ${#strings.arrayJoin(err,', ')}">Email is incorrect!</li>
        </ul></td>
</tr>

What’s new and noteworthy here?

–          The list items are shown only when their respective fields really have errors, for which the Thymeleaf function #fields.hasErrors(‘field name’) was used

–          Each field could have more than one error message, that’s why the function #fields.errors(‘field name’) returns an array

–          The array is converted to a comma-space separated string for nicer display with the function #strings.arrayJoin(array, separator)

You could if you want hide the whole row when #fields.hasErrors(‘*’) is false…

thyme7

5.4       Update person

This is actually left as exercise. Your tasks will be:

–          add to the person list table a new action button,

–          check what mapping it should have,

–          create a new template page and reuse in it the person fragment

–          but modify it slightly to block you from renaming an existing person

That wasn’t difficult anymore, was it? You will soon notice however that it’s not exactly eqasy to visually tell the difference between read-only fields and editable. That’s easily done in the CSS file:

input[readonly] {
    background-color:#F0F0F0 !important;
    color:#505050 !important;
}

PS: is your application really updating, or adding a new row each time you try to update? Hint: you’re losing the id and version of the entity passed over the form. The solution is in the demo project, see link below.

6         Useful links

  1. STS Spring Tool Suite downloads: http://www.springsource.org/downloads/sts-ggts
  2. Thymeleaf documentation: http://www.thymeleaf.org/documentation.html
  3. Quick intro on Tiles vs. Thymeleaf: http://blog.springsource.org/2012/10/30/spring-mvc-from-jsp-and-tiles-to-thymeleaf/
  4. HTML5 Spring Roo Thymeleaf demo (you can read code, right?): http://demo.arkadias.cloudfoundry.com/
  5. Spring and Thymeleaf with JavaConfig http://krams915.blogspot.ch/2012/12/spring-and-thymeleaf-with-javaconfig.html
  6. Recommended reading, focus chapter 5 for Spring Security integration: http://doanduyhai.wordpress.com/2012/04/14/spring-mvc-part-iii-thymeleaf-integration/
Advertisements

2 thoughts on “My second step with Thymeleaf (migrating JSPX generated by Spring Roo)

  1. Pingback: My first step with Thymeleaf (migrating JSPX generated by Spring Roo) | Trying things

  2. Pingback: Putting some masonry and infinite scrolling on top | Trying things

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s