Thymeleaf and Roo AND Twitter Bootstrap

(this exercise comes as follow-up to my first experiences with Roo and Thymeleaf, read them here)

1 Why bother?

Because Bootstrap is cool – says the world. Do you need other reasons? No? Ok, let’s add Bootstrap to the project.

(You do know what is Twitter Bootstrap, right?)

2 Add it to your project

As you have a Maven project already, how about installing Bootstrap with Maven? What do you know, I found this Bootstrap Maven project on github, so you just have to add to your pom.xml a define ${bootstrap-maven.version} as 2.3.1 for the time being and the dependency itself – see project’s page.

BUT.

All fine and dandy, project built and would even run fine, but at edit time, there’s no css there for the integrated web editor. No surprise, Maven-Boostrap adds Boostrap as a JAR and I have no idea if you can tell Eclipse to use stuff from the project classpath. Insolvable problem: what should I put here???

<link rel="stylesheet" href="../../styles/standard.css" th:href="@{/styles/standard.css}" />

So I had to remove the nice maven-bootstrap integration project… the main idea of Thymeleaf was to edit nicely and also see the looks of the edited page, remember?

Let’s revert to the classical way, download the zip and extract it in your directories thymetest\src\main\webapp\. Note: you could think also about Bower package manager, but let’s not overdo it… 

You will have now:

<link href="../../bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" th:href="@{/bootstrap/css/bootstrap.min.css}">

(remember to close and reopen the html file, I noticed Eclipse won’t really update the view according to the new css).

Start your application and navigate to the main page. Did it work? Not quite – if you copied the code from Bootstrap’s website start, you clearly noticed by now that they don’t bother closing html tags. Thymeleaf does NOT like open tags, so do close them and retry. Voila!

boots1

Idea: how about using some hosted Bootstrap files to offload some traffic from your (test) site? Not that you’d have much traffic while doing this test project, but just as a fun exercise. Well, there they are: BootstrapCDN does host Boostrap files. Your CSS link will be like the following – no need for Thymeleaf tricks anymore so you can remove the th:href attribute and call the same CSS both locally and remote.

<link rel="stylesheet" media="screen" href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap.min.css"/>

Noticed the protocol-relative URL I used there? It’s meant to access the CSS with the protocol your page uses – now it’s HTTP but when you switch later to HTTPS (you will, right?) the page code will stay the same while loading the CSS also via HTTPS. Caveat: IE7 and 8 do some stuff wrong (but that’s no news, right).

Now the tables don’t look that nice as before, do they? And hey, they have no pagination at all!

thyme8

3 Table pagination

…are we requesting the data paginated at all? Certainly not! Check your blocks.html for the menu fragment – it indeed requests the whole people list. The original blog post uses the Page feature of the Spring Data, very nice. Roo however doesn’t make use of that 😦 and because the aim of this exercise was to keep Roo, let’s do it the (really) hard way. That’s how we learn stuff, right?

A look into the controller aspect shows what we should provide: simple request parameters.

    @RequestMapping(produces = "text/html")
    public String PersonController.list(@RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, Model uiModel) {
        if (page != null || size != null) {
            int sizeNo = size == null ? 10 : size.intValue();
            final int firstResult = page == null ? 0 : (page.intValue() - 1) * sizeNo;
            uiModel.addAttribute("people", personService.findPersonEntries(firstResult, sizeNo));
            float nrOfPages = (float) personService.countAllPeople() / sizeNo;
            uiModel.addAttribute("maxPages", (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages));
        } else {
            uiModel.addAttribute("people", personService.findAllPeople());
        }
        return "people/list";
    }

hmm… how do I rewrite this menu entry from the Roo-generated JSPX menu:

<menu:item url="/people?page=1&amp;size=${empty param.size ? 10 : param.size}"...

to Thymeleaf? Where’s that user guide again

<li><a href="people/list.html" th:href="@{/people(page=1,size=${#strings.isEmpty(param.size) ? 10 : param.size})}">People list</a></li>

Now we show the first 10 records, it’s just it doesn’t have yet the next buttons, and page numbers are missing too. Well, let’s add them by modifying the pagination bar of the original blog post. The “maxpages” parameter the Roo-generated controller is adding will come in handy, but the Thymeleaf template I ended with is anything else than nice to see.

