viernes, noviembre 25, 2011

Your big daddy's got no place to stay, Bad communication, I feel like the president of the USA (Mr. Bad Guy - Freddie Mercury)


In current post I am going to explain how to implement nice web form using HTML 5 and CSS 3 effects, and how can be integrated into Spring MVC web application.

First thing to do is create a Spring MVC skeleton project using STS Spring Templates. In fact you can use any other method but I prefer to use a standard template.

Next step is create an HTML 5 web structure, but exists some online applications that can do this for you, hence I am going to HTML 5 Boilerplate site, and create a custom HTML 5 template.




Then open downloaded zip and copy required structure into previous generated project. Css and Js folders into resources directory, and rename index.html to index.jsp and copy it to views directory.

Now open servlet-context and add mvc:resources tag for serving static resources.

<resources mapping="/css/**" location="/css/" />
<resources mapping="/js/libs/**" location="/js/libs/" />

If you deploy application a white screen should be showed but without any client/server error.

Let's start creating next html form.



Now open index.jsp and remove div with id main and create next form:

<form action="#">
<h1>Get In Touch With Us ...</h1>
<fieldset id="user-details">
<label for="name">Name: <span class="required">*</span></label>
<input type="text" name="name" value="" id="name" placeholder="Your Name" required="required" autofocus="autofocus"/>
<label for="email">Email: <span class="required">*</span></label>
<input type="email" name="email" value="" id="email" placeholder="mail@email.com" required="required"/>
<label for="phone">Phone:</label>
<input type="text" name="phone" value="" />
<label for="website">Website:</label>
<input type="url" name="website" value="" />
</fieldset><!--end user-details-->
<fieldset id="user-message">
<label for="message">Your Message: <span class="required">*</span></label>
<textarea name="message" rows="0" cols="0"></textarea>
<input type="submit" value="Submit Message" name="submit" class="submit" />
<p id="req-field-desc"><span class="required">*</span> indicates a required field</p>
</fieldset><!-- end user-message -->
</form>
view raw index.jsp hosted with ❤ by GitHub
And now if you run again, you should see something like:



Add @import url(http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); on top of CSS file.This will make a nice font available from Google Fonts directory.

Now it is time to start applying CSS for creating a better layout. Open style.css and modify/create next elements:

* { margin: 0px; padding: 0px; }
body {
margin: 0 auto;
background: #f5f5f5;
color: #555;
width: 800px;
/* make reference to the Yanone Kaffeesatz font */
font-family: 'Yanone Kaffeesatz', arial, sans-serif;
}
h1 {
color: #555;
margin: 0 0 20px 0;
}
label {
font-size: 20px;
color: #666;
}
fieldset { border: none; }
#user-details {
float: left;
width: 230px;
}
#user-message {
float: right;
width: 405px;
}
textarea {
width: 390px;
height: 175px;
}
form {
float: left;
border: 1px solid #ddd;
padding: 30px 40px 20px 40px;
margin: 75px 0 0 0;
width: 715px;
background: #fff;
}
input,textarea {
padding: 8px;
margin: 4px 0 20px 0;
background: #fff;
width: 220px;
font-size: 14px;
color: #555;
border: 1px #ddd solid;
}
view raw style.css hosted with ❤ by GitHub
And now your form should look with next layout:


See that new font is not used, simply find body,button,input,select,textarea entry and remove font-family property.

Better than before, see how only modifying CSS, form layout is modified.

And now let's start with CSS3 magic.

First trick is adding shadows and gradients to form.

