Thymeleaf
th:text及外化文本
外化文本把模板代码从模板文件抽取出来,独立放到特定的文件中,例如.properties文件,可以很方便地替换为不同语言的文本表示,外化文本通常叫做消息。
使用#{…}来引用消息
模板文件与属性文件必须要放在同一个目录下,且文件名也要符合规范:
/WEB-INF/templates/home.html
/WEB-INF/templates/home_en.properties
/WEB-INF/templates/home_es.properties
/WEB-INF/templates/home_pt_BR.properties
/WEB-INF/templates/home.properties
可对消息使用参数:
home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!
消息的参数根据java.text.MessageFormat标准语法来指定:
<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
多个参数使用逗号分隔开
消息的键自身也是可以来自变量:
<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
上下文
上下文是实现接口
org.thymeleaf.context.IContext的对象,它把所有模板引擎执行需要的数据包含在一个Map变量中,同时引用用于处理外化文本的Locale。
org.thymeleaf.context.IWebContext继承了
org.thymeleaf.context.IContext:
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();
}
它们的实现有两个:
org.thymeleaf.context.Context
implementsIContext
org.thymeleaf.context.WebContext
implementsIWebContext
WebContext比Context多做的工作有:
把所有请求属性添加到上下文map变量中
添加包含所有请求参数的param上下文变量
添加包含所有会话属性的session上下文变量
添加包含所有ServletContext属性的application上下文变量
在执行模板解析前,会设置一个叫执行信息(exeInfo)的特殊变量到所有的上下文对象(实现IContext接口,包括Context和WebContext)中,这个变量有两个可在模板中使用的数据:
${execInfo.templateName} 模板名称
${execInfo.now} 一个Calendar对象,表示引擎开始执行模板的时间
保留文本原样
th:text标签默认会对标签内的特殊字符做转义处理
th:utext标签可以让标签值中的内容按原样输出
使用变量
${…}会基于上下文的map变量执行,从map变量中使用OGNL语言获取变量值。
${person.father.name}
${person['father']['name']}
${countriesByCode.ES}
${personsByName['Stephen Zucchini'].age}
${personsArray[0].name}
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}
在上下文变量上执行OGNL表达式时,有一些对象在表达式上是可用的,可通过使用#来引用:
#ctx 上下文对象
#vars 上下文变量
#locale 上下文locale
#httpServletRequest (仅Web上下文)HttpServletRequest对象
#httpSession (仅Web上下文)HttpSession对象
例子:
Established locale country: <span th:text="${#locale.country}">US</span>.
变量表达式也可以写成*{…},所不同的是,星号语法会在一个选定的对象上进行运算,而不是在整个map上下文变量上运算。如果没有选定的对象,则${…}与*{…}所做的事情都是相同的。
使用th:object来选定对象:
<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>
等价于:
<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>
星号和美元可以混用:
<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>
当选定一个对象时,这个对象可以在${…}中通过#object表达式变量来引用:
<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>
如果不选择对象,则两者是等价的:
<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>
标准表达式语法
简单表达式
-
${…} 变量表达式
-
*{…} 选择变量表达式
-
#{…} 消息表达式
-
@{…} 链接URL表达式
字面量
-
文本字面量:‘one text’
<span th:text="'working web application'">template file</span>
-
数字字面量:0,34,3.0
<span th:text="2013 + 2">1494</span>
-
布尔字面量:true,false
“
== false
”写在花括号外面,则比较操作由Thymeleaf标准表达式引擎处理<div th:if="${user.isAdmin()} == false"> ...
“
== false
”写在花括号里面,则由OGNL/SpEL引擎处理<div th:if="${user.isAdmin() == false}"> ...
-
Null字面量:null
<div th:if="${variable.something} == null"> ...
-
字面量标志:one,sometext,main,…
数字、布尔、Null三种字面量实际上是字面量token的特例。
这些token可以在标准表达式中得到一些简化,它们的工作原理与文本字面量完全相同(’…’),但只允许字母(A-Za-z)、数字(0-9)、方括号([])、点(.)、横线(-)、下划线(_),因此不能有空白符,不能有逗号等等。
token不需要使用任何引号包围,因此,可使用:
<div th:class="content">...</div>
替换:
<div th:class="'content'">...</div>
文本操作
字符串连接:+
th:text="'The name of the user is ' + ${user.name}"
字面量替换:|The name is ${name}|
字面量替换可以省略“+”操作符:
<span th:text="|Welcome to our application, ${user.name}!|">
等价于:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
也可以结合其它类型的表达式使用:
<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">
注意:只有变量表达式${…}才允许出现在|…|字面量替换中,其它的字面量’…’、布尔/数字token、条件表达式等等都不行
算法操作
二元操作符:+,-,*,/(div),%(mod)
由Thymeleaf标准表达式引擎处理运算
th:with="isEven=(${prodStat.count} % 2 == 0)"
由OGNL引擎处理运算
th:with="isEven=${prodStat.count % 2 == 0}"
负号(一元操作符):-
布尔操作
二元操作符:and,or
布尔取反(一元操作符):!,not
比较及相等操作
比较操作符:>,<,>=,<=(gt,lt,ge,le)
th:if="${prodStat.count} > 1"
相等操作符:==,!=(eq,ne)
th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')"
条件操作符
if-then: (if)?(then)
if-then-else:(if)?(the):(else)
<tr th:class="${row.even}? 'even' : 'odd'"> ... </tr>
条件表达式中的三个部分自身也是表达式,也可以是变量(
${...}
,*{...}
), 消息(#{...}
), URL (@{...}
) 或字面量 ('...'
)条件表达式也可以使用括号来嵌套:
<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'"> ... </tr>
else表达式也可以省略,当条件为false时,会返回null:
<tr th:class="${row.even}? 'alt'"> ... </tr>
default:(value)?:(defaultValue)
这是没有then部分的特殊的条件表达式,又叫做Elvis操作,只有在第一个表达式返回null时,第二个表达式才会运算:
<div th:object="${session.user}"> ... <p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p> </div>
等价于:
<p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>
也可以包含嵌套的表达式:
<p> Name: <span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span> </p>
综合案例
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
表达式工具对象
#dates 与java.util.Date对象的方法对应,格式化、日期组件抽取等等
#calendars 类似#dates,与java.util.Calendar对象对应
#numbers 格式化数字对象的工具方法
#strings 与java.lang.String对应的工具方法:contains、startsWith、prepending/appending等等
#objects 用于对象的工具方法
#bools 用于布尔运算的工具方法
#arrays 用于数组的工具方法
#lists 用于列表的工具方法
#sets 用于set的工具方法
#maps 用于map的工具方法
#aggregates 用于创建数组或集合的聚合的工具方法
#messages 用于在变量表达式内部获取外化消息的工具方法,与#{…}语法获取的方式相同
#ids 用于处理可能重复出现(例如,作为遍历的结果)的id属性的工具方法
链接URL
URL在web模板中是一级重要元素,使用@{…}表示
URL的类型:
绝对URL:http://www.thymeleaf.org
相对URL:
页面相对: user/login.html
上下文相对:/itemdetails?id=3 (服务器上下文名称会被自动添加)
服务器相对:~/billing/processInvoice(允许调用同一服务器上的另一个上下文中的URL)
协议相对://code.jquery.com/jquery-2.0.3.min.js
Thymeleaf在任何情况下都可以处理绝对URL,对于相对URL,则需要使用一个实现了IWebContext接口的上下文对象,这个对象包含了来自HTTP请求的信息,这些信息用于创建相对链接。
<!-- 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>
预处理
Thymeleaf提供预处理表达式的功能。
它是在表壳式正常执行前执行的操作,允许修改最终将要被执行的表达式。
预处理表达式跟正常的一样,但被两个下划线包围住,例如:__${expression}__
假设有一个i18n消息文件Message_fr.properties,里面有一个条目包含了一个调用具体语言的静态方法的OGNL表达式:
article.text=@myapp.translator.Translator@translateToFrench({0})
Messages_es.properties中的等价条目:
article.text=@myapp.translator.Translator@translateToSpanish({0})
可以根据locale先创建用于运算表达式的标记片段,本例中,先通过预处理选择表达式,然后让Thymeleaf处理这个选择出来的表达式:
<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>
对于locale为French的情况,上面的表达式经过预处理后,得出的等价物如下:
<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>
设置属性值
-
th:attr任何属性值
<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>
多个属性一起设置
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
-
设置指定属性
<input type="submit" value="Subscribe me!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
<li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>
所有的指定属性:
| |th:abbr
|th:accept
|th:accept-charset
| |th:accesskey
|th:action
|th:align
| |th:alt
|th:archive
|th:audio
| |th:autocomplete
|th:axis
|th:background
| |th:bgcolor
|th:border
|th:cellpadding
| |th:cellspacing
|th:challenge
|th:charset
| |th:cite
|th:class
|th:classid
| |th:codebase
|th:codetype
|th:cols
| |th:colspan
|th:compact
|th:content
| |th:contenteditable
|th:contextmenu
|th:data
| |th:datetime
|th:dir
|th:draggable
| |th:dropzone
|th:enctype
|th:for
| |th:form
|th:formaction
|th:formenctype
| |th:formmethod
|th:formtarget
|th:frame
| |th:frameborder
|th:headers
|th:height
| |th:high
|th:href
|th:hreflang
| |th:hspace
|th:http-equiv
|th:icon
| |th:id
|th:keytype
|th:kind
| |th:label
|th:lang
|th:list
| |th:longdesc
|th:low
|th:manifest
| |th:marginheight
|th:marginwidth
|th:max
| |th:maxlength
|th:media
|th:method
| |th:min
|th:name
|th:optimum
| |th:pattern
|th:placeholder
|th:poster
| |th:preload
|th:radiogroup
|th:rel
| |th:rev
|th:rows
|th:rowspan
| |th:rules
|th:sandbox
|th:scheme
| |th:scope
|th:scrolling
|th:size
| |th:sizes
|th:span
|th:spellcheck
| |th:src
|th:srclang
|th:standby
| |th:start
|th:step
|th:style
| |th:summary
|th:tabindex
|th:target
| |th:title
|th:type
|th:usemap
| |th:value
|th:valuetype
|th:vspace
| |th:width
|th:wrap
|th:xmlbase
| |th:xmllang
|th:xmlspace
| |
- 追加
<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />
<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">
修复的布尔属性
<input type="checkbox" name="active" th:checked="${user.active}" />
所有修复的布尔属性:
-| |th:async
|th:autofocus
|th:autoplay
| |th:checked
|th:controls
|th:declare
| |th:default
|th:defer
|th:disabled
| |th:formnovalidate
|th:hidden
|th:ismap
| |th:loop
|th:multiple
|th:novalidate
| |th:nowrap
|th:open
|th:pubdate
| |th:readonly
|th:required
|th:reversed
| |th:scoped
|th:seamless
|th:selected
|
- HTML5友好的属性及元素名
<table> <tr data-th-each="user : ${users}"> <td data-th-text="${user.login}">...</td> <td data-th-text="${user.name}">...</td> </tr> </table>
data-{prefix}-{name}是编写HTML5自定义属性的标准语法,不需要开发者使用th:*这样的命名空间,Thymeleaf让这种语法自动对所有dialect都可用。
遍历
基础
<tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
可遍历的对象:实现
java.util.Iterable、
java.util.Map(遍历时取
java.util.Map.Entry
)、array、任何对象都被当作只有对象自身一个元素的列表
状态
当前遍历索引,从0开始,index属性
当前遍历索引,从1开始,count属性
总元素数量,size属性
每一次遍历的iter变量,current属性
当前遍历是even还是odd,even/odd布尔属性
当前遍历是第一个,first布尔属性
当前遍历是最后一个,last布尔属性
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
若不指定状态变量,Thymeleaf会默认生成一个名为“变量名Stat”的状态变量:
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
条件运算
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> <td> <span th:text="${#lists.size(prod.comments)}">2</span> comment/s <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a> </td> </tr>
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a>
th:if不只运算布尔条件,它对以下情况也运算为true:
值不为null
值为boolean且为true
值为数字且非0
值为字符且非0
值是字符串且不是:“false”,“off”,“no”
值不是boolean、数字、字符、字符串
如果值为null,则th:if运算结果为false
<a href="comments.html" th:href="@{/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a>
th:if的反面是th:unless
<div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p> </div>
<div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p> <p th:case="*">User is some other thing</p> </div>