基于 3.0.9.RELEASE的文档,部分总结,不全。
目录
1,简单表达式
1.1,变量表达式${}
变量表达式是对包含在上下文的变量map上执行的OGNL表达式。在Spring中,支持mvc的应用程序OGNL将被SpringEL所取代,但它的语法与OGNL非常相似。
从OGNL的语法中,我们知道:
<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>
实际上等价于:
ctx.getVariable("today");
不过,OGNL允许创建更强大的表达式,这就是为什么下面的表达式:
<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
通过执行以下命令,能够获得用户名:
((User) ctx.getVariable("session").get("user")).getName();
但是,getter方法的调用方式只是OGNL的特性之一。
1.1.1,基本表达式对象
当在上下文变量上计算OGNL表达式时,为了获得更高的灵活性,可以将一些对象提供给表达式。这些对象将通过#引用(按照OGNL标准)。
#ctx,the context object.
#vars,the context variables.
#locale,the context locale.
#request,(only in Web Contexts) the HttpServletRequest object.
#response,(only in Web Contexts) the HttpServletResponse object.
#session,(only in Web Contexts) the HttpSession object.
#servletContext,(only in Web Contexts) the ServletContext object.
例如:
Established locale country: <span th:text="${#locale.country}">US</span>.
1.1.2,工具表达式对象
除了基本对象之外,Thymeleaf还提供了一组实用程序对象,帮助我们在表达式中执行常见任务。
#execInfo,information about the template being processed.
#messages,methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
#uris,methods for escaping parts of URLs/URIs
#conversions,methods for executing the configured conversion service (if any).
#dates,methods for java.util.Date objects: formatting, component extraction, etc.
#calendars,analogous to #dates, but for java.util.Calendar objects.
#numbers,methods for formatting numeric objects.
#strings,methods for String objects: contains, startsWith, prepending/appending, etc.
#objects,methods for objects in general.
#bools,methods for boolean evaluation.
#arrays,methods for arrays.
#lists,methods for lists.
#sets,methods for sets.
#maps,methods for maps.
#aggregates,methods for creating aggregates on arrays or collections.
#ids,methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
例子:
<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>
其中,"13 february 2011"只是对today变量的说明,不管today的值是否为空,today都不会被其替换掉。若要使用默认表达式,可用下例:
<p>Today is: <span th:text="(${test} == null) ? 'test为空的默认值' : 'test不为空的默认值'">13 february 2011</span>.</p>
1.2,选择变量表达式*{}
变量表达式不仅可以写成${},还可以写成*{}。
不过,有一个重要区别:星号语法计算的是选定对象的表达式,而不是整个上下文。也就是说,只要没有选定对象,【$】和【*】语法就完全相同。
使用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>
1.3,消息(i18n)表达式#{}
消息表达式(通常称为文本外部化,国际化或i18n)允许从外部源(如:.properties)文件中检索特定于语言环境的消息,通过键来引用消息内容。
在Spring应用程序中,它将自动与Spring的MessageSource机制集成。如下:
#{main.title}
#{message.entrycreated(${entryId})}
以下是在模板中使用的方式:
<table>
...
<th th:text="#{header.address.city}">...</th>
<th th:text="#{header.address.country}">...</th>
...
</table>
请注意,如果希望消息键由上下文变量的值确定,或者希望将变量指定为参数,则可以在消息表达式中使用变量表达式:
#{${config.adminWelcomeKey}(${session.user.name})}
1.4,链接(URL)表达式@{}
由于URL的重要性,它们是web应用程序模板中的头等元素,Thymeleaf标准方言有特殊的语法处理。
下面是不同类型的URL:
- 绝对地址的URLs,如http://www.thymeleaf.org
- 相对地址的URLs,可以是:
- Page-relative,如,user/login.html
- Context-relative,如,/itemdetails?id=3(服务端自动会加上下文名称)
- Server-relative,如,~/billing/processInvoice(允许在同一服务器的不同上下文[如application]中调用这些URL)
- Protocol-relative URLs,如,//code.jquery.com/jquery-2.0.3.min.js
这些表达式的实际处理以及它们对将要输出的url的转换是通过实现org.thymeleaf.linkbuilder.ILinkBuilder接口完成的,该接口注册到使用ITemplateEngine的对象上。
默认情况下,这个接口的单个实现注册了org.thymeleaf.linkbuilder.StandardLinkBuilder类,这对于脱机(非web)和基于Servlet API的web场景都足够了。其它场景(比如与非servlet api web框架的集成)可能需要链接构建器接口的特定实现。
让我们使用这个新语法。满足th:href的属性:
<!-- 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>
注意:
- th:href是一个修饰符属性:处理完之后,它将计算要使用的链接URL,并将该值设置为<a>标记的href属性。
- 可为URL参数使用表达式(在orderId=${o.id}中),所需的URL参数编码操作也会自动执行。
- 用逗号分隔多个参数:@{/order/process(execId=${execId},execType='FAST')}
- 在URL路径中允许使用变量模板:@{/order/{orderId}/details(orderId=${orderId})}
- 以/(如/order/details)开头的相对URL将自动以应用程序上下文名称作为前缀。
- 如果不启用cookie,或者还不知道,可能会将";jsessionid=.."后缀添加到相对URL中,以便保存会话。这就是所谓的URL重写,Thymeleaf允许您插入自己的重写过滤器,方法是通过Servlet API为每个URL使用response.encodeURL()机制。
- th:href属性允许在模板中(可选地)有一个正在工作的静态href属性,这样当为原型设计而直接打开模板链接时,仍然可以被浏览器导航。
和消息语法(#{…})一样,URL也可以作为计算另一个表达式的结果的基础元素:
<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>
1.4.1,首页菜单
在主页上为站点中的其它页面添加一个菜单。
<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>
1.4.2,服务器root相对url
可以使用额外的语法创建服务器root相对url(而不是上下文root相对url),以便链接到同一服务器中的不同上下文。这些url被指定为@{~/path/to/something}
1.5,片段表达式~{}
片段表达式是表示标记(markup)片段并在模板中移动它们的一种简单方法。可以复制它们,将它们作为参数传递给其它模板。
最常见的用法是使用th:insert或th:replace插入片段:
<div th:insert="~{commons :: main}">...</div>
它们可以在任何地方使用,就像任何其它变量一样:
<div th:with="frag=~{footer :: #main/text()}">
<p th:insert="${frag}">
</div>
在后面有一个专门介绍模板布局的部分,其中包括对片段表达式的更深入解释。
片段表达式可以有参数。
1.6,字面量
1.6.1,文本
文本文字只是在单引号之间指定的字符串。它们可以包含任何字符,但是要使用\'转义其中的任何单引号。
<p>
Now you are looking at a <span th:text="'working web application'">template file</span>.
</p>
1.6.2,数字
数字字面值就是:数字。
<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>
1.6.3,布尔值
true和false。
<div th:if="${user.isAdmin()} == false">...
在本例中,【== false】写在大括号外面,所以是Thymeleaf处理它。如果写在大括号内,就是OGNL/SpringEL处理。
<div th:if="${user.isAdmin() == false}"> ...
1.6.4,空值
可以用null字面量表示空值。
<div th:if="${variable.something} == null"> ...
1.6.5,文字令牌(Literal tokens)
数字、布尔和null值实际上是文字令牌的一种特殊情况。
这些tokens允许在标准表达式中做一点简化。它们的工作原理和文本文字('..')完全一样,但是只允许字母(A-Z和A-Z)、数字(0-9)、括号([和])、点(.)、连字符(-)和下划线(_),不能有空格,逗号等。
看上去简洁吗?令牌不需要任何引号,象这样:
<div th:class="content">...</div>
用以替代下面的写法:
<div th:class="'content'">...</div>
1.7,附加文本
只要是字面量,无论它们是文字,还是变量运算或消息表达式的结果,都可以很容易地使用【+】操作符追加:
<span th:text="'The name of the user is ' + ${user.name}">
1.8,字面量替换(Literal substitutions)
字面值替换允许对包含变量值的字符串进行简单格式化,而不需要在字面值后面加上'..'+'..'。必须用【|】包围被替换的内容,比如:
<span th:text="|Welcome to our application, ${user.name}!|">
等于:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
文字替换可以与其它类型的表达式组合:
<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">
只有变量/消息表达式(${},{},#{})可以在【|..|】做替换。其它的文字不行,如:布尔/数字标记,条件表达式等。
1.9,算术运算
算术运算符:+,-,*,/和%。
<div th:with="isEven=(${prodStat.count} % 2 == 0)">
注意,这些操作符也可用于OGNL内的变量表达式本身(在这种情况下,OGNL将执行算术操作,而不再是Thymeleaf的标准表达式引擎):
<div th:with="isEven=${prodStat.count % 2 == 0}">
有些算术操作符存在字面量别名:div(/),mod(%)。
1.10,比较运算
表达式中的值可以与>、<、>=和<=符号进行比较,并且可以使用==和!=操作符检查是否相等(或是否不等)。XML规定不应该在属性值中使用<和>符号,因此应该用<和>替换它们。
<div th:if="${prodStat.count} > 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
另一种更简单的方法是使用这些操作符的文本别名:gt(>)、lt(<)、ge(>=)、le(<=)、not(!),eq(==),neq/ne(!=)。
1.11,条件表达式
条件表达式仅用于根据条件(本身是另一个表达式)的计算结果来计算两个表达式中的一个。
示例片段:
<tr th:class="${row.even} ? 'even' : 'odd'">
...
</tr>
条件表达式的所有3个部分(条件,then和else)本身就是表达式,这意味着它们可以是变量(${},*{}),消息(#{}),url(@{})或文本('')。
条件表达式也可以用括号嵌套:
<tr th:class="${row.even} ? (${row.first} ? 'first' : 'even') : 'odd'">
...
</tr>
Else表达式也可以省略,如果条件为false,则返回null值:
<tr th:class="${row.even} ? 'alt'">
...
</tr>
1.12,缺省表达式(Elvis运算符)
缺省表达式是一种没有then部分的特殊条件值。它相当于Groovy等语言中的Elvis运算符,允许指定两个表达式:如果第一个表达式的值不等于null,就使用第一个表达式,如果它等于null,就使用第二个表达式。例如:
<div th:object="${session.user}">
...
<p>Age: <span th:text="*{age} ? : '(no age specified)'">27</span>.</p>
</div>
如你所见,操作符是【?:】,这里使用它来指定一个名称的默认值(文字值),前提是计算*{age}的结果为null。因此,它等于:
<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>
1.13,无操作令牌
无操作令牌由一个下划线符号(_)表示。
这个令牌背后的思想是让指定表达式的期望结果什么都不做,也就是说,正如可处理属性(例如th:text)根本不存在一样。
还有,允许开发人员使用原型文本作为默认值。例如:
<span th:text="${user.name} ? : 'no user authenticated'">...</span>
可以直接用'no user authenticated'作为原型文本,从设计的角度来看,这样的代码更加简洁和通用:
<span th:text="${user.name} ? : _">no user authenticated</span>
1.14,数据转换/格式化
Thymeleaf为变量(${})和选择变量(*{})表达式定义了双大括号({{}})语法,允许通过可配置的转换服务方法应用于数据转换。例如:
<td th:text="${{user.lastAccessDate}}">...</td>
注意到双括号了吗?:$ {{}}。它指示Thymeleaf将user.lastAccessDate表达式的结果传递给转换服务,并要求它在写入结果之前执行格式化操作(转换为字符串)。
假设user.lastAccessDate是java.util.Calendar类型,如果已经注册了一个转换服务(IStandardConversionService的实现),并且包含一个Calendar -> String的有效转换,则执行转换。
IStandardConversionService的默认实现(StandardConversionService类)对任何转换为String的对象执行.tostring()。
Thymeleaf-spring3和Thymeleaf-spring4集成包透明地将Thymeleaf的转换服务机制与Spring的转换服务集成在一起,这样,在Spring配置中声明的转换服务和格式化程序将自动提供给${{}}和{{}}表达式。
1.15,表达式预处理
除了这些用于表达式处理的特性之外,Thymeleaf还具有预处理表达式的特性。
预处理是在正常表达式之前执行的表达式,允许修改最终将执行的表达式。
预处理表达式与普通表达式完全相同,但被双下划线符号(如__${expression}__)包围。
假设有一个i18n的Messages_fr.properties入口项包含一个OGNL表达式,该表达式调用特定于语言的静态方法,如:
article.text=@myapp.translator.Translator@translateToFrench({0})
Messages_es.properties等价于:
article.text=@myapp.translator.Translator@translateToSpanish({0})
可以创建一个标记片段,根据语言环境来计算一个表达式或另一个表达式。为此,首先选择表达式(通过预处理),然后让Thymeleaf执行:
<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>
请注意,法语环境的预处理步骤将创建以下等效项:
<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>
预处理字符串【__】可以用【\_\_】在属性中转义。
2、设置属性值
2.1,为任意属性设值-th:attr
创建subscribe.html,一个带有form的模板,内容:
<form action="subscribe.html">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" />
</fieldset>
</form>
这个模板开始时更像一个静态原型,而不是web应用程序的模板。首先,表单中的action属性静态链接到模板文件本身,这样就没地方进行有用的URL重写。其次,submit按钮中的value属性使其显示英语文本,但我们希望它国际化。
使用th:attr属性,以及它改变标签属性值的能力,更改内容:
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>
th:attr只是接受一个表达式,将值赋给一个属性。在创建了相应的控制器和消息文件后,处理该文件的结果为:
<form action="/gtvg/subscribe">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="提交!"/>
</fieldset>
</form>
一次赋予多个值,用逗号分隔。
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
输出结果:
<img src="/gtgv/images/gtvglogo.png" title="Logo de Good Thymes" alt="Logo de Good Thymes" />
2.2,为指定属性设值-th:*
为属性赋值时,可能想这样:
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
但这是一个很难看的标记。在属性值中指定赋值可能非常实用,但这样做并不是创建模板最优雅的方式。
这也是Thymeleaf为什么在模板中很少使用attr的原因。通常,要使用其它th:*属性,其目标是设置特定的标记属性(而不是像th:attr这样的任意属性)。
例如,要设置value属性,就使用th:value:
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
这样看起来就好多了,现在,对表单中的action属性做同样的操作:
<form action="subscribe.html" th:action="@{/subscribe}">
类似的属性有很多,每一个都对应一个特定的HTML5属性:
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:fragment |
th:frame | th:frameborder | th:headers |
th:height | th:high | th:href |
th:hreflang | th:hspace | th:http-equiv |
th:icon | th:id | th:inline |
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:onabort | th:onafterprint |
th:onbeforeprint | th:onbeforeunload | th:onblur |
th:oncanplay | th:oncanplaythrough | th:onchange |
th:onclick | th:oncontextmenu | th:ondblclick |
th:ondrag | th:ondragend | th:ondragenter |
th:ondragleave | th:ondragover | th:ondragstart |
th:ondrop | th:ondurationchange | th:onemptied |
th:onended | th:onerror | th:onfocus |
th:onformchange | th:onforminput | th:onhashchange |
th:oninput | th:oninvalid | th:onkeydown |
th:onkeypress | th:onkeyup | th:onload |
th:onloadeddata | th:onloadedmetadata | th:onloadstart |
th:onmessage | th:onmousedown | th:onmousemove |
th:onmouseout | th:onmouseover | th:onmouseup |
th:onmousewheel | th:onoffline | th:ononline |
th:onpause | th:onplay | th:onplaying |
th:onpopstate | th:onprogress | th:onratechange |
th:onreadystatechange | th:onredo | th:onreset |
th:onresize | th:onscroll | th:onseeked |
th:onseeking | th:onselect | th:onshow |
th:onstalled | th:onstorage | th:onsubmit |
th:onsuspend | th:ontimeupdate | th:onundo |
th:onunload | th:onvolumechange | th:onwaiting |
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 |
2.3,同时设置多个值
有两个非常特殊的属性称为th:alt-title和th:lang-xmllang,它们可以同时将两个属性设置为相同的值。
- th:alt-title,同时设置alt和title。
- th:lang-xmllang,同时设置lang和xml:lang。
例如:
<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
等价于:
<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:title="#{logo}" th:alt="#{logo}" />
等价于:
<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:alt-title="#{logo}" />
2.4,附加和前置
th:attrappend和th:attrprepend属性将它们的计算结果附加到现有属性值上(后缀或前缀)。
例如,将要添加的CSS类的名称(不是设置,只是添加)存储到上下文变量中的某个按钮中。因为要使用的特定CSS类依赖于用户已产生的行为:
<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />
如果将这个模板的cssStyle变量设置为"warning",将得到:
<input type="button" value="Do it!" class="btn warning" />
在标准方言中也有两个附加属性th:classappend和th:styleappend,用于向元素中添加CSS类或style片段,而不覆盖现有的类:
<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">
2.5,固定值的布尔属性
HTML有布尔属性的概念,但没有具体的值,只要这个属性出现了,就意味着值是"true"。在XHTML中,这些属性也只接受"1"这个值,也就是它本身。
例如checked:
<input type="checkbox" name="option2" checked /> <!-- HTML -->
<input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->
在标准方言中包含的布尔属性可以通过计算条件来设置它的值,这样如果计算的结果为true,布尔属性将被设置为它的固定值,如果为假,就不会被设置:
<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 |
2.6,设置任何属性的默认值(默认属性处理器)
Thymeleaf提供了一个默认的属性处理器,它允许我们设置任何属性的值,即使在标准方言中没有为其定义特定的th:*处理器。
例如:
<span th:whatever="${user.name}">...</span>
它的结果是:
<span whatever="John Apricot">...</span>
2.7,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使这种语法自动适用于所有方言(不仅仅是标准方言)。
还有一个用于特定自定义标签的语法:{prefix}-{name},它遵循W3C自定义元素的规范。例如,上述方式可用于th:block元素(或者th-block)。
3、迭代器
3.1,属性th:each
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<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>
</table>
- ${prods},被称为被迭代的表达式或被迭代的变量。
- prod,被称为迭代变量,它的作用域是在<tr>内,意味着可用于内部的所有<td>。
3.2,迭代器的内部状态
- index,当前迭代器的索引,从0开始。
- count,当前迭代器的计数,从1开始。
- size,迭代器中的元素数量。
- current,当前迭代子变量。
- even/odd,布尔属性。
- first,布尔属性。
- last,布尔属性。
例如:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<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>
</table>
状态变量iterStat就是在th:each中定义的,方法是在iter变量后加上一个逗号。
上述模板的执行结果如下:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr class="odd">
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
</tr>
<tr>
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
</tr>
<tr class="odd">
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
</tr>
<tr>
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
</tr>
</table>
如果没有显式地设置状态变量,Thymeleaf会将Stat放到迭代变量的后面来创建一个。
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</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>
</tr>
</table>
3.3,延迟抓取数据优化性能
正常的:
<ul>
<li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>
延迟的:
<ul th:if="${condition}">
<li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>
4、条件求值
4.1,if和unless
例如,假设我们希望在产品表中显示一列,其中包含每个产品的评论数量,如果有评论,则显示该产品的评论详情页面的链接。
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</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>
</table>
模板展开后的结果:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr class="odd">
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
<td>
<span>2</span> comment/s
<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
<td>
<span>1</span> comment/s
<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
</table>
th:if属性不仅仅计算布尔条件,它还会多做些,它按照以下规则将指定的表达式计算为true:
如果value非空。
1、如果是布尔型,并且是true。
2、如果是数值型,并且非0。
3、如果是字符型,并且非0。
4、如果是string型,并且不是"false","off"或"no"。
5、如果不是以上4种。
如果value是空的,就直接返回false。
th:if还有一个逆属性th:unless,执行逻辑与if相反。
4.2,swith
与Java中的switch基本相同,th:switch / th:case属性集。
<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>
th:case="*",是默认分支。
5、模板布局
5.1,引入模板片段
5.1.1,定义和引用片段
通过th:fragment定义片段,如footer.html。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>
这个片段名为"copy",使用th:insert或th:replace将它包含在需要的地方。
<body>
...
<div th:insert="~{footer :: copy}"></div>
</body>
【~{,}】是可选的,因此上面的代码等于:
<body>
...
<div th:insert="footer :: copy"></div>
</body>
5.1.2,片段规范语法
片段定义语法有3中不同的格式。
- "~{templatename::selector}",在模板templatename上应用指定的标记选择器生成的片段。注意,选择器可以只是一个片段名,因此可以指定一些简单的东西,比如上面的~{templatename::fragmentname}。标记选择器语法由底层AttoParser解析库定义,类似于XPath表达式或CSS选择器。
- "~{templatename}",包含名为templatename的完整模板。
- "~{::selector}"或"~{this::selector}",根据指定的selector从当前模板中插入片段。如果在模板上没有找到匹配的选择器,那么模板调用(插入)栈将一直遍历到最初处理的模板(根)处,直到选择器在某种程度上匹配到为止。
上面提到的templatename和selector都可以是全特性表达式(甚至是条件语句)。
<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>
5.1.3,无th:fragment引用片段
<div id="copy-section">
© 2011 The Good Thymes Virtual Grocery
</div>
引用方式:
<div th:insert="~{footer :: #copy-section}"></div>
5.1.4,th:insert和th:replace的区别
- th:insert,简单地插入指定的片段作为其宿主标记的主体。
- th:replace,用指定的片段替换它的宿主标记。
例如:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
引入:
<body>
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
</body>
结果:
<body>
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</body>
5.2,可参数化的片段签名
为使创建模板片段更象函数定义,可在模板定义时指定一组参数。
<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>
使用方式:
<div th:replace="::frag (${value1},${value2})">...</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>
注意,在最后一个选项中顺序并不重要:
<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>
5.2.1,无片段参数的片段局部变量
即使片段定义没有参数:
<div th:fragment="frag">
...
</div>
仍然可使用上面的第二种语法来调用它们(并且只使用第二种语法):
<div th:replace="::frag (onevar=${value1},twovar=${value2})">
结果等价于th:replace和th:with的组合:
<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">
注意,这个片段的局部变量规范--不管它是否具有参数签名--不会导致上下文在执行之前被清空。片段仍然能够访问调用模板中使用的每个上下文变量,就像现在一样。
5.2.2,th:assert模板内断言
可以指定一个逗号分隔的表达式列表,会对所有表达式求值,并对每个求值生成true,否则会引发异常。
<div th:assert="${onevar},(${twovar} != 43)">...</div>
这有助于在片段签名处验证参数:
<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>
5.3,灵活的布局:不仅是插入片段
多亏了片段表达式,我们可以为片段指定参数,这些片段不是文本、数字、bean对象,而是标记片段。
这使我们能够以一种方式创建片段,这样就可以使用来自调用模板的标记使片段更丰富多样,从而形成非常灵活的模板布局机制。
注意下面片段中的title和links变量的使用:
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">The awesome application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!--/* Per-page placeholder for additional links */-->
<th:block th:replace="${links}" />
</head>
现在可以这样调用这个片段:
<head th:replace="base :: common_header(~{::title},~{::link})">
<title>Awesome - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
结果将使用调用模板中的实际<title>和<link>标签作为title和links变量的值,从而导致片段在插入期间可被定制。
<head>
<title>Awesome - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/awe/css/bootstrap.min.css">
<link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">
</head>
5.3.1,使用空片段
一个特殊的片段表达式,空片段(~{}),可以用来指定没有标记。使用前面的例子:
<head th:replace="base :: common_header(~{::title},~{})">
<title>Awesome - Main</title>
</head>
注意,片段(links)的第二个参数是如何设置为空片段的以及<th:block th:replace="${links}" />块是如何没写如何东西的。
<head>
<title>Awesome - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
</head>
5.3.2,使用空操作令牌
如果我们只想让fragment使用它的当前标记作为默认值,那么no-op也可以用作fragment的参数。同样,使用common_header示例:
<head th:replace="base :: common_header(_,~{::link})">
<title>Awesome - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
看看如何将title参数(common_header片段的第一个参数)设置为no-op(_),会导致片段的这一部分根本没有执行(title = no-operation):
<title th:replace="${title}">The awesome application</title>
因此,结果如下:
<head>
<title>The awesome application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/awe/css/bootstrap.min.css">
<link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">
</head>
5.3.3,高级带条件的片段
emtpy片段和no-operation令牌的可用性允许我们以一种非常简单和优雅的方式执行片段的条件插入。
例如,只有当用户是管理员时,才能插入common::adminhead片段,如果不是管理员,则不插入任何内容(emtpy片段):
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
还有,使用no-operation令牌的目的,就是只有在满足指定条件的情况下才插入片段,如果条件不满足,则不修改标记:
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
另外,如果配置了模板解析器来检查模板资源是否存在--通过它们的checkExistence标志--可以使用片段本身的存在作为默认操作中的条件:
<!-- The body of the <div> will be used if the "common :: salutation" fragment -->
<!-- does not exist (or is empty). -->
<div th:insert="~{common :: salutation} ?: _">
Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
5.4,删除模板片段
回到示例应用程序,让我们回顾一下产品列表模板的最后一个版本:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</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:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
</table>
这段代码正好可以作为一个模板,但是作为一个静态页面(当通过浏览器直接打开而Thymeleaf没有对其进行处理时),它并不是一个好的原型。
为什么呢?因为,尽管浏览器可以完美地显示出来,但是该表只有一行,而这一行有模拟数据。作为一个原型,它看起来不够真实...应该有更多的产品,需要更多行。那就再加些。
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</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:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</table>
好的,现在有了三个,对于原型来说肯定更好。当用Thymeleaf处理时会发生什么呢?结果如下:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
<td>
<span>2</span> comment/s
<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
<td>
<span>1</span> comment/s
<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</table>
最后两行竟然是模拟行!结果就是:迭代只在第一行起作用了,而Thymeleaf也没有理由去删除另外两行。
我们需要一种在模板处理过程中删除这两行代码的方法。让我们在第2、3个<tr>标签上使用th:remove。
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</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:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd" th:remove="all">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr th:remove="all">
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</table>
最终结果,符合预期:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
<td>
<span>2</span> comment/s
<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
<td>
<span>1</span> comment/s
<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
</table>
在属性中的all值是什么意思?th:remove有五种不同的行为方式,具体取决于它的值:
all,删除包含all的标签及其所有子标签。
body,不删除包含body的标签,而是删除它的所有子标签。
tag,删除包含tag的标签,但不删除它的子标签。
all-but-first,删除包含它的标签的所有子标签,第一个标签除外。
none,什么都不做。这个值对于动态评估很有用。
示例:
<table>
<thead>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
</thead>
<tbody th:remove="all-but-first">
<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:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
</tbody>
</table>
5.5,布局继承结构
为了能够将单个文件作为布局,可以使用片段。一个使用th:fragment和th:replace,拥有标题和内容的简单布局示例:
<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="${title}">Layout Title</title>
</head>
<body>
<h1>Layout H1</h1>
<div th:replace="${content}">
<p>Layout content</p>
</div>
<footer>
Layout footer
</footer>
</body>
</html>
这个示例声明了一个名为layout的片段,其中包含title和content作为参数。两者在页面上都将被替换,通过下面示例中提供的片段表达式继承它。
<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
<title>Page Title</title>
</head>
<body>
<section>
<p>Page content</p>
<div>Included on page</div>
</section>
</body>
</html>
在这个文件中,html标签将被布局所取代,但布局的title和content又将分别被title和section块所取代。
如果需要,布局可以由作为页眉和页脚的几个片段组成。
6,内联
6.1,表达式内联
尽管标准方言允许我们使用标记属性来做几乎所有的事情,但在某些情况下,我们更喜欢直接将表达式写入HTML文本。例如,我们更喜欢这样写:
<p>Hello, [[${session.user.name}]]!</p>
用于取代:
<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>
在Thymeleaf中可把[[]]或[()]之间的表达式视为内联表达式,并且在它们内部,我们可以使用任何在th:text或th:utext属性中有效的表达式。
注意,当[[]]对应th:text(即结果将被html转义),[()]对应th:utext时,不会执行任何html转义。因此,对于一个变量,如msg = 'This is <b>great!</b>',片段内容如下:
<p>The message is "[(${msg})]"</p>
结果是那些<b>标签没有被转义,所以:
<p>The message is "This is <b>great!</b>"</p>
如果要转义,就得象这样:
<p>The message is "[[${msg}]]"</p>
Html的转义结果:
<p>The message is "This is <b>great!</b>"</p>
注意,文本内联在所有标记的标签体内默认都是激活的,不是标签本身。
6.1.1,内联与自然模板对比
之所以不将内联方式作为文本标签的默认输出方式,是因为在浏览器中静态打开时,它们会逐字显示在html的文件中。这样就不能将它们作为设计原型使用了。
浏览器在不使用内联的情况下静态显示代码片段的区别:
Hello, Sebastian!
使用的情况:
Hello, [[${session.user.name}]]!
6.1.2,禁用内联
在某些情况下,我们确实想要输出[[]]或[()]序列,其内容不作为表达式处理。为此,要使用th:inline="none":
<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>
显示结果:
<p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>
6.2,文本内联
文本内联与表达式内联功能非常相似,实际上增加了更多功能,必须显式启用th:inline="text"。
文本内联不仅允许使用相同的内联表达式,而且可以像在文本模式中处理模板一样地处理标签的body,这允许我们执行基于文本的模板逻辑(不仅仅是输出表达式)。
6.3,JS内联
JavaScript内联允许在HTML模板模式下处理的模板中更好地集成JavaScript <script>块。
与文本内联一样,这实际上相当于将脚本内容当作JAVASCRIPT模板模式中的模板来处理,因此文本模板模式的所有功能都适用。在本节中,我们将重点讨论如何使用它将Thymeleaf表达式的输出添加到JavaScript块中。
这个模式必须显式启用th:inline="javascript":
<script th:inline="javascript">
...
var username = [[${session.user.name}]];
...
</script>
执行结果:
<script th:inline="javascript">
...
var username = "Sebastian \"Fruity\" Applejuice";
...
</script>
在上面的代码中需要注意两件重要的事情:
首先,JavaScript内联不仅会输出所需的文本,还会用引号和JavaScript转义符将其内容括起来,以便将表达式结果输出为格式良好的JavaScript文字。
其次,之所以会这样,是因为输出了转义后的${session.user.name}表达式,也就是用双括号表达式:[[${session.user.name}]]达到的效果。
如果用非转义的话,就像这样:
<script th:inline="javascript">
...
var username = [(${session.user.name})];
...
</script>
执行结果:
<script th:inline="javascript">
...
var username = Sebastian "Fruity" Applejuice;
...
</script>
这是难看的JavaScript代码。但如果通过添加内联表达式来构建脚本的某些部分,那么输出一些未转义的内容可能就是我们所需要的,所以手边有这个工具很好。
6.3.1,JS自然模板
JavaScript内联机制的能力远不止将JavaScript特有的转义和输出表达式结果作为有效的字面值这些事。
例如,可以将(转义的)内联表达式封装在JavaScript注释中,比如:
<script th:inline="javascript">
...
var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
...
</script>
Thymeleaf会忽略我们在注释后面和分号前面所写的所有内容(在本例中是'Gertrud Kiwifruit'),所以,执行这个操作的结果与不使用包装注释时完全一样:
<script th:inline="javascript">
...
var username = "Sebastian \"Fruity\" Applejuice";
...
</script>
但是,再仔细看上面使用内联的模板代码,这是有效的JavaScript代码。当以静态方式打开模板文件时(无需在服务器上执行),它将完美地执行。
6.3.2,高级内联计算和JS序列化
关于JavaScript内联,需要注意的一件重要事情是,这种表达式求值是智能的,并不局限于字符串。Thymeleaf会正确地用JavaScript语法编写以下对象:
- Strings
- Numbers
- Booleans
- Arrays
- Collections
- Maps
- Beans
例如,有如下代码:
<script th:inline="javascript">
...
var user = /*[[${session.user}]]*/ null;
...
</script>
${session.user}表达式将赋值给user对象,Thymeleaf将正确地把它转换为Javascript语法。
<script th:inline="javascript">
...
var user = {"age":null,"firstName":"John","lastName":"Apricot",
"name":"John Apricot","nationality":"Antarctica"};
...
</script>
这种JavaScript序列化的方式是通过org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer接口的实现来实现的,这个接口可以在模板引擎中使用的标准方言的实例中配置。
这个JS序列化机制的默认实现将在类路径中查找Jackson库,如果存在,就使用它。如果没有,就用一个内置的序列化机制来满足大多数场景的需求,并产生类似的结果(但是不那么灵活)。
6.4,CSS内联
Thymeleaf还允许在CSS <style>标签中使用内联,例如:
<style th:inline="css">
...
</style>
例如,有两个变量设置为两个不同的字符串值:
classname = 'main elems'
align = 'center'
可以像这样使用它们:
<style th:inline="css">
.[[${classname}]] {
text-align: [[${align}]];
}
</style>
结果如下:
<style th:inline="css">
.main\ elems {
text-align: center;
}
</style>
CSS内联就像JavaScript内联一样具备一定的智能,具体就是,通过[[${classname}]]这样的转义表达式输出的表达式将转义为CSS标识符。这就是为什么classname = 'main elems'在上面的代码片段中变成main\ elems的原因。
6.4.1,高级特性:CSS自然模板等
与之前对JavaScript内联的解释一样,CSS内联也允许<style>标签在静态和动态两种模式下工作,即通过在注释中封装内联表达式作为CSS的自然模板。
完。