Moral of the story: Roo has its limits and we’re kind of already hitting them when trying to stick to its rather basic generated code. Anyway, here’s the pagination bar code:

    <div th:fragment="paginationbar">
        <div class="pagination pagination-centered"
            th:with="actualSize = ${#strings.isEmpty(param.size) ? 10 : param.size}, actualPage = ${param.page == null ? 1 : T(java.lang.Integer).parseInt(param.page)}">
            <ul>
                <li th:class="${actualPage eq 1}? 'disabled' : ''"><span th:if="${actualPage eq 1}">&larr;
                        First</span> <a th:if="${actualPage gt 1}" th:href="@{/people(page=1,size=${actualSize})}"
                    title="Go to first page">&larr; First</a></li>
                <li th:class="${actualPage eq 1}? 'disabled' : ''"><span th:if="${actualPage eq 1}">&laquo;</span>
                    <a th:if="${actualPage gt 1}" th:href="@{/people(page=${actualPage-1},size=${actualSize})}"
                    title="Go to previous page">&laquo;</a></li>
                <li th:class="${actualPage ge maxPages}? 'disabled' : ''"><span th:if="${actualPage ge maxPages}">&raquo;</span>
                    <a th:if="${actualPage lt maxPages}" th:href="@{/people(page=${actualPage+1},size=${actualSize})}"
                    title="Go to next page">&raquo;</a></li>
                <li th:class="${actualPage ge maxPages}? 'disabled' : ''"><span th:if="${actualPage ge maxPages}">Last
                        &rarr;</span> <a th:if="${actualPage lt maxPages}"
                    th:href="@{/people(page=${maxPages},size=${actualSize})}" title="Go to last page">Last &rarr;</a></li>
            </ul>
        </div>
    </div>

There are a few things to underline:

  • notice the two Thymeleaf-specific variable assignments in <div th:with=… – otherwise the code would have grown unreadable
  • even now, it’s not exactly Miss Brevity…
  • you’ll notice a <span> and an <a> for each element – that’s because anchors cannot be disabled via CSS, thus they are shown alternatively when needed
  • I didn’t want to enumerate all page numbers between arrows like the original post had, I cannot imagine how that would have looked with 100 pages, and to code around that would have been… life’s too short
  • I noticed my past code was showing the list index in the first column of the persons list, instead of showing the real person id. Can you find yourself where to insert this snippet of code? <td th:text=”${guy.id}”>

4 Givin’ the page a theme

For the coronation of the day, let’s add to it a Bootstrap theme – I chose the Geo theme for its genuine 90’s look, timeless as they call it. You will need to apply some class styles to your elements so let’s start Firebug for the theme site to find out who’s who. Once styles identified, download the theme CSS, replace the CSS directive with a usual @ Thymeleaf anchor (see previous articles) in your templates, then try your test site with Firebug. Firebug will show you all the 404 errors for missing resources, so download also the needed GIFs and place them in a new /img directory (if there’s a ZIP with the whole theme, sorry I wasn’t able to find it because of the temporary blindness this special theme is inducing).

And finally… here you go, timeless!

thyme10

5 Now some code

This code didn’t do much to actually show much else than Twitter CSS, right? The missing part can be seen here

6 Useful links

Twitter Bootstrap goto address http://twitter.github.io/bootstrap/getting-started.html

  1. Yuan Ji’s blog post which started it all http://www.jiwhiz.com/post/2013/2/Implement_Bootstrap_Pagination_With_SpringData_And_Thymeleaf
  2. Bootstrap-Maven – add Bootstrap to your project as a Maven dependency https://github.com/efsavage/Bootstrap-Maven
  3. NetDNA hosted Bootstrap files http://www.bootstrapcdn.com/
  4. Geo for Bootstrap – the only theme you’ll ever need (hope not) http://divshot.github.io/geo-bootstrap/
Advertisements

One thought on “Thymeleaf and Roo AND Twitter Bootstrap

  1. Pingback: Thymeleaf and Roo AND Twitter Bootstrap – the missing part | 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