1 - CH01-介绍

什么是 Thymeleaf

Thymeleaf 是面向 Web 和独立环境的现代服务器端 Java 模板引擎,能够处理 HTML、XML、JavaScript、CSS 甚至纯文本。

Thymeleaf 的主要目标是提供一个优雅和高度可维护的创建模板的方式。 为了实现这一点,它建立在自然模板( Natural Templates)的概念上,将其逻辑注入到模板文件中,不会影响模板被用作设计原型。 这改善了设计的沟通,弥合了设计和开发团队之间的差距。

Thymeleaf 的设计从一开始就遵从 Web 标准,特别是 HTML5,这样就能创建完全符合验证的模板。

处理模版

Thymeleaf 能处理以下 6 种类型的模版,我们称之为模版模式(Template Mode):

  • HTML
  • XML
  • TEXT
  • JAVASCRIPT
  • CSS
  • RAW

其中包含有两种标记模板模式(HTML和XML),三种文本模板模式(TEXT、JAVASCRIPT和CSS)和一个无操作模板模式(RAW)。

HTML 模板模式将允许任何类型的 HTML输入,包括HTML5、HTML4和XHTML。将不执行验证或对格式进行严格检查,这样就能尽可能的将模板代码/结构进行输出。

XML 模板模式将允许XML输入。在这种情况下,代码预期格式是良好的——没有未关闭的标签,没有引用属性等,如果找到为符合格式要求,解析器将抛出异常。请注意,不会执行验证(针对DTD或XML架构)。

TEXT 模板模式将允许对非标记性质的模板使用特殊语法。此类模板的示例可能是文本电子邮件或模板文档。请注意,HTML或XML模板也可以作为TEXT处理,在这种情况下,它们将不会被解析为标记,并且每个标签DOCTYPE、注释等将被视为纯文本。

JAVASCRIPT 模板模式将允许在Thymeleaf应用程序中处理JavaScript文件。这意味着能够使用JavaScript文件中的模型数据与HTML文件中可以完成的方式相同,但可以使用特定于JavaScript的集成,例如专门的转义或自然脚本(natural scripting)。 JAVASCRIPT模板模式被认为是文本模式,因此使用与TEXT模板模式相同的特殊语法。

CSS 模板模式将允许处理涉及Thymeleaf应用程序的CSS文件。与JAVASCRIPT模式类似,CSS模板模式也是文本模式,并使用TEXT模板模式下的特殊处理语法。

RAW 模板模式根本不会处理模板。它用于将未经修改的资源(文件、URL响应等)插入正在处理的模板中。例如,HTML格式的外部不受控制的资源可以包含在应用程序模板中,安全地知道这些资源可能包含的任何Thymeleaf代码将不会被执行。

标准方言

Thymeleaf是一个非常可扩展的模板引擎,实际上它更像是一个模板引擎框架(template engine framework)),允许您定义和自定义您的模板。

将一些逻辑应用于标记工件(例如标签、某些文本、注释或只有占位符)的一个对象被称为处理器(processor)。方言(dialect)通常包括这些处理器的集合以及一些额外的工件。Thymeleaf 的核心库提供了一种称为标准方言(Standard Dialect)的方言,提供给用户开箱即用的功能。

当然,如果用户希望在利用库的高级功能的同时定义自己的处理逻辑,用户也可以创建自己的方言(甚至扩展标准的方言)。也可以将Thymeleaf配置为同时使用几种方言。

官方的 thymeleaf-spring3 和 thymeleaf-spring4 集成包都定义了一种称为“SpringStandard Dialect”的方言,与标准方言大致相同,但是对于 Spring 框架中的某些功能则更加友好,例如, 想通过使用Spring Expression Language 或 SpringEL 而不是 OGNL。所以如果你是一个Spring MVC用户,这里的所有东西都能够在你的Spring应用程序中使用。

标准方言的大多数处理器是属性处理器。这样,即使在处理之前,浏览器也可以正确地显示HTML模板文件,因为它们将简单地忽略其他属性。对比JSP,在浏览器中会直接显示的代码片断:

<form:inputText name="userName" value="${user.name}" />

Thymeleaf 标准方言将允许我们实现与以下功能相同的功能::

<input type="text" name="userName" value="James Carrot" th:value="${user.name}" />

浏览器不仅可以正确显示这些信息,而且还可以(可选地)在浏览器中静态打开原型时显示的值(可选地)指定一个值属性(在这种情况下为“James Carrot”),将在模板处理期间由${user.name}的计算得到的值代替。

这有助于您的设计师和开发人员处理相同的模板文件,并减少将静态原型转换为工作模板文件所需的工作量。这样的功能是称为 自然模板(Natural Templating) 的功能.

2 - CH02-使用文本

先看一个页面示例:

<!DOCTYPE html>
<html 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>

首先,注意到的这个文件是HTML5的,可以由任何浏览器正确显示,因为它不包含任何非HTML标签(浏览器会忽略他们所不能理解的属性,如 th:text)。

但是您也可能会注意到,这个模板并不是一个真正有效的HTML5文档,因为HTML5规范不允许在th:*形式中使用这些非标准属性。 事实上,我们甚至在我们的 <html> 标签中添加了一个xmlns:th 属性,这也不属于 HTML5 语言:

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

它在模板处理中根本没有任何影响,但我们的 IDE 会提示诸如“缺少th:*属性命名空间定义”等字样的告警。

