Material design with WebJars

Our application is already great but it seriously leaves something to be desired in terms of aesthetics. You may have heard of material design. It is Google's take on flat design.

We will use Materialize (http://materializecss.com), a great looking responsive CSS and JavaScript library, just like Bootstrap.

Material design with WebJars

We talked a bit about WebJars in Chapter 1, Setting Up a Spring Web Application in No Time; we will now get to use them. Add jQuery and Materialize CSS to our dependencies:

compile 'org.webjars:materializecss:0.96.0'
compile 'org.webjars:jquery:2.1.4'

The way a WebJar is organized is completely standardized. You will find the JS and CSS files of any library in /webjars/{lib}/{version}/*.js.

For instance, to add jQuery to our page, the following to a web page:

<script src="/webjars/jquery/2.1.4/jquery.js"></script>

Let's modify our controller so that it gives us a list of all tweet objects instead of simple text:

package masterSpringMvc.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.twitter.api.SearchResults;
import org.springframework.social.twitter.api.Tweet;
import org.springframework.social.twitter.api.Twitter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
public class TweetController {

    @Autowired
    private Twitter twitter;

    @RequestMapping("/")
    public String hello(@RequestParam(defaultValue = "masterSpringMVC4") String search, Model model) {
        SearchResults searchResults = twitter.searchOperations().search(search);
        List<Tweet> tweets = searchResults.getTweets();
        model.addAttribute("tweets", tweets);
        model.addAttribute("search", search);
        return "resultPage";
    }
}

Let's include materialize CSS in our view:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
    <meta charset="UTF-8"/>
    <title>Hello twitter</title>

    <link href="/webjars/materializecss/0.96.0/css/materialize.css" type="text/css" rel="stylesheet" media="screen,projection"/>
</head>
<body>
<div class="row">

    <h2 class="indigo-text center" th:text="|Tweet results for ${search}|">Tweets</h2>

    <ul class="collection">
        <li class="collection-item avatar" th:each="tweet : ${tweets}">
            <img th:src="${tweet.user.profileImageUrl}" alt="" class="circle"/>
            <span class="title" th:text="${tweet.user.name}">Username</span>
            <p th:text="${tweet.text}">Tweet message</p>
        </li>
    </ul>

</div>

<script src="/webjars/jquery/2.1.4/jquery.js"></script>
<script src="/webjars/materializecss/0.96.0/js/materialize.js"></script>
</body>
</html>

The result already looks way better!

Material design with WebJars

Using layouts

The last thing we want to do is to put the reusable chunks of our UI into templates. To do this, we will use the thymeleaf-layout-dialect dependency, which is included in the spring-boot-starter-thymeleaf dependency of our project.

We will create a new file called default.html in src/main/resources/templates/layout. It will contain the code we will repeat from page to page:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"/>
    <title>Default title</title>

    <link href="/webjars/materializecss/0.96.0/css/materialize.css" type="text/css" rel="stylesheet" media="screen,projection"/>
</head>
<body>

<section layout:fragment="content">
    <p>Page content goes here</p>
</section>

<script src="/webjars/jquery/2.1.4/jquery.js"></script>
<script src="/webjars/materializecss/0.96.0/js/materialize.js"></script>
</body>
</html>

We will now modify the resultPage.html file so it uses the layout, which will simplify its contents:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/default">
<head lang="en">
    <title>Hello twitter</title>
</head>
<body>
<div class="row" layout:fragment="content">

    <h2 class="indigo-text center" th:text="|Tweet results for ${search}|">Tweets</h2>

    <ul class="collection">
        <li class="collection-item avatar" th:each="tweet : ${tweets}">
            <img th:src="${tweet.user.profileImageUrl}" alt="" class="circle"/>
            <span class="title" th:text="${tweet.user.name}">Username</span>

            <p th:text="${tweet.text}">Tweet message</p>
        </li>
    </ul>
</div>
</body>
</html>

The layout:decorator="layout/default" will indicate where our layout can be found. We can then inject content into the different layout:fragment sections of the layout. Note that each template are valid HTML files. You can also override the title very easily.

Navigation

We have a nice little tweet display application, but how are our users supposed to figure out that they need to supply a "search" request parameter?

It would be nice if we added a little form to our application.

Let's do something like this:

Navigation

First, we need to modify our TweetController to add a second view to our application. The search page will be available directly at the root of our application and the result page when hit enter in the search field:

@Controller
public class TweetController {

    @Autowired
    private Twitter twitter;

    @RequestMapping("/")
    public String home() {
        return "searchPage";
    }

    @RequestMapping("/result")
    public String hello(@RequestParam(defaultValue = "masterSpringMVC4") String search, Model model) {
        SearchResults searchResults = twitter.searchOperations().search(search);
        List<Tweet> tweets = searchResults.getTweets();
        model.addAttribute("tweets", tweets);
        model.addAttribute("search", search);
        return "resultPage";
    }
}

We will add another page to the templates folder called the searchPage.html file. It will contain a simple form, which will pass the search term to the result page via the get method:

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/default">
<head lang="en">
    <title>Search</title>
</head>
<body>

<div class="row" layout:fragment="content">

    <h4 class="indigo-text center">Please enter a search term</h4>

    <form action="/result" method="get" class="col s12">
        <div class="row center">
            <div class="input-field col s6 offset-s3">
                <i class="mdi-action-search prefix"></i>
                <input id="search" name="search" type="text" class="validate"/>
                <label for="search">Search</label>
            </div>
        </div>
    </form>
</div>

</body>
</html>

This is very simple HTML and it works perfectly. You can try it now.

What if we wanted to disallow some search result? Let's say we want to display an error message if the user types in struts.

The best way to achieve this would be to modify the form to post the data. In the controller, we can then intercept what is posted and implement this business rule accordingly.

First, we need to change the form in the searchPage, which is as follows:

<form action="/result" method="get" class="col s12">

Now, we change the form to this:

<form action="/postSearch" method="post" class="col s12">

We also need to handle this post on the server. Add this method to the TweetController:

@RequestMapping(value = "/postSearch", method = RequestMethod.POST)
public String postSearch(HttpServletRequest request,
    RedirectAttributes redirectAttributes) {
        String search = request.getParameter("search");
        redirectAttributes.addAttribute("search", search);
        return "redirect:result";
}

There are several novelties here:

  • In the request mapping annotation, we specify the HTTP method we want to handle, that is, POST.
  • We inject two attributes directly as method parameters. They are the request and RedirectAttributes.
  • We retrieve the value posted on the request and pass it on to the next view.
  • Instead of returning the name of the view, we make a redirection to a URL.

The RedirectAttributes is a Spring model that will be specifically used to propagate values in a redirect scenario.

Note

Redirect/Forward are classical options in the context of a Java web application. They both change the view that is displayed on the user's browser. The difference is that Redirect will send a 302 header that will trigger navigation inside the browser, whereas Forward will not cause the URL to change. In Spring MVC, you can use either option simply by prefixing your method return strings with redirect: or forward:. In both cases, the string you return will not be resolved to a view like we saw earlier, but will instead trigger navigation to a specific URL.

The preceding example is a bit contrived, and we will see smarter form handling in the next chapter. If you put a breakpoint in the postSearch method, you will see that it will be called right after a post in our form.

So what about the error message?

Let's change the postSearch method:

@RequestMapping(value = "/postSearch", method = RequestMethod.POST)
public String postSearch(HttpServletRequest request,
    RedirectAttributes redirectAttributes) {
        String search = request.getParameter("search");
        if (search.toLowerCase().contains("struts")) {
                redirectAttributes.addFlashAttribute("error", "Try using spring instead!");
                return "redirect:/";
        }
        redirectAttributes.addAttribute("search", search);
        return "redirect:result";
}

If the user's search terms contain "struts", we redirect them to the searchPage and add a little error message using flash attributes.

These special kinds of attributes live only for the time of a request and will disappear when the page is refreshed. This is very useful when we use the POST-REDIRECT-GET pattern, as we just did.

We will need to display this message in the searchPage result:

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorator="layout/default">
<head lang="en">
    <title>Search</title>
</head>
<body>

<div class="row" layout:fragment="content">

    <h4 class="indigo-text center">Please enter a search term</h4>

    <div class="col s6 offset-s3">
        <div id="errorMessage" class="card-panel red lighten-2" th:if="${error}">
            <span class="card-title" th:text="${error}"></span>
        </div>

        <form action="/postSearch" method="post" class="col s12">
            <div class="row center">
                <div class="input-field">
                    <i class="mdi-action-search prefix"></i>
                    <input id="search" name="search" type="text" class="validate"/>
                    <label for="search">Search</label>
                </div>
            </div>
        </form>
    </div>
</div>

</body>
</html>

Now, if users try to search for "struts2" tweets, they will get a useful and appropriate answer:

Navigation
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.24.30