/* -- CSS3 - define rounded corners -- */
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
/* -- CSS3 - create a background gradient -- */
background: -webkit-gradient(linear, 0% 0%, 0% 40%, from(#EEE), to(#FFF)
);
background: -moz-linear-gradient(0% 40% 90deg, #FFF, #EEE);
/* -- CSS3 - add a drop shadow -- */
-webkit-box-shadow: 0px 0 50px #ccc;
-moz-box-shadow: 0px 0 50px #ccc;
box-shadow: 0px 0 50px #ccc;
view raw style.css hosted with ❤ by GitHub
And next lines into input, textarea style:

/* -- CSS3 Shadow - create a shadow around each input element -- */
-webkit-box-shadow: 0px 0px 4px #aaa;
-moz-box-shadow: 0px 0px 4px #aaa;
box-shadow: 0px 0px 4px #aaa;
/* -- CSS3 Transition - define what the transition will be applied to (i.e. the background) -- */
-webkit-transition: background 0.3s linear;
view raw style.css hosted with ❤ by GitHub
where -moz prefix is used for Gecko based browsers, and -webkit is used for Webkit browsers.

Next lines add some mouse over effects into form elements.

input:hover, textarea:hover {
background: #eee;
}
view raw style.css hosted with ❤ by GitHub

And to finish applying styles, we are going to modify submit button.

Remove input[type="submit"] from button and input style.

And add next lines into CSS file:

input[type="submit"] {
width: 150px;
color: #eee;
text-transform: uppercase;
margin-top: 10px;
background-color: #18a5cc;
border: none;
/* -- CSS3 Transition - define which property to animate (i.e. the shadow) -- */
-webkit-transition: -webkit-box-shadow 0.3s linear;
/* -- CSS3 Shadow - create a shadow around each input element -- */
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#18a5cc),
to(#0a85a8) );
background: -moz-linear-gradient(25% 75% 90deg, #0a85a8, #18a5cc);
/* -- CSS3 - Rounded Corners -- */
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
input[type="submit"]:hover {
-webkit-box-shadow: 0px 0px 20px #555;
-moz-box-shadow: 0px 0px 20px #aaa;
box-shadow: 0px 0px 20px #555;
cursor: pointer;
}
view raw style.css hosted with ❤ by GitHub

CSS magic is over, now it is time to start with server side.

If you look careful Spring taglibs, you will see that you cannot create for example email input types directly or URL types to cite a few. So I decided to use a new template engine called Thymeleaf, that offers a really nice approach for integrating HTML 5 with Spring MVC applications.

The main goal of Thymeleaf is to provide an elegant and well-formed way of creating HTML 5 templates. Its Standard and SpringStandard dialects allow you to create powerful natural templates, that can be correctly displayed by browsers and therefore work also as static prototypes. This is possible due the absence of taglibs like  <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>.

First thing to do is change index.jsp to index.html, and change ViewResolver to Thymeleaf ViewResolver:

First add Thymeleaf dependencies into pom and then modify servlet-context.xml to register TempateResolver, TemplateEngine and ThymeleafViewResolver and remove the deault view resolver.

<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring3</artifactId>
<version>1.1.2</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML5" />
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
</bean>
<bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
</bean>

Next step is modeling our form to a model class, and modify Spring Controller to manage form submition.

Model class is not shown because is a simple POJO with form fields. Let's see how controller is modified.

Open HomeController class and create two methods (GET, POST) to deal with HTML5 form. Note that it is a typical Spring Form Controller;

@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String homeForm(Model model) {
logger.info("Welcome home! from Thymeleaf");
model.addAttribute("messageInfo", new MessageForm());
return "index";
}
@RequestMapping(value = "/", method = RequestMethod.POST)
public String homeForm(@ModelAttribute("messageInfo") MessageForm messageForm) {
logger.info("Welcome home! from Form Thymeleaf");
System.out.println(messageForm);
return "index";
}
}
And finally we are going to templarize index.html content, using Thymeleaf engine.

Open index.html and make next changes:

Add Thymeleaf namespace on html tag.

<html xmlns:th="http://www.thymeleaf.org">

Then change form tag with two new attributes, one with object's model name (form backing object) and other one with submition URL.

<form action="#" th:object="${messageInfo}" th:action="@{/}" method="post">

Next thing to do is change each form element with th:value attribute containing backing object property name.

<input type="text" name="name" value="" id="name" placeholder="Your Name" required="required" autofocus="autofocus" th:value="*{name}"/>

And more few changes should be performed:

Use DOCTYPE instead of doctype

At time of writing this post, html5boilporate introduces link tag without closing, so Thymeleaf parser will throw an exception if is executed as is, so let's terminate link tag and meta tags.

And finally last change is required because of xhtml correctness:

<script>
/*<![CDATA[*/
window.jQuery
|| document
.write('<script src="js/libs/jquery-1.6.2.min.js"><\/script>')
/*]]>*/
</script>
view raw index.html hosted with ❤ by GitHub

<script> tag is surrounded with CDATA tags. See explanation here:  http://javascript.about.com/library/blxhtml.htm.

Now you can deploy application and your HTML5+CSS3 form is displayed and data submitted is displayed through server console and form page is reloaded with introduced data. And now I am going to explain what are those strange expressions in form and then we will add a new page.

Inside each Thymelef attribute four kind of expressions can be used:

  • ${...} are Spring EL expressions and are executed on model attributes.
  • *{...} are expressions executed on the form backing bean, it is like a pointer to form object (root).
  •  #{...} are for internationalization.
  • @{...} are link expressions to rewrite URLs.

And before jumping to next section try this, instead of opening index.html from http://localhost:..... try to open from your drive file://.... and I suppose you see that that's not a nice prototype because no styling is applied. This happens because we are using CSS location as:

<link rel="stylesheet" href="css/style.css"/>

HTML file is at /src/main/webapp/WEB-INF/views/ and this directory does not contain css folder. And now you can think that I have not told you the true about prototyping with Thymeleaf, but wait trust me and change that line to:

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

And open again, and wow now you can see it, a real page as one generated by the server. What we have changed is that when a browser opens HTML file without using template engine (offline), href attribute is used, but when page is online (and requested file acts as a template), th:href attribute is used.


Listing Messages:

Let's start creating a new HTML file to display all inserted messages and apply some styling with CSS.


First add a new CSS property to apply same design in table.

#display-data {
float: left;
border: 1px solid #ddd;
padding: 30px 40px 20px 40px;
margin: 75px 0 0 0;
width: 715px;
background: #fff;
/* -- CSS3 - define rounded corners -- */
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
/* -- CSS3 - create a background gradient -- */
background: -webkit-gradient(linear, 0% 0%, 0% 40%, from(#EEE), to(#FFF)
);
background: -moz-linear-gradient(0% 40% 90deg, #FFF, #EEE);
/* -- CSS3 - add a drop shadow -- */
-webkit-box-shadow: 0px 0 50px #ccc;
-moz-box-shadow: 0px 0 50px #ccc;
box-shadow: 0px 0 50px #ccc;
}
th {
border: 1px solid #000000;
background-color: #A7A7A7;
color: #fff;
padding: 0.4em;
text-align: left;
}
tr:nth-child(2n+1) {
background-color: #E6E6E6;
}
view raw style.css hosted with ❤ by GitHub
New CSS 3 property (nth-child) is used so even tr tags of a table will contain different style than odd tr tags.

Then create a file called list.html in same level of index.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title></title>
<meta name="description" content="HTML 5 CSS 3 Spring Form" />
<meta name="author" content="alex soto" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="../../css/style.css"
th:href="@{/css/style.css}" />
<script src="js/libs/modernizr-2.0.6.min.js"></script>
</head>
<body>
<div id="container">
<table id="display-data">
<thead>
<tr>
<th>Count</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Website</th>
</tr>
</thead>
<tbody>
<tr th:each="msg, rowStat : ${messages}">
<td th:text="${rowStat.count}">1</td>
<td th:text="${msg.name}">My Name</td>
<td th:text="${msg.email}">my@email.com</td>
<td th:text="${msg.phone}">9999</td>
<td th:text="${msg.website}">http://mysite.com</td>
</tr>
</tbody>
</table>
</div>
<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script>
/*<![CDATA[*/
window.jQuery
|| document
.write('<script src="js/libs/jquery-1.6.2.min.js"><\/script>')
/*]]>*/
</script>
</body>
</html>
view raw list.html hosted with ❤ by GitHub

This is a copy paste of index.html but changing div with id content.

In this case we are using another nice feature of Thymeleaf, collection iteration. With th:each you are iterating over a collection of elements. See that rowStat variable can be used for retrieving column information like number of column. And also see how we are defining default values between td tags, so this file is a prototype too.

Next step is changing our controller, so homeForm (renamed to showResults) method returns a list of messages:

private static List<MessageForm> messageFormRepository = new ArrayList<MessageForm>();
@RequestMapping(value = "/", method = RequestMethod.POST)
public ModelAndView showResults(@ModelAttribute("messageInfo") MessageForm messageForm) {
addNewMessage(messageForm);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("list");
modelAndView.addObject("messages", messageFormRepository);
return modelAndView;
}
private void addNewMessage(MessageForm messageForm) {
messageFormRepository.add(messageForm);
}

Using a simple list as repository is not a good design, but for teaching purpose is enough. See that view name is set to list, and list of messageForm are sent back with model name used in th:each attribute.

Internationalization:

Next step is internationalizing the application. For this reason we create a message properties file (messages_en_US.properties) into src/main/resources/locale:

theader.count = Count
theader.name = Name
theader.email = E-Mail
theader.phone = Phone
theader.website = Website
theader.message = Message
Then you must configure Locale beans in Spring context file.

<beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<beans:property name="defaultLocale" value="en_US" />
</beans:bean>
<beans:bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<beans:property name="paramName" value="language" />
</beans:bean>
<beans:bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
<beans:property name="interceptors">
<beans:list>
<beans:ref bean="localeChangeInterceptor" />
</beans:list>
</beans:property>
</beans:bean>
<beans:bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<beans:property name="basename" value="locale/messages" />
</beans:bean>
And finally change index.html and list.html static values to internationalized keys using #{} expression:

<th th:text="#{theader.count}"></th>
<label for="name" th:text="#{theader.name}">Name: </label>

And although internationalization is used, web page is still a prototype.

It seems like application is working well, but it has one problem. Try next sequence of events:

1. Add a new message.
2. When list of all messages are shown, refresh the page (F5).

And surprise the same message is added too, so now we have two repeated messages, but you have only inserted one.

To fix this problem we must change our controller and use redirect keyword on create method to redirect to a method of controller that finds all inserted messages.

@RequestMapping(value = "/insert", method = RequestMethod.POST)
public String create(@ModelAttribute("messageInfo") MessageForm messageForm) {
addNewMessage(messageForm);
return "redirect:/list";
}
@RequestMapping(value = "/list", method = RequestMethod.GET)
public ModelAndView showResults() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("list");
modelAndView.addObject("messages", messageFormRepository);
return modelAndView;
}

When a new message is created (create method), a redirect to /list is returned, then showResults method is called, and returns a list with all inserted messages. Remember to change th:action from form tag to /insert too.

And now if you execute the same process as before, no multiple insertions occur.

Validation:

And as final step let's see how to implement validation with Thymeleaf.

First thing is adding Jsr-303 provider into our pom. In this case Hibernate-Validator.

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
view raw pom.xml hosted with ❤ by GitHub

Next step is changing our model by for example creating a length constraint in phone field:

@Length(min=9)
private String phone;

To trigger validation of a @Controller input, you must annotate input arguments with @Valid. So our controller is modified to:

@RequestMapping(value = "/insert", method = RequestMethod.POST)
public String create(@Valid @ModelAttribute("messageInfo")MessageForm messageForm, BindingResult result) {
if(result.hasErrors()) {
return "index";
}
addNewMessage(messageForm);
return "redirect:/list";
}
And finally modify form page so when an error occurs, a message is shown.

First a new CSS style is created for showing error message:

#errors {
float: right;
border:solid 1px #E58E8E;
padding:8px;
margin:8px 0px 10px 50px;
display:block;
width:405px;
-webkit-border-radius:8px;
-moz-border-radius:8px;
border-radius:8px;
background:#FFE6E6 url(../img/cancel_48.png) no-repeat 370px center;
}
view raw style.css hosted with ❤ by GitHub
See that in previous style we are defining an img folder so new static resource resolution in servlet-context.xml as done with css and js folders must be registered.

<resources mapping="/img/**" location="/img/" />

And finally in index.html a new div is created for showing errors:

<div id="errors">
<ul th:if="${#fields.hasErrors('*')}">
<li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
</ul>
</div>
view raw index.html hosted with ❤ by GitHub

For showing errors, #fields variable is used. This variable is provided by Thymeleaf and contains all errors bound by Validation Framework. Note that star '*' is used as errors function parameters for returning all errors produced by all form fields, but Thymeleaf allows you to specify only one particular field.

Conclusions:

From this post I have learned some interesting points:

  • With new HTML5 tags, some Javascript validation (like entering well-formatted email) is not required anymore.
  • CSS 3 shadows and gradients properties allow us to create amazing forms without using complicated structures of images.
  • Thymeleaf is an amazing template engine, in fact for me a real way for creating prototypes and final code at once. 
  • Thymeleaf is a young project, and for example does not support jQuery by default, but exists   a Thymeleaf dialect that integrate it. I think that in nearest future it will be a template engine to be consider.

Hope you find Thymeleaf useful too.


Download Code.

Music: http://www.youtube.com/watch?v=MMr1zH8NoEY

1 comentarios:

jepeto dijo...

Merci pour cet article et bon courage