如果我们想让这些模板对于 HTML5 验证是有效的,需要做简单地修改,将属性语法,改为data-前缀,(:)改为 (-) 即可:

<!DOCTYPE html>
<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" data-th-href="@{/css/gtvg.css}" />
  </head>

  <body>
    <p data-th-text="#{home.welcome}">Welcome to our grocery store!</p>
  </body>
</html>

这样,data-前缀就符合 HTML5 中的规范了。

注:在实际开发过程中,由于th:*的辨识度往往必data-th-*这种方式更高,所以很多人仍然是选择使用th:*,这也是可以理解的。本书的示例大多也是采用th:*方式。

使用 th:text 和外部化文本

外部化文本是从模板文件中提取模板代码的片段,以便它们可以保存在单独的文件(通常为 .properties 文件)中,并且可以轻松地替换为使用其他语言编写的等效文本(称为国际化或简单的i18n) 。文本的外部化片段通常称为“消息(messages)”。

消息总是具有标识它们的 key,而Thymeleaf允许您指定文本应与 #{...}语法对应的特定消息:

<p th:text="#{home.welcome}">Welcome to our grocery store!</p>

我们在这里可以看到的其实是Thymeleaf标准方言的两个不同功能:

  • th:text属性,它评估其值表达式并将结果设置为主机标签的主体,有效地替换了代码中我们看到的“Welcome to our grocery store!”文本。
  • #{home.welcome}表达式指示 th:text 属性使用的文本应该是key 为home.welcome 所对应于的消息。

Thymeleaf中外部化文本的位置是完全可配置的,它将取决于正在使用的具体的 org.thymeleaf.messageresolver.IMessageResolver实现。通常,将使用基于 .properties文件的实现,但是如果我们想要(例如)从数据库获取消息,我们可以创建自己的实现。

但是,我们在初始化期间尚未为模板引擎指定消息解析器,这意味着我们的应用程序正在使用由org.thymeleaf.messageresolver.StandardMessageResolver实现的标准消息解析器。

标准消息解析器期望在 /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_es.properties 文件:

home.welcome=¡Bienvenido a nuestra tienda de comestibles!

上下文

为了执行模版,我们创建了HomeController类,它实现了 IGTVGController 接口:

public class HomeController implements IGTVGController {

    public void process(
            final HttpServletRequest request, final HttpServletResponse response,
            final ServletContext servletContext, final ITemplateEngine templateEngine)
            throws Exception {

        WebContext ctx = 
                new WebContext(request, response, servletContext, request.getLocale());

        templateEngine.process("home", ctx, response.getWriter());
    }
}

首先是 *上下文(context)*的创建。 Thymeleaf 上下文对象实现了 org.thymeleaf.context.IContext接口。上下文包含了执行模版引擎的所有数据变量 map,同时也引用了外部消息的区域设置。

public interface IContext {

    public Locale getLocale();
    public boolean containsVariable(final String name);
    public Set<String> getVariableNames();
    public Object getVariable(final String name);

}

这个接口有一个专门的扩展,org.thymeleaf.context.IWebContext,用于基于ServletAPI的Web应用程序(如SpringMVC)中。

public interface IWebContext extends IContext {
    public HttpServletRequest getRequest();
    public HttpServletResponse getResponse();
    public HttpSession getSession();
    public ServletContext getServletContext();
}

Thymeleaf 核心库提供了这些接口的每个实现:

  • org.thymeleaf.context.Context 实现了 IContext
  • org.thymeleaf.context.WebContext 实现了 IWebContext

而在控制器代码中可以看到,WebContext是我们使用的。 实际上我们必须,因为使用一个ServletContextTemplateResolver要求我们使用实现 IWebContext 的上下文。

WebContext ctx = new WebContext(request, response, servletContext, request.getLocale());

只需要这四个构造函数参数中的三个,因为如果没有指定,那么将使用系统的默认语言环境(尽管不应该在实际应用程序中发生)。

有一些专门的表达式,能够从我们的模板中的 WebContext 获取请求参数和请求、会话和应用程序属性。 例如:

  • ${x} 将返回存储在Thymeleaf上下文中的变量 x 或作为 请求属性(request attribute)
  • ${param.x} 将返回一个名为 x请求参数(request parameter)(可能是多值的)
  • ${session.x} 将返回一个名为 x会话属性(session attribute)
  • ${application.x} 将返回一个名为xservlet上下文属性 (servlet context attribute)

执行模版引擎

随着我们的上下文对象准备就绪,现在我们可以告诉模板引擎使用上下文来处理模板(通过它的名字),并传递一个响应写入器,以便可以将响应写入它:

templateEngine.process("home", ctx, response.getWriter());

在西班牙环境下,输出如下 :

<!DOCTYPE html>
<html>
  <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>

非转义文本

如果我们的消息中包含特殊字符,比如html标签,如下

home.welcome=Welcome to our <b>fantastic</b> grocery store!

那么,执行模版,将会得到如下输出:

<p>Welcome to our &lt;b&gt;fantastic&lt;/b&gt; grocery store!</p>

这是th:text 属性的默认行为。 但我们希望 Thymeleaf 能如实输出HTML标签,我们可以使用th:utext,即“unescaped text(非转义文本)”:

<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>

这样就能输出符合我们预期的文本了:

<p>Welcome to our <b>fantastic</b> grocery store!</p>

使用和显示变量

我们控制器如下:

