- 快召唤伙伴们来围观吧
- 微博 QQ QQ空间 贴吧
- 文档嵌入链接
- 复制
- 微信扫一扫分享
- 已成功复制到剪贴板
usingthymeleaf
展开查看详情
1 . Thymeleaf Tutorial: Using Thymeleaf Document version: 20160713 - 13 July 2016 Project version: 2.1.5.RELEASE Project web site: http://www.thymeleaf.org Page 1 of 87
2 .1 Introducing Thymeleaf 1.1 What is Thymeleaf? Thymeleaf is a Java library. It is an XML/XHTML/HTML5 template engine able to apply a set of transformations to template files in order to display data and/or text produced by your applications. It is better suited for serving XHTML/HTML5 in web applications, but it can process any XML file, be it in web or in standalone applications. The main goal of Thymeleaf is to provide an elegant and well-formed way of creating templates. In order to achieve this, it is based on XML tags and attributes that define the execution of predefined logic on the DOM (Document Object Model) , instead of explicitly writing that logic as code inside the template. Its architecture allows a fast processing of templates, relying on intelligent caching of parsed files in order to use the least possible amount of I/O operations during execution. And last but not least, Thymeleaf has been designed from the beginning with XML and Web standards in mind, allowing you to create fully validating templates if that is a need for you. 1.2 What kind of templates can Thymeleaf process? Out-of-the-box, Thymeleaf allows you to process six kinds of templates, each of which is called a Template Mode: XML Valid XML XHTML Valid XHTML HTML5 Legacy HTML5 All of these modes refer to well-formed XML files except the Legacy HTML5 mode, which allows you to process HTML5 files with features such as standalone (not closed) tags, tag attributes without a value or not written between quotes. In order to process files in this specific mode, Thymeleaf will first perform a transformation that will convert your files to well-formed XML files which are still perfectly valid HTML5 (and are in fact the recommended way to create HTML5 code)1. Also note that validation is only available for XML and XHTML templates. Nevertheless, these are not the only types of template that Thymeleaf can process, and the user is always able to define his/her own mode by specifying both a way to parse templates in this mode and a way to write the results. This way, anything that can be modelled as a DOM tree (be it XML or not) could effectively be processed as a template by Thymeleaf. 1.3 Dialects: The Standard Dialect Thymeleaf is an extremely extensible template engine (in fact it should be better called a template engine framework) that allows you to completely define the DOM nodes that will be processed in your templates and also how they will be processed. An object that applies some logic to a DOM node is called a processor, and a set of these processors —plus some extra artifacts— is called a dialect, of which Thymeleaf’s core library provides one out-of-the-box called the Standard Dialect, Page 2 of 87
3 .which should be enough for the needs of a big percent of users. The Standard Dialect is the dialect this tutorial covers . Every attribute and syntax feature you will learn about in the following pages is defined by this dialect, even if that isn’t explicitly mentioned. Of course, users may create their own dialects (even extending the Standard one) if they want to define their own processing logic while taking advantage of the library’s advanced features. A Template Engine can be configured several dialects at a time. The official thymeleaf-spring3 and thymeleaf-spring4 integration packages both define a dialect called the “SpringStandard Dialect”, mostly equivalent to the Standard Dialect but with small adaptations to make better use of some features in Spring Framework (for example, by using Spring Expression Language instead of Thymeleaf’s standard OGNL). So if you are a Spring MVC user you are not wasting your time, as almost everything you learn here will be of use in your Spring applications. The Thymeleaf Standard Dialect can process templates in any mode, but is especially suited for web-oriented template modes (XHTML and HTML5 ones). Besides HTML5, it specifically supports and validates the following XHTML specifications: XHTML 1.0 Transitional, XHTML 1.0 Strict, XHTML 1.0 Frameset , and XHTML 1.1. Most of the processors of the Standard Dialect are attribute processors. This allows browsers to correctly display XHTML/HTML5 template files even before being processed, because they will simply ignore the additional attributes. For example, while a JSP using tag libraries could include a fragment of code not directly displayable by a browser like: <form:inputText name="userName" value="${user.name}" /> …the Thymeleaf Standard Dialect would allow us to achieve the same functionality with: <input type="text" name="userName" value="James Carrot" th:value="${user.name}" /> Which not only will be correctly displayed by browsers, but also allow us to (optionally) specify a value attribute in it (“James Carrot”, in this case) that will be displayed when the prototype is statically opened in a browser, and that will be substituted by the value resulting from the evaluation of ${user.name} during Thymeleaf processing of the template. If needed, this will allow your designer and developer to work on the very same template file and reduce the effort required to transform a static prototype into a working template file. The ability to do this is a feature usually called Natural Templating. 1.4 Overall Architecture Thymeleaf’s core is a DOM processing engine. Specifically, it uses its own high-performance DOM implementation —not the standard DOM API— for building in-memory tree representations of your templates, on which it later operates by traversing their nodes and executing processors on them that modify the DOM according to the current configuration and the set of data that is passed to the template for its representation —known as the context. The use of a DOM template representation makes it very well suited for web applications because web documents are very often represented as object trees (in fact DOM trees are the way browsers represent web pages in memory). Also, building on the idea that most web applications use only a few dozen templates, that these are not big files and that they don’t normally change while the application is running, Thymeleaf’s usage of an in-memory cache of parsed template DOM trees allows it to be fast in production environments, because very little I/O is needed (if any) for most template processing operations. Page 3 of 87
4 . If you want more detail, later in this tutorial there is an entire chapter dedicated to caching and to the way Thymeleaf optimizes memory and resource usage for faster operation. Nevertheless, there is a restriction: this architecture also requires the use of bigger amounts of memory space for each template execution than other template parsing/processing approaches, which means that you should not use the library for creating big data XML documents (as opposed to web documents). As a general rule of thumb (and always depending on the memory size of your JVM), if you are generating XML files with sizes around the tens of megabytes in a single template execution, you probably should not be using Thymeleaf. The reason we consider this restriction only applies to data XML files and not web XHTML/HTML5 is that you should never generate web documents so big that your users’ browsers set ablaze and/or explode – remember that these browsers will also have to create DOM trees for your pages! 1.5 Before going any further, you should read… Thymeleaf is especially suited for working in web applications. And web applications are based on a series of standards that everyone should know very well but few do – even if they have been working with them for years. With the advent of HTML5, the state of the art in web standards today is more confusing than ever… are we going back from XHTML to HTML? Will we abandon XML syntax? Why is nobody talking about XHTML 2.0 anymore? So before going any further in this tutorial, you are strongly advised to read an article on Thymeleaf’s web site called “From HTML to HTML (via HTML)”, which you can find at this address: http://www.thymeleaf.org/doc/articles/fromhtmltohtmlviahtml.html Page 4 of 87
5 .2 The Good Thymes Virtual Grocery The source code for the examples shown in this and future chapters of this guide can be found in the Good Thymes Virtual Grocery GitHub repository. 2.1 A website for a grocery In order to better explain the concepts involved in processing templates with Thymeleaf, this tutorial will use a demo application you can download from the project web site. This application represents the web site of an imaginary virtual grocery, and will provide us with the adequate scenarios to exemplify diverse Thymeleaf features. We will need a quite simple set of model entities for our application: Products which are sold to Customers by creating Orders . We will also be managing Comments about those Products : Example application model Our small application will also have a very simple service layer, composed by Service objects containing methods like: Page 5 of 87
6 . public class ProductService { ... public List<Product> findAll() { return ProductRepository.getInstance().findAll(); } public Product findById(Integer id) { return ProductRepository.getInstance().findById(id); } } Finally, at the web layer our application will have a filter that will delegate execution to Thymeleaf-enabled commands depending on the request URL: private boolean process(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { /* * Query controller/URL mapping and obtain the controller * that will process the request. If no controller is available, * return false and let other filters/servlets process the request. */ IGTVGController controller = GTVGApplication.resolveControllerForRequest(request); if (controller == null) { return false; } /* * Obtain the TemplateEngine instance. */ TemplateEngine templateEngine = GTVGApplication.getTemplateEngine(); /* * Write the response headers */ response.setContentType("text/html;charset=UTF-8"); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); /* * Execute the controller and process view template, * writing the results to the response writer. */ controller.process( request, response, this.servletContext, templateEngine); return true; } catch (Exception e) { throw new ServletException(e); } } This is our IGTVGController interface: Page 6 of 87
7 . public interface IGTVGController { public void process( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, TemplateEngine templateEngine); } All we have to do now is create implementations of the IGTVGController interface, retrieving data from the services and processing templates using the TemplateEngine object. In the end, it will look like this: Example application home page But first let’s see how that template engine is initialized. 2.2 Creating and configuring the Template Engine The process(…) method in our filter contained this sentence: TemplateEngine templateEngine = GTVGApplication.getTemplateEngine(); Which means that the GTVGApplication class is in charge of creating and configuring one of the most important objects in a Thymeleaf-enabled application: The TemplateEngine instance. Our org.thymeleaf.TemplateEngine object is initialized like this: Page 7 of 87
8 . public class GTVGApplication { ... private static TemplateEngine templateEngine; ... static { ... initializeTemplateEngine(); ... } private static void initializeTemplateEngine() { ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(); // XHTML is the default mode, but we set it anyway for better understanding of code templateResolver.setTemplateMode("XHTML"); // This will convert "home" to "/WEB-INF/templates/home.html" templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); // Template cache TTL=1h. If not set, entries would be cached until expelled by LRU templateResolver.setCacheTTLMs(3600000L); templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(templateResolver); } ... } Of course there are many ways of configuring a TemplateEngine object, but for now these few lines of code will teach us enough about the steps needed. The Template Resolver Let’s start with the Template Resolver: ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(); Template Resolvers are objects that implement an interface from the Thymeleaf API called org.thymeleaf.templateresolver.ITemplateResolver : public interface ITemplateResolver { ... /* * Templates are resolved by String name (templateProcessingParameters.getTemplateName()) * Will return null if template cannot be handled by this template resolver. */ public TemplateResolution resolveTemplate( TemplateProcessingParameters templateProcessingParameters); } Page 8 of 87
9 .These objects are in charge of determining how our templates will be accessed, and in this GTVG application, the org.thymeleaf.templateresolver.ServletContextTemplateResolver implementation that we are using specifies that we are going to retrieve our template files as resources from the Servlet Context: an application-wide javax.servlet.ServletContext object that exists in every Java web application, and that resolves resources considering the web application root as the root for resource paths. But that’s not all we can say about the template resolver, because we can set some configuration parameters on it. First, the template mode, one of the standard ones: templateResolver.setTemplateMode("XHTML"); XHTML is the default template mode for ServletContextTemplateResolver , but it is good practice to establish it anyway so that our code documents clearly what is going on. templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); These prefix and suffix do exactly what it looks like: modify the template names that we will be passing to the engine for obtaining the real resource names to be used. Using this configuration, the template name “product/list” would correspond to: servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html") Optionally, the amount of time that a parsed template living in cache will be considered valid can be configured at the Template Resolver by means of the cacheTTLMs property: templateResolver.setCacheTTLMs(3600000L); Of course, a template can be expelled from cache before that TTL is reached if the max cache size is reached and it is the oldest entry currently cached. Cache behaviour and sizes can be defined by the user by implementing the ICacheManager interface or simply modifying the StandardCacheManager object set to manage caches by default. We will learn more about template resolvers later. Now let’s have a look at the creation of our Template Engine object. The Template Engine Template Engine objects are of class org.thymeleaf.TemplateEngine, and these are the lines that created our engine in the current example: templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(templateResolver); Rather simple, isn’t it? All we need is to create an instance and set the Template Resolver to it. A template resolver is the only required parameter a TemplateEngine needs, although of course there are many others that will be covered later (message resolvers, cache sizes, etc). For now, this is all we need. Our Template Engine is now ready and we can start creating our pages using Thymeleaf. Page 9 of 87
10 .Page 10 of 87
11 .3 Using Texts 3.1 A multi-language welcome Our first task will be to create a home page for our grocery site. The first version we will write of this page will be extremely simple: just a title and a welcome message. This is our /WEB- INF/templates/home.html file: <!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" /> </head> <body> <p th:text="#{home.welcome}">Welcome to our grocery store!</p> </body> </html> The first thing you will notice here is that this file is XHTML that can be correctly displayed by any browser, because it does not include any non-XHTML tags (and browsers ignore all attributes they don’t understand, like th:text ). Also, browsers will display it in standards mode (not in quirks mode), because it has a well-formed DOCTYPE declaration. Next, this is also valid XHTML2, because we have specified a Thymeleaf DTD which defines attributes like th:text so that your templates can be considered valid. And even more: once the template is processed (and all th:* attributes are removed), Thymeleaf will automatically substitute that DTD declaration in the DOCTYPE clause by a standard XHTML 1.0 Strict one (we will leave this DTD translation features for a later chapter). A thymeleaf namespace is also being declared for th:* attributes: <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> Note that, if we hadn’t cared about our template’s validity or well-formedness at all, we could have simply specified a standard XHTML 1.0 Strict DOCTYPE , along with no xmlns namespace declarations: Page 11 of 87
12 . <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" /> </head> <body> <p th:text="#{home.welcome}">Welcome to our grocery store!</p> </body> </html> …and this would still be perfectly processable by Thymeleaf in the XHTML mode (although probably our IDE would make our life quite miserable showing warnings everywhere). But enough about validation. Now for the really interesting part of the template: let’s see what that th:text attribute is about. Using th:text and externalizing text Externalizing text is extracting fragments of template code out of template files so that they can be kept in specific separate files (typically .properties files) and that they can be easily substituted by equivalent texts written in other languages (a process called internationalization or simply i18n). Externalized fragments of text are usually called “messages”. Messages have always a key that identifies them, and Thymeleaf allows you to specify that a text should correspond to a specific message with the #{...} syntax: <p th:text="#{home.welcome}">Welcome to our grocery store!</p> What we can see here are in fact two different features of the Thymeleaf Standard Dialect: The th:text attribute, which evaluates its value expression and sets the result of this evaluation as the body of the tag it is in, effectively substituting that “Welcome to our grocery store!” text we see in the code. The #{home.welcome} expression, specified in the Standard Expression Syntax, specifying that the text to be used by the th:text attribute should be the message with the home.welcome key corresponding to whichever locale we are processing the template with. Now, where is this externalized text? The location of externalized text in Thymeleaf is fully configurable, and it will depend on the specific org.thymeleaf.messageresolver.IMessageResolver implementation being used. Normally, an implementation based on .properties files will be used, but we could create our own implementations if we wanted, for example, to obtain messages from a database. However, we have not specified a message resolver to our Template Engine during initialization, and that means that our application is using the Standard Message Resolver, implemented by class org.thymeleaf.messageresolver.StandardMessageResolver . This standard message resolver expects to find messages for /WEB-INF/templates/home.html in .properties files in the same folder and with the same name as the template, like: Page 12 of 87
13 . /WEB-INF/templates/home_en.properties for English texts. /WEB-INF/templates/home_es.properties for Spanish language texts. /WEB-INF/templates/home_pt_BR.properties for Portuguese (Brazil) language texts. /WEB-INF/templates/home.properties for default texts (if locale is not matched). Let’s have a look at our home_es.properties file: home.welcome=¡Bienvenido a nuestra tienda de comestibles! This is all we need for making Thymeleaf process our template. Let’s create our Home controller then. Contexts In order to process our template, we will create a HomeController class implementing the IGTVGController interface we saw before: public class HomeController implements IGTVGController { public void process( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, TemplateEngine templateEngine) { WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); templateEngine.process("home", ctx, response.getWriter()); } } The first thing we can see here is the creation of a context. A Thymeleaf context is an object implementing the org.thymeleaf.context.IContext interface. Contexts should contain all the data required for an execution of the Template Engine in a variables map, and also reference the Locale that must be used for externalized messages. public interface IContext { public VariablesMap<String,Object> getVariables(); public Locale getLocale(); ... } There is a specialized extension of this interface, org.thymeleaf.context.IWebContext : public interface IWebContext extends IContext { public HttpSerlvetRequest getHttpServletRequest(); public HttpSession getHttpSession(); public ServletContext getServletContext(); public VariablesMap<String,String[]> getRequestParameters(); public VariablesMap<String,Object> getRequestAttributes(); public VariablesMap<String,Object> getSessionAttributes(); public VariablesMap<String,Object> getApplicationAttributes(); } Page 13 of 87
14 .The Thymeleaf core library offers an implementation of each of these interfaces: org.thymeleaf.context.Context implements IContext org.thymeleaf.context.WebContext implements IWebContext And as you can see in the controller code, WebContext is the one we will use. In fact we have to, because the use of a ServletContextTemplateResolver requires that we use a context implementing IWebContext . WebContext ctx = new WebContext(request, servletContext, request.getLocale()); Only two of those three constructor arguments are required, because the default locale for the system will be used if none is specified (although you should never let this happen in real applications). From the interface definition we can tell that WebContext will offer specialized methods for obtaining the request parameters and request, session and application attributes . But in fact WebContext will do a little bit more than just that: Add all the request attributes to the context variables map. Add a context variable called param containing all the request parameters. Add a context variable called session containing all the session attributes. Add a context variable called application containing all the ServletContext attributes. Just before execution, a special variable is set into all context objects (implementations of IContext ), including both Context and WebContext , called the execution info ( execInfo ). This variable contains two pieces of data that can be used from within your templates: The template name ( ${execInfo.templateName} ), the name specified for engine execution, and corresponding to the template being executed. The current date and time ( ${execInfo.now} ), a Calendar object corresponding to the moment the template engine started its execution for this template. Executing the template engine With our context object ready, all we need is executing the template engine specifying the template name and the context, and passing on the response writer so that the response can be written to it: templateEngine.process("home", ctx, response.getWriter()); Let’s see the results of this using the Spanish locale: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Good Thymes Virtual Grocery</title> <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/> <link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" /> </head> <body> <p>¡Bienvenido a nuestra tienda de comestibles!</p> </body> </html> Page 14 of 87
15 .3.2 More on texts and variables Unescaped Text The simplest version of our Home page seems to be ready now, but there is something we have not thought about… what if we had a message like this? home.welcome=Welcome to our <b>fantastic</b> grocery store! If we execute this template like before, we will obtain: <p>Welcome to our <b>fantastic</b> grocery store!</p> Which is not exactly what we expected, because our <b> tag has been escaped and therefore it will be displayed at the browser. This is the default behaviour of the th:text attribute. If we want Thymeleaf to respect our XHTML tags and not escape them, we will have to use a different attribute: th:utext (for “unescaped text”): <p th:utext="#{home.welcome}">Welcome to our grocery store!</p> This will output our message just like we wanted it: <p>Welcome to our <b>fantastic</b> grocery store!</p> Using and displaying variables Now let’s add some more contents to our home page. For example, we could want to display the date below our welcome message, like this: Welcome to our fantastic grocery store! Today is: 12 july 2010 First of all, we will have to modify our controller so that we add that date as a context variable: public void process( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, TemplateEngine templateEngine) { SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); Calendar cal = Calendar.getInstance(); WebContext ctx = new WebContext(request, response, servletContext, request.getLocale()); ctx.setVariable("today", dateFormat.format(cal.getTime())); templateEngine.process("home", ctx, response.getWriter()); } We have added a String today variable to our context, and now we can display it in our template: Page 15 of 87
16 . <body> <p th:utext="#{home.welcome}">Welcome to our grocery store!</p> <p>Today is: <span th:text="${today}">13 February 2011</span></p> </body> As you can see, we are still using the th:text attribute for the job (and that’s correct, because we want to substitute the tag’s body), but the syntax is a little bit different this time and instead of a #{...} expression value, we are using a ${...} one. This is a variable expression value, and it contains an expression in a language called OGNL (Object-Graph Navigation Language) that will be executed on the context variables map. The ${today} expression simply means “get the variable called today”, but these expressions could be more complex (like ${user.name} for “get the variable called user, and call its getName() method”). There are quite a lot of possibilities in attribute values: messages, variable expressions… and quite a lot more. Next chapter will show us what all these possibilities are. Page 16 of 87
17 .4 Standard Expression Syntax We will make a small break in the development of our grocery virtual store to learn about one of the most important parts of the Thymeleaf Standard Dialect: the Thymeleaf Standard Expression syntax. We have already seen two types of valid attribute values expressed in this syntax: message and variable expressions: <p th:utext="#{home.welcome}">Welcome to our grocery store!</p> <p>Today is: <span th:text="${today}">13 february 2011</span></p> But there are more types of value we don’t know yet, and more interesting detail to know about the ones we already know. First, let’s see a quick summary of the Standard Expression features: Simple expressions: Variable Expressions: ${...} Selection Variable Expressions: *{...} Message Expressions: #{...} Link URL Expressions: @{...} Literals Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations: String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations: Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations: Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality: Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators: If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) All these features can be combined and nested: Page 17 of 87
18 . 'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown')) 4.1 Messages As we already know, #{...} message expressions allow us to link this: <p th:utext="#{home.welcome}">Welcome to our grocery store!</p> …to this: home.welcome=¡Bienvenido a nuestra tienda de comestibles! But there’s one aspect we still haven’t thought of: what happens if the message text is not completely static? What if, for example, our application knew who is the user visiting the site at any moment and we wanted to greet him/her by name? <p>¡Bienvenido a nuestra tienda de comestibles, John Apricot!</p> This means we would need to add a parameter to our message. Just like this: home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}! Parameters are specified according to the java.text.MessageFormat standard syntax, which means you could add format to numbers and dates as specified in the API docs for that class. In order to specify a value for our parameter, and given an HTTP session attribute called user , we would have: <p th:utext="#{home.welcome(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper! </p> If needed, several parameters could be specified, separated by commas. In fact, the message key itself could come from a variable: <p th:utext="#{${welcomeMsgKey}(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper! </p> 4.2 Variables We already mentioned that ${...} expressions are in fact OGNL (Object-Graph Navigation Language) expressions executed on the map of variables contained in the context. For detailed info about OGNL syntax and features, you should read the OGNL Language Guide at: http://commons.apache.org/ognl/ From OGNL’s syntax, we know that this: Page 18 of 87
19 . <p>Today is: <span th:text="${today}">13 february 2011</span>.</p> …is in fact equivalent to this: ctx.getVariables().get("today"); But OGNL allows us to create quite more powerful expressions, and that’s how this: <p th:utext="#{home.welcome(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper! </p> …does in fact obtain the user name by executing: ((User) ctx.getVariables().get("session").get("user")).getName(); But getter method navigation is just one of OGNL’s features. Let’s see some more: /* * Access to properties using the point (.). Equivalent to calling property getters. */ ${person.father.name} /* * Access to properties can also be made by using brackets ([]) and writing * the name of the property as a variable or between single quotes. */ ${person['father']['name']} /* * If the object is a map, both dot and bracket syntax will be equivalent to * executing a call on its get(...) method. */ ${countriesByCode.ES} ${personsByName['Stephen Zucchini'].age} /* * Indexed access to arrays or collections is also performed with brackets, * writing the index without quotes. */ ${personsArray[0].name} /* * Methods can be called, even with arguments. */ ${person.createCompleteName()} ${person.createCompleteNameWithSeparator('-')} Expression Basic Objects When evaluating OGNL expressions on the context variables, some objects are made available to expressions for higher flexibility. These objects will be referenced (per OGNL standard) starting with the # symbol: #ctx : the context object. #vars: the context variables. #locale : the context locale. #httpServletRequest : (only in Web Contexts) the HttpServletRequest object. Page 19 of 87
20 . #httpSession : (only in Web Contexts) the HttpSession object. So we can do this: Established locale country: <span th:text="${#locale.country}">US</span>. You can read the full reference of these objects in the Appendix A. Expression Utility Objects Besides these basic objects, Thymeleaf will offer us a set of utility objects that will help us perform common tasks in our expressions. #dates : utility methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects. #numbers : utility methods for formatting numeric objects. #strings : utility methods for String objects: contains, startsWith, prepending/appending, etc. #objects : utility methods for objects in general. #bools : utility methods for boolean evaluation. #arrays : utility methods for arrays. #lists : utility methods for lists. #sets : utility methods for sets. #maps : utility methods for maps. #aggregates : utility methods for creating aggregates on arrays or collections. #messages : utility methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax. #ids : utility methods for dealing with id attributes that might be repeated (for example, as a result of an iteration). You can check what functions are offered by each of these utility objects in the Appendix B. Reformatting dates in our home page Now we know about these utility objects, we could use them to change the way in which we show the date in our home page. Instead of doing this in our HomeController : SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); Calendar cal = Calendar.getInstance(); WebContext ctx = new WebContext(request, servletContext, request.getLocale()); ctx.setVariable("today", dateFormat.format(cal.getTime())); templateEngine.process("home", ctx, response.getWriter()); …we can do just this: WebContext ctx = new WebContext(request, servletContext, request.getLocale()); ctx.setVariable("today", Calendar.getInstance()); templateEngine.process("home", ctx, response.getWriter()); …and then perform date formatting in the view layer itself: Page 20 of 87
21 . <p> Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span> </p> 4.3 Expressions on selections (asterisk syntax) Variable expressions not only can be written in ${...} expressions, but also in *{...} ones. There is an important difference, though: the asterisk syntax evaluates expressions on selected objects rather than on the whole context variables map. This is: as long as there is no selected object, the dollar and the asterisk syntaxes do exactly the same. And what is that object selection thing? A th:object attribute. Let’s use it in our user profile ( userprofile.html ) page: <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> Which is exactly equivalent to: <div> <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p> </div> Of course, dollar and asterisk syntax can be mixed: <div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> When an object selection is in place, the selected object will be also available to dollar expressions as the #object expression variable: <div th:object="${session.user}"> <p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> As said, if no object selection has been performed, dollar and asterisk syntaxes are exactly equivalent. <div> <p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p> <p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p> <p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p> </div> Page 21 of 87
22 .4.4 Link URLs Because of their importance, URLs are first-class citizens in web application templates, and the Thymeleaf Standard Dialect has a special syntax for them, the @ syntax: @{...} There are different types of URLs: Absolute URLs, like http://www.thymeleaf.org Relative URLs, which can be: Page-relative, like user/login.html Context-relative, like /itemdetails?id=3 (context name in server will be added automatically) Server-relative, like ~/billing/processInvoice (allows calling URLs in another context (= application) in the same server. Protocol-relative URLs, like //code.jquery.com/jquery-2.0.3.min.js Thymeleaf can handle absolute URLs in any situation, but for relative ones it will require you to use a context object that implements the IWebContext interface, which contains some info coming from the HTTP request and needed to create relative links. Let’s use this new syntax. Meet the th:href attribute: <!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/3/details' (plus rewriting) --> <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a> Some things to note here: th:href is an attribute modifier attribute: once processed, it will compute the link URL to be used and set the href attribute of the <a> tag to this URL. We are allowed to use expressions for URL parameters (as you can see in orderId=${o.id} ). The required URL-encoding operations will also be automatically performed. If several parameters are needed, these will be separated by commas like @{/order/process(execId=${execId},execType='FAST')} Variable templates are also allowed in URL paths, like @{/order/{orderId}/details(orderId=${orderId})} Relative URLs starting with / (like /order/details ) will be automatically prefixed the application context name. If cookies are not enabled or this is not yet known, a ";jsessionid=..." suffix might be added to relative URLs so that session is preserved. This is called URL Rewriting, and Thymeleaf allows you to plug in your own rewriting filters by using the response.encodeURL(...) mechanism from the Servlet API for every URL. The th:href tag allowed us to (optionally) have a working static href attribute in our template, so that our template links remained navigable by a browser when opened directly for prototyping purposes. As was the case with the message syntax ( #{...} ), URL bases can also be the result of evaluating another expression: <a th:href="@{${url}(orderId=${o.id})}">view</a> <a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a> A menu for our home page Page 22 of 87
23 .Now we know how to create link URLs, what about adding a small menu in our home for some of the other pages in the site? <p>Please select an option</p> <ol> <li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li> <li><a href="order/list.html" th:href="@{/order/list}">Order List</a></li> <li><a href="subscribe.html" th:href="@{/subscribe}">Subscribe to our Newsletter</a></li> <li><a href="userprofile.html" th:href="@{/userprofile}">See User Profile</a></li> </ol> Server root relative URLs An additional syntax can be used to create server-root-relative (instead of context-root-relative) URLs in order to link to different contexts in the same server. These URLs will be specified like @{~/path/to/something} 4.5 Literals Text literals Text literals are just character strings specified between single quotes. They can include any character, but you should escape any single quotes inside them as \' . <p> Now you are looking at a <span th:text="'working web application'">template file</span>. </p> Number literals Numeric literals look exactly like what they are: numbers. <p>The year is <span th:text="2013">1492</span>.</p> <p>In two years, it will be <span th:text="2013 + 2">1494</span>.</p> Boolean literals The boolean literals are true and false . For example: <div th:if="${user.isAdmin()} == false"> ... Note that in the above example, the == false is written outside the braces, and thus it is Thymeleaf itself who takes care of it. If it were written inside the braces, it would be the responsibility of the OGNL/SpringEL engines: <div th:if="${user.isAdmin() == false}"> ... The null literal Page 23 of 87
24 .The null literal can be also used: <div th:if="${variable.something} == null"> ... Literal tokens Numeric, boolean and null literals are in fact a particular case of literal tokens. These tokens allow a little bit of simplification in Standard Expressions. They work exactly the same as text literals ( '...' ), but they only allow letters ( A-Z and a-z ), numbers ( 0-9 ), brackets ( [ and ] ), dots ( . ), hyphens ( - ) and underscores ( _ ). So no whitespaces, no commas, etc. The nice part? Tokens don’t need any quotes surrounding them. So we can do this: <div th:class="content">...</div> instead of: <div th:class="'content'">...</div> 4.6 Appending texts Texts, no matter whether they are literals or the result of evaluating variable or message expressions, can be easily appended using the + operator: th:text="'The name of the user is ' + ${user.name}" 4.7 Literal substitutions Literal substitutions allow the easy formatting of strings containing values from variables without the need to append literals with '...' + '...' . These substitutions must be surrounded by vertical bars ( | ), like: <span th:text="|Welcome to our application, ${user.name}!|"> Which is actually equivalent to: <span th:text="'Welcome to our application, ' + ${user.name} + '!'"> Literal substitutions can be combined with other types of expressions: <span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|"> Note: only variable expressions ( ${...} ) are allowed inside |...| literal substitutions. No other literals ( '...' ), boolean/numeric tokens, conditional expressions etc. are. Page 24 of 87
25 .4.8 Arithmetic operations Some arithmetic operations are also available: + , - , * , / and % . th:with="isEven=(${prodStat.count} % 2 == 0)" Note that these operators can also be applied inside OGNL variable expressions themselves (and in that case will be executed by OGNL instead of the Thymeleaf Standard Expression engine): th:with="isEven=${prodStat.count % 2 == 0}" Note that textual aliases exist for some of these operators: div ( / ), mod ( % ). 4.9 Comparators and Equality Values in expressions can be compared with the > , < , >= and <= symbols, as usual, and also the == and != operators can be used to check equality (or the lack of it). Note that XML establishes that the < and > symbols should not be used in attribute values, and so they should be substituted by < and > . th:if="${prodStat.count} > 1" th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')" Note that textual aliases exist for some of these operators: gt ( > ), lt ( < ), ge ( >= ), le ( <= ), not ( ! ). Also eq ( == ), neq / ne ( != ). 4.10 Conditional expressions Conditional expressions are meant to evaluate only one of two expressions depending on the result of evaluating a condition (which is itself another expression). Let’s have a look at an example fragment (introducing another attribute modifier, this time th:class ): <tr th:class="${row.even}? 'even' : 'odd'"> ... </tr> All three parts of a conditional expression ( condition , then and else ) are themselves expressions, which means that they can be variables ( ${...} , *{...} ), messages ( #{...} ), URLs ( @{...} ) or literals ( '...' ). Conditional expressions can also be nested using parentheses: <tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'"> ... </tr> Else expressions can also be omitted, in which case a null value is returned if the condition is false: <tr th:class="${row.even}? 'alt'"> ... </tr> Page 25 of 87
26 .4.11 Default expressions (Elvis operator) A default expression is a special kind of conditional value without a then part. It is equivalent to the Elvis operator present in some languages like Groovy, and allows to specify two expressions, being the second one evaluated only in the case of the first one returning null. Let’s see it in action in our user profile page: <div th:object="${session.user}"> ... <p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p> </div> As you can see, the operator is ?: , and we use it here to specify a default value for a name (a literal value, in this case) only if the result of evaluating *{age} is null. This is therefore equivalent to: <p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p> As with conditional values, they can contain nested expressions between parentheses: <p> Name: <span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span> </p> 4.12 Preprocessing In addition to all these features for expression processing, Thymeleaf offers to us the possibility of preprocessing expressions. And what is that preprocessing thing? It is an execution of the expressions done before the normal one, that allows the modification of the actual expression that will be eventually executed. Preprocessed expressions are exactly like normal ones, but appear surrounded by a double underscore symbol (like __${expression}__ ). Let’s imagine we have an i18n Messages_fr.properties entry containing an OGNL expression calling a language-specific static method, like: article.text=@myapp.translator.Translator@translateToFrench({0}) …and a Messages_es.properties equivalent : article.text=@myapp.translator.Translator@translateToSpanish({0}) We can create a fragment of markup that evaluates one expression or the other depending on the locale. For this, we will first select the expression (by preprocessing) and then let Thymeleaf execute it: <p th:text="${__#{article.text('textVar')}__}">Some text here...</p> Note that the preprocessing step for a French locale will be creating the following equivalent: Page 26 of 87
27 . <p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p> The preprocessing String __ can be escaped in attributes using \_\_ . Page 27 of 87
28 .5 Setting Attribute Values This chapter will explain the way in which we can set (or modify) values of attributes in our markup tags, possibly the next most basic feature we will need after setting the tag body content. 5.1 Setting the value of any attribute Say our website publishes a newsletter, and we want our users to be able to subscribe to it, so we create a /WEB- INF/templates/subscribe.html template with a form: <form action="subscribe.html"> <fieldset> <input type="text" name="email" /> <input type="submit" value="Subscribe me!" /> </fieldset> </form> It looks quite OK, but the fact is that this file looks more like a static XHTML page than a template for a web application. First, the action attribute in our form statically links to the template file itself, so that there is no place for useful URL rewriting. Second, the value attribute in the submit button makes it display a text in English, but we’d like it to be internationalized. Enter then the th:attr attribute, and its ability to change the value of attributes of the tags it is set in: <form action="subscribe.html" th:attr="action=@{/subscribe}"> <fieldset> <input type="text" name="email" /> <input type="submit" value="Subscribe me!" th:attr="value=#{subscribe.submit}"/> </fieldset> </form> The concept is quite straightforward: th:attr simply takes an expression that assigns a value to an attribute. Having created the corresponding controller and messages files, the result of processing this file will be as expected: <form action="/gtvg/subscribe"> <fieldset> <input type="text" name="email" /> <input type="submit" value="¡Suscríbeme!"/> </fieldset> </form> Besides the new attribute values, you can also see that the applicacion context name has been automatically prefixed to the URL base in /gtvg/subscribe , as explained in the previous chapter. But what if we wanted to set more than one attribute at a time? XML rules do not allow you to set an attribute twice in a tag, so th:attr will take a comma-separated list of assignments, like: <img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" /> Given the required messages files, this will output: Page 28 of 87
29 . <img src="/gtgv/images/gtvglogo.png" title="Logo de Good Thymes" alt="Logo de Good Thymes" /> 5.2 Setting value to specific attributes By now, you might be thinking that something like: <input type="submit" value="Subscribe me!" th:attr="value=#{subscribe.submit}"/> …is quite an ugly piece of markup. Specifying an assignment inside an attribute’s value can be very practical, but it is not the most elegant way of creating templates if you have to do it all the time. Thymeleaf agrees with you. And that’s why in fact th:attr is scarcely used in templates. Normally, you will be using other th:* attributes whose task is setting specific tag attributes (and not just any attribute like th:attr ). And which attribute does the Standard Dialect offer us for setting the value attribute of our button? Well, in a rather obvious manner, it’s th:value . Let’s have a look: <input type="submit" value="Subscribe me!" th:value="#{subscribe.submit}"/> This looks much better!. Let’s try and do the same to the action attribute in the form tag: <form action="subscribe.html" th:action="@{/subscribe}"> And do you remember those th:href we put in our home.html before? They are exactly this same kind of attributes: <li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li> There are quite a lot of attributes like these, each of them targeting a specific XHTML or HTML5 attribute: Page 29 of 87