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 implements IContext
  • org.thymeleaf.context.WebContext implements IWebContext

 

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} &gt; 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>
posted @ 2016-05-14 02:03  Mark.Chan  阅读(1166)  评论(0编辑  收藏  举报