public void process(
            final HttpServletRequest request, final HttpServletResponse response,
            final ServletContext servletContext, final ITemplateEngine templateEngine)
            throws Exception {

    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());

}

添加了一个名为 todayString 变量到我们的上下文中,我们在模版中显示:

<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>

正如你所看到的,我们仍然使用th:text属性,但是这个时候语法有点不同,不是 #{...},我们使用 ${...}。 这是一个变量表达式,它包含一个名为OGNL(Object-Graph Navigation Language)的表达式。

${today} 表达式只是意味着“获取名为 today 的变量”,但是这些表达式可能可以更复杂(例如 ${user.name} 表示变量为 user的变量,调用其 getName() 方法。

3 - CH03-表达式

Thymeleaf 标准表达式( Standard Expression)语法,是 Thymeleaf 标准方言(Standard Dialect)的最重要组成部分之一。

标准表达式

Thymeleaf 提供了多种标准表达式包括:

  • 简单表达式:
    • Variable expressions(变量表达式)${...}
    • Selection expressions(选择表达式)*{...}
    • Message (i18n) expressions(消息表达式) #{...}
    • Link (URL) expressions(链接表达式)@{...}
    • Fragment expressions(片段表达式)~{...}
  • 字面量:
    • 文本:'one text''Another one!'等;
    • 数值:0、34、3.0、12.3 等;
    • 布尔:true、false
    • Null:null
    • Literal token(字面标记): one、sometext、 main等;
  • 文本操作:
    • 字符串拼接:+
    • 文本替换:|The name is ${name}|
  • 算术操作:
    • 二元运算符:+-*/%
    • 减号(单目运算符):-
  • 布尔操作:
    • 二元运算符:andor
    • 布尔否定(一元运算符):!not
  • 比较和等价:
    • 比较:><>=<=gtltgele
    • 等价:==!=eqne
  • 条件运算符:
    • If-then:(if) ? (then)
    • If-then-else:(if) ? (then) : (else)
    • Default:(value) ?: (defaultvalue)
  • 特殊标记:
    • No-Operation(无操作):_

下面的这个示例,涵盖了上述大部分表达式:

'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type}

消息表达式

消息表达式(通常称为文本外化、国际化或i18n)允许我们从外部源(.properties文件)检索特定于语言环境的消息,通过 key 引用它们(可选)应用一组参数。

在Spring应用程序中,这将自动与Spring的MessageSource机制集成。

#{main.title}

#{message.entrycreated(${entryId})}

在模版中的应用如下:

<table>
  ...
  <th th:text="#{header.address.city}">...</th>
  <th th:text="#{header.address.country}">...</th>
  ...
</table>

请注意,如果希望消息 key 由上下文变量的值确定,或者要将变量指定为参数,则可以在消息表达式中使用变量表达式:

#{${config.adminWelcomeKey}(${session.user.name})}

变量表达式

变量表达式可以是OGNL表达式或者是 Spring EL,如果集成了Spring的话,可以在上下文变量(context variables )中执行。

有关OGNL语法和功能的详细信息,请阅读OGNL语言指南 在 Spring MVC 启用的应用程序中,OGNL将被替换为SpringEL,但其语法与OGNL非常相似(实际上,在大多数常见情况下完全相同)。

在Spring术语中,变量表达式也称为模型属性(model attributes)。 他们看起来像这样:

${session.user.name}

他们作为属性值或作为属性的一部分:

<span th:text="${book.author.name}">

上面的表达式在在OGNL和SpringEL中等价于:

((Book)context.getVariable("book")).getAuthor().getName()

这些变量表达式不仅涉及输出,还包括更复杂的处理,如条件判断、迭代等:

<li th:each="book : ${books}">

这里${books}从上下文中选择名为books的变量,并将其评估为可在th:each循环中使用的迭代器(iterable)。

更多 OGNL 的功能有:

/*
 * 使用点(.)来访问属性,等价于调用属性的  getter 
 */
${person.father.name}

/*
 * 访问属性也可以使用([])块
 */
${person['father']['name']}

/*
 * 如果对象是一个map,则点和块语法等价于调用其get(...)方法
 */
${countriesByCode.ES}
${personsByName['Stephen Zucchini'].age}

/*
 * 在块语法中,也可以通过索引来访问数组或者集合
 */
${personsArray[0].name}

/*
 * 可以调用方法,同时也支持参数
 */
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}

表达式预设对象

当对上下文变量评估 OGNL 表达式时,某些对象可用于表达式以获得更高的灵活性。 这些对象将被引用(按照OGNL标准),以符号开始:

  • #ctx:上下文对象。
  • #vars:上下文变量。
  • #locale:上下文区域设置。
  • #request:HttpServletRequest 对象(仅在 Web 上下文中)。
  • #response:HttpServletResponse 对象(仅在 Web 上下文中)。
  • #session:HttpSession对象(仅在 Web 上下文中)。
  • #servletContext:ServletContext对象(仅在 Web 上下文中)。

所以我们可以这样做:

Established locale country: <span th:text="${#locale.country}">US</span>.

完整内容可以参考后文[表达式基本对象]部分。

表达式工具对象

除了上面这些基本的对象之外,Thymeleaf 将为我们提供一组工具对象,这些对象将帮助我们在表达式中执行常见任务:

  • #execInfo: 模版执行的信息
  • #messages: 在变量内获取外部消息的方法 表达式,与使用#{...}语法获得的方式相同。.
  • #uris: 用于转义 URL/URI 部分的方法
  • #conversions: 执行已配置的 conversion service
  • #dates: java.util.Date对象的方法,比如格式化,组件提取等
  • #calendars:类似于#dates,但是对应于java.util.Calendar对象
  • #numbers: 格式化数字对象的方法。
  • #strings: String对象的方法,包括 contains、startsWith、prepending/appending等 等等
  • #objects: 对象通常的方法
  • #bools: 布尔判断的方法
  • #arrays: array 方法
  • #lists: list 方法
  • #sets: set 方法
  • #maps: map 方法
  • #aggregates:在数组或集合上创建聚合的方法
  • #ids: 用于处理可能重复的id属性的方法(例如,作为迭代的结果)。

下面是一个格式化日期的例子:

<p>
  Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>

完整内容可以参考后文[表达式工具对象]部分。

选择表达式

选择表达式与变量表达式很像,区别在于它们是在当前选择的对象而不是整个上下文变量映射上执行。 他们看起来像这样:

*{customer.name}

它们所作用的对象由th:object属性指定:

<div th:object="${book}">
  ...
  <span th:text="*{title}">...</span>
  ...
</div>

这等价于:

{
  // th:object="${book}"
  final Book selection = (Book) context.getVariable("book");
  // th:text="*{title}"
  output(selection.getTitle());
}

链接表达式

链接表达式旨在构建 URL 并向其添加有用的上下文和会话信息(通常称为URL重写的过程)。

因此,对于部署在Web服务器的/myapp上下文中的Web应用程序,可以使用以下表达式:

<a th:href="@{/order/list}">...</a>

可以转成:

<a href="/myapp/order/list">...</a>

cookie没有启用下,如果我们需要保持会话,可以这样:

<a href="/myapp/order/list;jsessionid=23fa31abd41ea093">...</a>

URL 可以携带参数:

<a th:href="@{/order/details(id=${orderId},type=${orderType})}">...</a>

结果如下:

<!-- Note ampersands (&) should be HTML-escaped in tag attributes... -->
<a href="/myapp/order/details?id=23&amp;type=online">...</a>

链接表达式可以是相对的,在这种情况下,应用程序上下文将不会作为URL的前缀:

<a th:href="@{../documents/report}">...</a>

也 可以是服务器相对(同样,没有应用程序上下文前缀):

<a th:href="@{~/contents/main}">...</a>

和协议相对(就像绝对URL,但浏览器将使用在显示的页面中使用的相同的HTTP或HTTPS协议):

<a th:href="@{//static.mycompany.com/res/initial}">...</a>

当然,Link表达式可以是绝对的:

<a th:href="@{http://www.mycompany.com/main}">...</a>

在绝对(或协议相对)的URL等里面,Thymeleaf链接表达式添加的是什么值? 答案是,可能是由响应过滤器定义的URL重写。在基于Servlet的Web应用程序中,对于每个输出的URL(上下文相对、相对、绝对…)Thymeleaf将总是调用HttpServletResponse.encodeUrl(…) 机制 在显示URL之前。 这意味着过滤器可以通过包装HttpServletResponse对象(通常使用的机制)来为应用程序执行定制的URL重写。

片段表达式

片段表达式是 3.x 版本新增的内容。

片段段表达式是一种表示标记片段并将其移动到模板周围的简单方法。 正是由于这些表达式,片段可以被复制,或者作为参数传递给其他模板等等。

最常见的用法是使用th:insertth:replace:插入片段:

<div th:insert="~{commons :: main}">...</div>

但是它们可以在任何地方使用,就像任何其他变量一样:

<div th:with="frag=~{footer :: #main/text()}">
  <p th:insert="${frag}">
</div>

片段表达式可以有参数。

字面量

本文

文本文字只是在单引号之间指定的字符串。 他们可以包含任何字符,但您应该避免其中的任何单引号使用\'

<p>
  Now you are looking at a <span th:text="'working web application'">template file</span>.
</p>

数字

数字文字就是数字。

<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>

布尔

布尔文字为“true”和“false”。 例如::

<div th:if="${user.isAdmin()} == false"> ...

在这个例子中,== false写在大括号之外,Thymeleaf 会做处理。如果是写在大括号内,那就是由 OGNL/SpringEL 引擎负责处理:

<div th:if="${user.isAdmin() == false}"> ...

null

null 字面量使用如下:

<div th:if="${variable.something} == null"> ...

字面量标记

数字、布尔和 null 字面实际上是*字面量标记(literal tokens)*的特殊情况。

这些标记允许在标准表达式中进行一点简化。 他们工作与文字文字('...')完全相同,但只允许使用字母(A-Z)和a-z'),数字(0-9),括号([]),点(.),连字符(-) 和下划线(_`)。 所以没有空白,没有逗号等

标记不需任何引号。 所以我们可以这样做:

<div th:class="content">...</div>

用来代替:

<div th:class="'content'">...</div>

附加文本

无论是文字,还是评估变量或消息表达式的结果,都可以使用 + 操作符轻松地附加文本:  

<span th:text="'The name of the user is ' + ${user.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}|">

|...| 字面量替换只允许使用变量/消息表达式(${...}, *{...}, #{...}), 其他字面量 ('...')、布尔/数字标记、条件表达式等是不允许的

算术运算

支持算术运算:+, -, *, /%

<div th:with="isEven=(${prodStat.count} % 2 == 0)">

请注意,这些运算符也可以在OGNL变量表达式本身中应用(在这种情况下将由OGNL执行,而不是Thymeleaf标准表达式引擎):

<div th:with="isEven=${prodStat.count % 2 == 0}">

请注意,其中一些运算符存在文本别名:div (/)、 mod (%)。

比较与相等

表达式中的值可以与>, <, >=<= 号进行比较,并且可以使用==!= 运算符来检查是否相等。 请注意, <>符号不应该在XML属性值中使用,因此它们应被替换为<>

<div th:if="${prodStat.count} &gt; 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">

一个更简单的替代方案可能是使用一些这些运算符存在的文本别名:gt (>), lt (<), ge (>=), le (<=), not (!). eq (==), neq/ne (!=)

条件表达式

条件表达式仅用于评估两个表达式中的一个,这取决于评估条件(本身就是另一个表达式)的结果。

我们来看一个示例 th:class 片段 :

<tr th:class="${row.even}? 'even' : 'odd'">
  ...
</tr>

条件表达式(condition,then和else)的所有三个部分都是自己的表达式,这意味着它们可以是变量(${...}, *{...}),消息 (#{...}) ,(@{...}) 或字面量('...')。

条件表达式也可以使用括号嵌套:

<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
  ...
</tr>

else表达式也可以省略,在这种情况下,如果条件为false,则返回null值:

<tr th:class="${row.even}? 'alt'">
  ...
</tr>

默认表达式

默认表达式(default expression)是一种特殊的条件值,没有then 部分。它相当于某些语言中的Elvis operator存在,比如 Groovy。指定两个表达式,如果第一个不是 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>

无操作标记

无操作标记由下划线符号(_)表示。表示什么也不做,这允许开发人员使用原型中的文本默认值。 例如,:

<span th:text="${user.name} ?: 'no user authenticated'">...</span>

我们可以直接使用*’no user authenticated’* 作为原型文本,这样代码从设计的角度来看起来很简洁:

<span th:text="${user.name} ?: _">no user authenticated</span>

数据转换及格式化

Thymeleaf 的双大括号为变量表达式($ {...})和选择表达式(* {...})提供了数据转换服务

它看上去是这样的:

<td th:text="${{user.lastAccessDate}}">...</td>

注意到双括号吗?:$ {{...}}。这意味着Thymeleaf可以通过转换服务将结果转换为String

假设user.lastAccessDate类型为java.util.Calendar,如果转换服务 (“IStandardConversionService”的实现)已经被注册并且包含有效的Calendar - > String的 转换,则它将被应用。

“IStandardConversionService”(“StandardConversionService”)的默认实现类)只需在转换为“String”的任何对象上执行.toString()。有关更多信息如何注册一个自定义转换服务实现,看看[更多配置](#more-on-configuration)部分。

表达式预处理

表达式预处理(expression preprocessing),它被定义在下划线_之间:

#{selection.__${sel.code}__}

我们看到的变量表达式${sel.code}将先被执行,假如结果是"ALL",那么_之间的值"ALL"将被看做表达式的一部分被执行,在这里会变成selection.ALL

4 - CH04-设置属性值

设置任意属性值

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="¡Suscríbe!"/>
  </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" />

设置值到指定的属性

现在,你可能会想到像:

<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>

上面可以指定一个属性的值,但看上去有点丑陋,且并不是最优雅的方式。 通常,你将使用其他任务的th:*属性 设置特定的标签属性(而不仅仅是像“th:attr”这样的任意属性)。

例如,要设置value属性,使用th:value

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>

要设置 action 属性,使用th:action

<form action="subscribe.html" th:action="@{/subscribe}">

Thymeleaf 提供了很多属性,每个都针对特定的HTML5属性:

th:abbrth:acceptth:accept-charset
th:accesskeyth:actionth:align
th:altth:archiveth:audio
th:autocompleteth:axisth:background
th:bgcolorth:borderth:cellpadding
th:cellspacingth:challengeth:charset
th:citeth:classth:classid
th:codebaseth:codetypeth:cols
th:colspanth:compactth:content
th:contenteditableth:contextmenuth:data
th:datetimeth:dirth:draggable
th:dropzoneth:enctypeth:for
th:formth:formactionth:formenctype
th:formmethodth:formtargetth:fragment
th:frameth:frameborderth:headers
th:heightth:highth:href
th:hreflangth:hspaceth:http-equiv
th:iconth:idth:inline
th:keytypeth:kindth:label
th:langth:listth:longdesc
th:lowth:manifestth:marginheight
th:marginwidthth:maxth:maxlength
th:mediath:methodth:min
th:nameth:onabortth:onafterprint
th:onbeforeprintth:onbeforeunloadth:onblur
th:oncanplayth:oncanplaythroughth:onchange
th:onclickth:oncontextmenuth:ondblclick
th:ondragth:ondragendth:ondragenter
th:ondragleaveth:ondragoverth:ondragstart
th:ondropth:ondurationchangeth:onemptied
th:onendedth:onerrorth:onfocus
th:onformchangeth:onforminputth:onhashchange
th:oninputth:oninvalidth:onkeydown
th:onkeypressth:onkeyupth:onload
th:onloadeddatath:onloadedmetadatath:onloadstart
th:onmessageth:onmousedownth:onmousemove
th:onmouseoutth:onmouseoverth:onmouseup
th:onmousewheelth:onofflineth:ononline
th:onpauseth:onplayth:onplaying
th:onpopstateth:onprogressth:onratechange
th:onreadystatechangeth:onredoth:onreset
th:onresizeth:onscrollth:onseeked
th:onseekingth:onselectth:onshow
th:onstalledth:onstorageth:onsubmit
th:onsuspendth:ontimeupdateth:onundo
th:onunloadth:onvolumechangeth:onwaiting
th:optimumth:patternth:placeholder
th:posterth:preloadth:radiogroup
th:relth:revth:rows
th:rowspanth:rulesth:sandbox
th:schemeth:scopeth:scrolling
th:sizeth:sizesth:span
th:spellcheckth:srcth:srclang
th:standbyth:startth:step
th:styleth:summaryth:tabindex
th:targetth:titleth:type
th:usemapth:valueth:valuetype
th:vspaceth:widthth:wrap
th:xmlbaseth:xmllangth:xmlspace

同时设置多个值

th:alt-titleth:lang-xmllang 是两个特殊的属性,可以同时设置同一个值到两个属性:

  • th:alt-title 用于设置 alttitle
  • th:lang-xmllang 用于设置 langxml: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:alt-title="#{logo}" />

最终结果都是:

<img src="../../images/gtvglogo.png" 
     th:src="@{/images/gtvglogo.png}" th:title="#{logo}" th:alt="#{logo}" />

附加和添加前缀

th:attrappendth:attrprepend 用于附加和添加前缀属性。例如

<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />

执行模版, cssStyle 变量设置为 "warning"时,输出如下:

<input type="button" value="Do it!" class="btn warning" />

同时,有 th:classappendth:styleappend 用于设置CSS 的 class 和 style。例如:

<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">

固定值布尔属性

HTML具有布尔属性的概念,没有值的属性意味着该值为“true”。 在XHTML中,这些属性只取一个值,即它本身。

例如,checked:

<input type="checkbox" name="option2" checked /> <!-- HTML -->
<input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->

标准方言包括允许您通过评估条件来设置这些属性,如果评估为true,则该属性将被设置为其固定值,如果评估为false,则不会设置该属性:

<input type="checkbox" name="active" th:checked="${user.active}" />

标准方言中存在以下固定值布尔属性:

th:asyncth:autofocusth:autoplay
th:checkedth:controlsth:declare
th:defaultth:deferth:disabled
th:formnovalidateth:hiddenth:ismap
th:loopth:multipleth:novalidate
th:nowrapth:openth:pubdate
th:readonlyth:requiredth:reversed
th:scopedth:seamlessth:selected

默认属性处理器

提供 “默认属性处理器(default attribute processor)”,当标准方言没有提供的属性时,也可以设置其属性。比如:

<span th:whatever="${user.name}">...</span>

th:whatever 不是标准方言中提供的属性,则最终输出如下:

<span whatever="John Apricot">...</span>

支持对HTML5 友好的属性及元素名称

data-{prefix}-{name}语法在HTML5中编写自定义属性的标准方式,不需要开发人员使用任何名称空间的名字,如th:*。 Thymeleaf使这种语法自动提供给所有的方言(而不只是标准方言)。

<table>
    <tr data-th-each="user : ${users}">
        <td data-th-text="${user.login}">...</td>
        <td data-th-text="${user.name}">...</td>
    </tr>
</table>

5 - CH05-迭代器

基本的迭

th:each将循环 array 或 list 中的元素并重复打印一组标签,语法相当于 Java foreach 表达式:

<li th:each="book : ${books}" th:text="${book.title}">En las Orillas del Sar</li>

可以使用th:each属性进行遍历的对象包括:

  • 任何实现java.util.Iterable的对象
  • 任何实现java.util.Enumeration的对象
  • 任何实现java.util.Iterator的对象,其值将被迭代器返回,而不需要在内存中缓存所有的值
  • 任何实现java.util.Map的对象。 迭代映射时,迭代变量 将是java.util.Map.Entry
  • 任何数组
  • 任何其他对象将被视为包含对象本身的单值列表

状态变量

Thymeleaf 提供 状态变量(status variable) 来跟踪迭代器的状态。

th:each属性中,定义了如下状态变量:

  • index 属性是当前 迭代器索引(iteration index),从0开始
  • count 属性是当前 迭代器索引(iteration index),从1开始
  • size 属性是迭代器元素的总数
  • current 是当前 迭代变量(iter variable)
  • even/odd 判断当前迭代器是否是 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中定义了。

我们来看看模板的处理后的结果:

<!DOCTYPE html>

<html>

  <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>

    <h1>Product list</h1>

    <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>

    <p>
      <a href="/gtvg/" shape="rect">Return to home</a>
    </p>

  </body>

</html>

请注意,我们的迭代状态变量已经运行良好,建立只有奇数行具有 “odd” CSS 类。

如果您没有明确设置状态变量,则 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>

6 - CH06-条件语句

“if” 和 “unless”

th:if 属性用法如下:

<a href="comments.html"
   th:href="@{/product/comments(prodId=${prod.id})}" 
   th:if="${not #lists.isEmpty(prod.comments)}">view</a>

请注意,th:if 属性不仅是将评估布尔条件。 它的功能有点超出这一点,它将按照这些规则评估指定的表达式:

  • 如果值不为 null:
    • 如果值为布尔值,则为true。
    • 如果值是数字,并且不为零
    • 如果值是一个字符且不为零
    • 如果value是String,而不是“false”,“off”或“no”
    • 如果值不是布尔值,数字,字符或字符串。
  • 如果值为null,则th:if 将为 false。

另外,th:if有一个相反的属性th:unless,前面的例子改为:

<a href="comments.html"
   th:href="@{/comments(prodId=${prod.id})}" 
   th:unless="${#lists.isEmpty(prod.comments)}">view</a>

switch 语句

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>
</div>

请注意,只要一个th:case属性被评估为’true’,每个其他同一个 switch 语句中的th:case属性将被评估为false

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>

7 - CH07-模板布局

包含模板片段

定义和引用片段

在我们的模板中,我们经常需要从其他模板中添加 html 页面片段,如页脚、标题、菜单…

为了做到这一点,Thymeleaf 需要我们来定义这些“片段”,可以使用th:fragment属性来完成。我们定义了/WEB-INF/templates/footer.html页面

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

    <div th:fragment="copy">
      &copy; 2017 <a href="https://waylau.com">waylau.com</a>
    </div>

  </body>
</html>

如果,我们想引用这个 copy 代码片段,我们可以用 th:insertth:replace 属性 (th:include 也是可以,但自 Thymeleaf 3.0 以来就不再推荐):

<body>

  ...

  <div th:insert="~{footer :: copy}"></div>

</body>

注意th:insert需要一个片段表达式〜{...})。 在上面的例子中,非复杂片段表达式,(〜{})包围是完全可选,所以上面的代码将等效于:

<body>

  ...

  <div th:insert="footer :: copy"></div>

</body>

片段规范语法

  • "~{templatename::selector}" 名为templatename的模板上的指定标记选择器。 selector可以只是一个片段名。
  • "~{templatename}" : 包含完整的模版 templatename
  • ~{::selector}" or "~{this::selector}" 相同模版中的代码片段

不使用 th:fragment

不使用 th:fragment也可以引用HTML片段,比如:

...
<div id="copy-section">
  &copy; 2017 <a href="https://waylau.com">waylau.com</a>
</div>
...

通过 id 也可以引用到页面片段:

<body>

  ...

  <div th:insert="~{footer :: #copy-section}"></div>

</body>

th:insertth:replaceth:include三者区别

  • th:insert是最简单的:它将简单地插入指定的片段作为正文 的主标签。
  • th:replace用指定实际片段来替换其主标签。
  • th:include类似于th:insert,但不是插入片段它只插入此片段的内容

所以

<footer th:fragment="copy">
  &copy; 2017 <a href="https://waylau.com">waylau.com</a>
</footer>

三种方式引用该片段

<body>

  ...

  <div th:insert="footer :: copy"></div>

  <div th:replace="footer :: copy"></div>

  <div th:include="footer :: copy"></div>

</body>

结果为:

<body>

  ...

  <div>
    <footer>
      &copy; 2017 <a href="https://waylau.com">waylau.com</a>
    </footer>
  </div>

  <footer>
    &copy; 2017 <a href="https://waylau.com">waylau.com</a>
  </footer>

  <div>
    &copy; 2017 <a href="https://waylau.com">waylau.com</a>
  </div>

</body>

8 - CH08-局部变量

在迭代器中,我们可以使用局部变量prod

<tr th:each="prod : ${prods}">
    ...
</tr>

Thymeleaf 为您提供了一种在不使用迭代的情况下声明局部变量的方法th:with属性,其语法与属性值类似:

<div th:with="firstPer=${persons[0]}">
  <p>
    The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
  </p>
</div>

th:with被处理时,firstPer变量创建为局部变量,并添加到来自上下文的变量 map 中,以便它是可用于评估以及在上下文中声明的任何其他变量,但只能在包含<div>标签的范围内。

可以同时定义多个变量,赋值语法为:

<div th:with="firstPer=${persons[0]},secondPer=${persons[1]}">
  <p>
    The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
  </p>
  <p>
    But the name of the second person is 
    <span th:text="${secondPer.name}">Marcus Antonius</span>.
  </p>
</div>

th:with属性允许重用在同一属性中定义的变量:

<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div>

9 - CH09-属性优先级

当在同一个标签中写入多个th:*属性时,会发生什么? 对于下面的例子:

<ul>
  <li th:each="item : ${items}" th:text="${item.description}">
    Item description here...   
  </li>
</ul>

我们期望th:each属性在th:text之前执行,从而可以得到了我们想要的结果,但是鉴于 HTML/XML标准并未对标签中的属性的顺序给出任何的定义,所以必须在属性中建立*优先级(precedence)*机制 以确保这将按预期工作。

所以,所有的Thymeleaf属性定义一个数字优先级,它建立了它们在标签中执行的顺序。 这个是列表:

OrderFeatureAttributes
1Fragment inclusionth:insert th:replace
2Fragment iterationth:each
3Conditional evaluationth:if th:unless th:switch th:case
4Local variable definitionth:object th:with
5General attribute modificationth:attr th:attrprepend th:attrappend
6Specific attribute modificationth:value th:href th:src ...
7Text (tag body modification)th:text th:utext
8Fragment specificationth:fragment
9Fragment removalth:remove

这个优先机制意味着如果属性位置被反转,上述迭代片段将给出完全相同的结果(尽管它的可读性稍差):

<ul>
  <li th:text="${item.description}" th:each="item : ${items}">Item description here...</li>
</ul>

10 - CH10-注释及注释块

标准 HTML/XML 注释

标准 HTML/XML 注释<!-- ... --> 可以在模版中使用:

<!-- User info follows -->
<div th:text="${...}">
  ...
</div>

Thymeleaf 解析器级注释块

解析器级注释块是当 Thymeleaf 解析它的模板时,这些代码将被简单地从中删除。 他们看起来像这样:

<!--/* This code will be removed at Thymeleaf parsing time! */-->

Thymeleaf将删除<!--/**/-->之间的所有内容,所以这些注释块也可以用于在模板静态打开时显示代码,当Thymeleaf处理它会被删除:

<!--/*--> 
  <div>
     you can see me only before Thymeleaf processes me!
  </div>
<!--*/-->

这在原型中设计中有很多 <tr>时,非常适用:

<table>
   <tr th:each="x : ${xs}">
     ...
   </tr>
   <!--/*-->
   <tr>
     ...
   </tr>
   <tr>
     ...
   </tr>
   <!--*/-->
</table>

原型注释块

原型注释块是指,当模版静态打开时(比如原型设计),原型注释块所注释的代码将被注释,而在模版执行时,这些注释的代码,就能被显示出来。

<span>hello!</span>
<!--/*/
  <div th:text="${...}">
    ...
  </div>
/*/-->
<span>goodbye!</span>

Thymeleaf 的解析系统将简单地删除<!--/*//*/--> 标记,但保留所注释的内容。 那么什么时候执行模板,Thymeleaf 实际上会看到:

<span>hello!</span>

  <div th:text="${...}">
    ...
  </div>

<span>goodbye!</span>

与解析器级注释块一样,此功能与方言无关。

合成th:block标签

标准方言中包含的唯一的元素处理器(不是属性)是th:block

th:block只是一个属性容器,允许模板开发人员指定他们想要的任何属性。 Thymeleaf 将执行这些属性,然后简单地制作块,而不是让其内容消失。

因此,例如,当为每个元素创建需要多个<tr> 的迭代时,这可能是有用的:

<table>
  <th:block th:each="user : ${users}">
    <tr>
        <td th:text="${user.login}">...</td>
        <td th:text="${user.name}">...</td>
    </tr>
    <tr>
        <td colspan="2" th:text="${user.address}">...</td>
    </tr>
  </th:block>
</table>

当与原型注释块组合使用时尤其有用:

<table>
    <!--/*/ <th:block th:each="user : ${users}"> /*/-->
    <tr>
        <td th:text="${user.login}">...</td>
        <td th:text="${user.name}">...</td>
    </tr>
    <tr>
        <td colspan="2" th:text="${user.address}">...</td>
    </tr>
    <!--/*/ </th:block> /*/-->
</table>

11 - CH11-内联

表达式内联

虽然标准方言使我们能够使用标签属性来做几乎所有的事情,但是有些情况下我们更喜欢将表达式直接写入我们的HTML文本。 例如,我们可以喜欢写这个:

<p>Hello, [[${session.user.name}]]!</p>

来代替:

<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>

[[...]][(...)] 被称为 内联表达式(inlined expressions),分别对应于 th:textth:utext 属性。

<p>The message is "[(${msg})]"</p>

这个的结果显示, <b> 标签不会被转义:

<p>The message is "This is <b>great!</b>"</p>

如果是这样:

<p>The message is "[[${msg}]]"</p>

则结果会被转义:

<p>The message is "This is &lt;b&gt;great!&lt;/b&gt;"</p>

内联与自然模板

虽然内联看上去比自然模板更加简洁,但它并不总是适用,比如当您静态打开HTML文件时,会逐字显示,因为内联无法将它们用作设计原型了!

禁用内联

有时,我们想禁用这种机制,比如,想输出 [[...]][(...)] 文本内容,而不是将其视为表达式。 为此,我们将使用 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>

文本内联

文本内联与我们拥有的表达式内联功能非常相似,但实际上拥有了更多的能力。 为了启用它我们使用th:inline="text"

文本内联不仅允许我们使用相同的内联表达式,而且实际上可以处理在“TEXT”模板模式下的标签体。

我们将在下一章中看到关于[文本模板模式]的更多信息。

JavaScript 内联

<script th:inline="javascript">
    ...
    var username = [[${session.user.name}]];
    ...
</script>

结果为:

<script th:inline="javascript">
    ...
    var username = "Sebastian \"Fruity\" Applejuice";
    ...
</script>

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序列化

关于JavaScript内联的一个重要的事情是这个表达式评估是非常智能的,并不仅限于字符串的处理。Thymeleaf 会正确写入 JavaScript 语法中的以下几种对象:

  • String
  • Number
  • Boolean
  • Array
  • Collection
  • Map
  • Bean (有 getter and setter 方法的对象)

例如,如果我们有以下代码:

<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 接口来实现的,可以在 StandardDialect的实例配置被用于模板引擎。

此JS序列化机制的默认实现将会查找在类路径中[Jackson 库],如果存在,将会使用它。如果没有,它将应用一个内置的序列化机制。这涵盖了大多数场景的需求,并产生了类似的结果(但是 相比较 Jackson 而言不够灵活)。

CSS 内联

Thymeleaf 允许使用 CSS <style>标签,比如:

<style th:inline="css">
  ...
</style>

比如,我们想设置两个变量到不同的String值:

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

CSS 自然模版

与JavaScript 类似,也有 CSS 自然模版,用法如下:

<style th:inline="css">
    .main\ elems {
      text-align: /*[[${align}]]*/ left;
    }
</style>