Thymeleaf

知乎专栏

尚硅谷

配置

  • 视图模板技术,在index.html上加载java内存中的数据为渲染

  • 如果直接访问index.html本身,那么index.html是不需要通过Servlet,也不经过模板引擎,所以index.html上的Thymeleaf的任何表达式都不会被解析。通过Servlet访问index.html,这样就可以让模板引擎渲染页面

  • 添加thymeleaf的jar包

  • 新建一个Servlet类ViewBaseServlet

  • 在web.xml文件中添加配置

    • 配置前缀 view-prefix
    • 配置后缀 view-suffix
  • 使得我们的Servlet继承ViewBaseServlet

  • 根据逻辑视图名称 得到 物理视图名称
    逻辑视图名称 : index
    物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
    真实的视图名称是: / index .html
    super.processTemplate("index",request,response);

  • 使用thymeleaf的标签 html表头使用

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

语法

表达式

信息

信息表达式 #{} 用于展示静态资源的内容,比如i18n属性配置文件

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

新建/static/templates/home.properties,其中home.welcome=this messages is from home.properties。那么,信息表达式解析后为:

<p>this messages is from home.properties!</p>
  1. 先计算变量${sel.code},假如值是welcome
  2. __的含义是需要预处理的变量值,那么就变成#
  3. 计算最终结果
#{home.__${sel.code}__}

变量

变量表达式 ${...} 实际上是把上下文中包含的变量映射到OGNL表达式,即在SpringMVC中的模型属性,如:${user.name}。可直接使用 th:xx = "${...}" 获取对象属性,如定义在属性中:<span th:text="${book.author.name}">、遍历:<li th:each="book : ${books}">

<tr th:if="${#lists.isEmpty(session.gradelist)}">
    <td colspan="5">没有对应的信息!</td>
</tr>
<tr th:unless="${#lists.isEmpty(session.gradelist)}"  th:each="classandstu: ${session.gradelist}" >
    <td class="w20" th:text="${classandstu.cid}"></td >
    <td class="w20" th:text="${classandstu.cname}"></td>
    <td class="w20" th:text="${classandstu.credit}"></td>
    <td class="w20" th:text="${classandstu.sgrad}/20"></td>
    <td class="w20" th:text="${classandstu.sgrad}"></td>
</tr>
<p th:utext="#{home.welcome(${session.user.name})}">   Welcome to our grocery store, Sebastian Pepper! </p>

等价于:

((User) ctx.getVariable("session").get("user")).getName();

变量表达式不仅可以写成 ${...},而且还可以写成 *{...}

有一个重要的区别:星号语法针对选定的对象进行评估,而不是整个上下文上。也就是说,只要没有选定的对象,$ 和 ***** 的语法就完全一样。

选定的对象就是使用 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>

其中,firstName、lastName、nationality均为user对象中的属性。而选的的对象,正是 ${session.user} 表达式的结果,即用户信息对象。

其等同于:

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

此时,#object 表达式变量即等同于 ${session.user}

如前面所说,如果没有任何对象选定,则 ${}*{} 语法是等效的。

<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

@{}

<a href="details.html" th:href="@{http://localhost:8080/mall/order/details(orderId=${o.id})}">view</a>
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

URL地址后面附加请求参数

@{/order/process(id=${cid},name='FAST')}
@{/order/{orderId}/details(orderId=${orderId})}
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

如果未启用Cookie或尚不知道,则 “;jsessionid=…” 可能会作为后缀添加到相对URL中,以便保留会话。这称为URL重写,Thymeleaf允许通过使用Servlet API中的 response.encodeURL(…) 机制,可针对所有url,以实现自己的重写过滤器

片段

片段表达式提供了模板共用的简便方法,同时还可以作为参数传递给其它模板。

最常见的用途是使用 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>
  Now you are looking at a <span th:text="'working web application, \'thanks very much\'.'">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>
布尔

布尔常量即 truefalse

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

在此样例中,== false 判断是写在花括号外面的,因此,Thymeleaf引擎将负责处理。如写在花括号里面,则OGNL/SpringEL引擎将负责解析。

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

空常量即 null

<div th:if="${variable.something} == null"> ...
文本追加

无论是字面量,还是评估表达式或信息表达式的结果,都可以使用 + 运算符轻松追加文本。

<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规范规定,不允许属性值使用 < 和 > 符号,因此应使用 &lt;&gt;代替。

<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!=)。

条件表达式

条件表达式旨在仅根据两个条件的求值结果来求值(其本身就是另一个表达式)。

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

条件表达式的所有三个部分(conditionthenelse)本身的表达式,这意味着它们可以是变量(${…}{…}),消息(#{…}),网址(@{…})或文字(‘…’*)。

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

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

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

<tr th:class="${row.even}? 'alt'">
  ...
</tr>
默认运算符

默认表达式,允许指定两个表达式,如果第一个表达式的计算结果不为null,则使用第一个表达式;如果为null,则使用第二个表达式。

<div th:object="${session.user}">
  ...
  <p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>

<p>
  Name: 
  <span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>
无操作标记
<span th:text="${user.name} ?: _">no user authenticated</span>
数据转换/格式化

Thymeleaf 为变量表达式(${…})和选择变量表达式(*{…})表达式定义了双括号语法,使我们能够通过配置的转换服务来进行数据转换。如:

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

其中,双括号 ${{…}} 的作用即为指示Thymeleaf将 user.lastAccessDate 表达式的结果传递给转换服务*,*并要求它执行格式操作(将转换为String),然后再写入结果。

IStandardConversionService的默认实现(StandardConversionService类)只是简单的使用 .toString() 将任何对象转换为String

OGNL表达式

OGNL:Objects-Graph Navigation Language对象图导航语言

常用OGNL表达式
/*
 * Access to properties using the point (.). Equivalent to calling property getters.
 */
${person.father.name}

/*
 * Access to properties can also be made by using brackets ([]) and writing 
 * the name of the property as a variable or between single quotes.
 */
${person['father']['name']}

/*
 * If the object is a map, both dot and bracket syntax will be equivalent to 
 * executing a call on its get(...) method.
 */
${countriesByCode.ES}
${personsByName['Stephen Zucchini'].age}

/*
 * Indexed access to arrays or collections is also performed with brackets, 
 * writing the index without quotes.
 */
${personsArray[0].name}

/*
 * Methods can be called, even with arguments.
 */
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}
Established locale country: <span th:text="${#locale.country}">US</span>
  • #ctx:上下文对象。

  • #vars: 上下文变量。

  • #locale:上下文语言环境。

  • #request:(仅在Web上下文中)HttpServletRequest 对象。

  • #response:(仅在Web上下文中)HttpServletResponse 对象。

  • #session:(仅在Web上下文中)HttpSession 对象。

  • #servletContext:(仅在Web上下文中)ServletContext 对象。

  • #execInfo:正在处理的模板的信息。

  • #messages:用于获取变量表达式内的外部化消息的方法,与使用**#{…}** 语法获得消息的方法相同。

  • #uris:用于转义部分 URL / URI 的方法

  • #conversions:用于执行已配置的转换服务(如果有)的方法。

  • #datesjava.util.Date 对象的方法:格式化,组件提取等。

  • #calendars:与 #dates 类似,用于 java.util.Calendar 对象。

  • #numbers:格式化数字的方法。

  • #stringsString对象的方法:包含,startsWith,prepending/appending等。

  • #objects:一般对象的方法。

  • #bools:布尔值评估的方法。

  • #arrays:数组方法。

  • #lists:列表方法。

  • #sets:set集合方法。

  • #maps:map方法。

  • #aggregates:用于在数组或集合上创建聚合的方法。

  • #ids:用于处理可能重复的id属性的方法(例如,由于迭代的结果)。

#ctx:上下文对象。实现了 org.thymeleaf.context.IContext 或 org.thymeleaf.context.IWebContext 接口,取决于我们的环境(单机或网络)与 #vars#root 是同一个对象,但建议使用 #ctx

基础对象
${#ctx.locale}
${#ctx.variableNames}
${#ctx.request}
${#ctx.response}
${#ctx.session}
${#ctx.servletContext}

请求/会话属性等的Web上下文名称空间
//param:用于检索请求参数。${param.foo} 是 String[] 类型的foo request参数的值数组,因此${param.foo[0]}通常用于获取第一个值
${param.foo}              // Retrieves a String[] with the values of request parameter 'foo'
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}

//session : 用于检索会话属性
${session.foo}                 // Retrieves the session atttribute 'foo'
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}

//application 用于检索应用程序/ servlet上下文属性
${application.foo}              // Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}

Web上下文对象
//#request:直接访问 javax.servlet.http.HttpServletRequest 与当前请求关联的对象
${#request.getAttribute('foo')}
${#request.getParameter('foo')}
${#request.getContextPath()}
${#request.getRequestName()}

//#session:直接访问 javax.servlet.http.HttpSession 与当前请求关联的对象
${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}

//#servletContext:直接访问 javax.servlet.ServletContext 与当前请求关联的对象
${#servletContext.getAttribute('foo')}
${#servletContext.contextPath}

表达式实用对象
//#execInfo:提供有关Thymeleaf标准表达式中正在处理的模板的有用信息的表达式对象
${#execInfo.templateName}
${#execInfo.templateMode}
${#execInfo.processedTemplateName}
${#execInfo.processedTemplateMode}
${#execInfo.templateNames}
${#execInfo.templateModes}

//#messages:用于获取变量表达式内的外部化消息的实用程序方法,与使用 #{...} 语法获得消息的方式相同
${#messages.msg('msgKey')}
${#messages.msg('msgKey', param1)}
${#messages.msg('msgKey', param1, param2)}
${#messages.msg('msgKey', param1, param2, param3)}
${#messages.msgWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
${#messages.arrayMsg(messageKeyArray)}
${#messages.listMsg(messageKeyList)}
${#messages.setMsg(messageKeySet)}

${#messages.msgOrNull('msgKey')}
${#messages.msgOrNull('msgKey', param1)}
${#messages.msgOrNull('msgKey', param1, param2)}
${#messages.msgOrNull('msgKey', param1, param2, param3)}
${#messages.msgOrNullWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
${#messages.arrayMsgOrNull(messageKeyArray)}
${#messages.listMsgOrNull(messageKeyList)}
${#messages.setMsgOrNull(messageKeySet)}

//#uris:在Thymeleaf标准表达式内执行URI / URL操作(尤其是转义/转义)的实用程序对象
/*
 * Escape/Unescape as a URI/URL path
 */
${#uris.escapePath(uri)}
${#uris.escapePath(uri, encoding)}
${#uris.unescapePath(uri)}
${#uris.unescapePath(uri, encoding)}
/*
 * Escape/Unescape as a URI/URL path segment (between '/' symbols)
 */
${#uris.escapePathSegment(uri)}
${#uris.escapePathSegment(uri, encoding)}
${#uris.unescapePathSegment(uri)}
${#uris.unescapePathSegment(uri, encoding)}
/*
 * Escape/Unescape as a Fragment Identifier (#frag)
 */
${#uris.escapeFragmentId(uri)}
${#uris.escapeFragmentId(uri, encoding)}
${#uris.unescapeFragmentId(uri)}
${#uris.unescapeFragmentId(uri, encoding)}
/*
 * Escape/Unescape as a Query Parameter (?var=value)
 */
${#uris.escapeQueryParam(uri)}
${#uris.escapeQueryParam(uri, encoding)}
${#uris.unescapeQueryParam(uri)}
${#uris.unescapeQueryParam(uri, encoding)}

//#conversions:允许在模板的任何位置执行转换服务的实用程序对象
/*
 * Execute the desired conversion of the 'object' value into the
 * specified class.
 */
${#conversions.convert(object, 'java.util.TimeZone')}
${#conversions.convert(object, targetClass)}

日期与时间
//#dates:java.util.Date 对象的实用程序方法
/*
 * Format date with the standard locale format
 * Also works with arrays, lists or sets
 */
${#dates.format(date)}
${#dates.arrayFormat(datesArray)}
${#dates.listFormat(datesList)}
${#dates.setFormat(datesSet)}
/*
 * Format date with the ISO8601 format
 * Also works with arrays, lists or sets
 */
${#dates.formatISO(date)}
${#dates.arrayFormatISO(datesArray)}
${#dates.listFormatISO(datesList)}
${#dates.setFormatISO(datesSet)}
/*
 * Format date with the specified pattern
 * Also works with arrays, lists or sets
 */
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
/*
 * Obtain date properties
 * Also works with arrays, lists or sets
 */
${#dates.day(date)}                    // also arrayDay(...), listDay(...), etc.
${#dates.month(date)}                  // also arrayMonth(...), listMonth(...), etc.
${#dates.monthName(date)}              // also arrayMonthName(...), listMonthName(...), etc.
${#dates.monthNameShort(date)}         // also arrayMonthNameShort(...), listMonthNameShort(...), etc.
${#dates.year(date)}                   // also arrayYear(...), listYear(...), etc.
${#dates.dayOfWeek(date)}              // also arrayDayOfWeek(...), listDayOfWeek(...), etc.
${#dates.dayOfWeekName(date)}          // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
${#dates.dayOfWeekNameShort(date)}     // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
${#dates.hour(date)}                   // also arrayHour(...), listHour(...), etc.
${#dates.minute(date)}                 // also arrayMinute(...), listMinute(...), etc.
${#dates.second(date)}                 // also arraySecond(...), listSecond(...), etc.
${#dates.millisecond(date)}            // also arrayMillisecond(...), listMillisecond(...), etc.
/*
 * Create date (java.util.Date) objects from its components
 */
${#dates.create(year,month,day)}
${#dates.create(year,month,day,hour,minute)}
${#dates.create(year,month,day,hour,minute,second)}
${#dates.create(year,month,day,hour,minute,second,millisecond)}
/*
 * Create a date (java.util.Date) object for the current date and time
 */
${#dates.createNow()}
${#dates.createNowForTimeZone()}
/*
 * Create a date (java.util.Date) object for the current date (time set to 00:00)
 */
${#dates.createToday()}
${#dates.createTodayForTimeZone()}

//#calendars:与 #dates 类似,但基于 java.util.Calendar 对象。
/*
 * Format calendar with the standard locale format
 * Also works with arrays, lists or sets
 */
${#calendars.format(cal)}
${#calendars.arrayFormat(calArray)}
${#calendars.listFormat(calList)}
${#calendars.setFormat(calSet)}
/*
 * Format calendar with the ISO8601 format
 * Also works with arrays, lists or sets
 */
${#calendars.formatISO(cal)}
${#calendars.arrayFormatISO(calArray)}
${#calendars.listFormatISO(calList)}
${#calendars.setFormatISO(calSet)}
/*
 * Format calendar with the specified pattern
 * Also works with arrays, lists or sets
 */
${#calendars.format(cal, 'dd/MMM/yyyy HH:mm')}
${#calendars.arrayFormat(calArray, 'dd/MMM/yyyy HH:mm')}
${#calendars.listFormat(calList, 'dd/MMM/yyyy HH:mm')}
${#calendars.setFormat(calSet, 'dd/MMM/yyyy HH:mm')}
/*
 * Obtain calendar properties
 * Also works with arrays, lists or sets
 */
${#calendars.day(date)}                // also arrayDay(...), listDay(...), etc.
${#calendars.month(date)}              // also arrayMonth(...), listMonth(...), etc.
${#calendars.monthName(date)}          // also arrayMonthName(...), listMonthName(...), etc.
${#calendars.monthNameShort(date)}     // also arrayMonthNameShort(...), listMonthNameShort(...), etc.
${#calendars.year(date)}               // also arrayYear(...), listYear(...), etc.
${#calendars.dayOfWeek(date)}          // also arrayDayOfWeek(...), listDayOfWeek(...), etc.
${#calendars.dayOfWeekName(date)}      // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
${#calendars.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
${#calendars.hour(date)}               // also arrayHour(...), listHour(...), etc.
${#calendars.minute(date)}             // also arrayMinute(...), listMinute(...), etc.
${#calendars.second(date)}             // also arraySecond(...), listSecond(...), etc.
${#calendars.millisecond(date)}        // also arrayMillisecond(...), listMillisecond(...), etc.
/*
 * Create calendar (java.util.Calendar) objects from its components
 */
${#calendars.create(year,month,day)}
${#calendars.create(year,month,day,hour,minute)}
${#calendars.create(year,month,day,hour,minute,second)}
${#calendars.create(year,month,day,hour,minute,second,millisecond)}
${#calendars.createForTimeZone(year,month,day,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,second,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,second,millisecond,timeZone)}
/*
 * Create a calendar (java.util.Calendar) object for the current date and time
 */
${#calendars.createNow()}
${#calendars.createNowForTimeZone()}
/*
 * Create a calendar (java.util.Calendar) object for the current date (time set to 00:00)
 */
${#calendars.createToday()}
${#calendars.createTodayForTimeZone()}
数字格式化
//#numbers:用于数字对象的实用方法
/* 
 * Set minimum integer digits.
 * Also works with arrays, lists or sets
 */
${#numbers.formatInteger(num,3)}
${#numbers.arrayFormatInteger(numArray,3)}
${#numbers.listFormatInteger(numList,3)}
${#numbers.setFormatInteger(numSet,3)}
/* 
 * Set minimum integer digits and thousands separator: 
 * 'POINT', 'COMMA', 'WHITESPACE', 'NONE' or 'DEFAULT' (by locale).
 * Also works with arrays, lists or sets
 */
${#numbers.formatInteger(num,3,'POINT')}
${#numbers.arrayFormatInteger(numArray,3,'POINT')}
${#numbers.listFormatInteger(numList,3,'POINT')}
${#numbers.setFormatInteger(numSet,3,'POINT')}
/*
 * ==========================
 * Formatting decimal numbers
 * ==========================
 */
/*
 * Set minimum integer digits and (exact) decimal digits.
 * Also works with arrays, lists or sets
 */
${#numbers.formatDecimal(num,3,2)}
${#numbers.arrayFormatDecimal(numArray,3,2)}
${#numbers.listFormatDecimal(numList,3,2)}
${#numbers.setFormatDecimal(numSet,3,2)}
/*
 * Set minimum integer digits and (exact) decimal digits, and also decimal separator.
 * Also works with arrays, lists or sets
 */
${#numbers.formatDecimal(num,3,2,'COMMA')}
${#numbers.arrayFormatDecimal(numArray,3,2,'COMMA')}
${#numbers.listFormatDecimal(numList,3,2,'COMMA')}
${#numbers.setFormatDecimal(numSet,3,2,'COMMA')}
/*
 * Set minimum integer digits and (exact) decimal digits, and also thousands and 
 * decimal separator.
 * Also works with arrays, lists or sets
 */
${#numbers.formatDecimal(num,3,'POINT',2,'COMMA')}
${#numbers.arrayFormatDecimal(numArray,3,'POINT',2,'COMMA')}
${#numbers.listFormatDecimal(numList,3,'POINT',2,'COMMA')}
${#numbers.setFormatDecimal(numSet,3,'POINT',2,'COMMA')}
/* 
 * =====================
 * Formatting currencies
 * =====================
 */
${#numbers.formatCurrency(num)}
${#numbers.arrayFormatCurrency(numArray)}
${#numbers.listFormatCurrency(numList)}
${#numbers.setFormatCurrency(numSet)}
/* 
 * ======================
 * Formatting percentages
 * ======================
 */
${#numbers.formatPercent(num)}
${#numbers.arrayFormatPercent(numArray)}
${#numbers.listFormatPercent(numList)}
${#numbers.setFormatPercent(numSet)}
/* 
 * Set minimum integer digits and (exact) decimal digits.
 */
${#numbers.formatPercent(num, 3, 2)}
${#numbers.arrayFormatPercent(numArray, 3, 2)}
${#numbers.listFormatPercent(numList, 3, 2)}
${#numbers.setFormatPercent(numSet, 3, 2)}
/*
 * ===============
 * Utility methods
 * ===============
 */
/*
 * Create a sequence (array) of integer numbers going
 * from x to y
 */
${#numbers.sequence(from,to)}
${#numbers.sequence(from,to,step)}
字符串
//#strings:String 对象的实用方法
/*
 * Null-safe toString()
 */
${#strings.toString(obj)}                           // also array*, list* and set*
/*
 * Check whether a String is empty (or null). Performs a trim() operation before check
 * Also works with arrays, lists or sets
 */
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
/*
 * Perform an 'isEmpty()' check on a string and return it if false, defaulting to
 * another specified string if true.
 * Also works with arrays, lists or sets
 */
${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}
/*
 * Check whether a fragment is contained in a String
 * Also works with arrays, lists or sets
 */
${#strings.contains(name,'ez')}                     // also array*, list* and set*
${#strings.containsIgnoreCase(name,'ez')}           // also array*, list* and set*
/*
 * Check whether a String starts or ends with a fragment
 * Also works with arrays, lists or sets
 */
${#strings.startsWith(name,'Don')}                  // also array*, list* and set*
${#strings.endsWith(name,endingFragment)}           // also array*, list* and set*
/*
 * Substring-related operations
 * Also works with arrays, lists or sets
 */
${#strings.indexOf(name,frag)}                      // also array*, list* and set*
${#strings.substring(name,3,5)}                     // also array*, list* and set*
${#strings.substringAfter(name,prefix)}             // also array*, list* and set*
${#strings.substringBefore(name,suffix)}            // also array*, list* and set*
${#strings.replace(name,'las','ler')}               // also array*, list* and set*
/*
 * Append and prepend
 * Also works with arrays, lists or sets
 */
${#strings.prepend(str,prefix)}                     // also array*, list* and set*
${#strings.append(str,suffix)}                      // also array*, list* and set*
/*
 * Change case
 * Also works with arrays, lists or sets
 */
${#strings.toUpperCase(name)}                       // also array*, list* and set*
${#strings.toLowerCase(name)}                       // also array*, list* and set*
/*
 * Split and join
 */
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.arraySplit(namesStr,',')}                // returns String[]
${#strings.listSplit(namesStr,',')}                 // returns List<String>
${#strings.setSplit(namesStr,',')}                  // returns Set<String>
/*
 * Trim
 * Also works with arrays, lists or sets
 */
${#strings.trim(str)}                               // also array*, list* and set*
/*
 * Compute length
 * Also works with arrays, lists or sets
 */
${#strings.length(str)}                             // also array*, list* and set*
/*
 * Abbreviate text making it have a maximum size of n. If text is bigger, it
 * will be clipped and finished in "..."
 * Also works with arrays, lists or sets
 */
${#strings.abbreviate(str,10)}                      // also array*, list* and set*
/*
 * Convert the first character to upper-case (and vice-versa)
 */
${#strings.capitalize(str)}                         // also array*, list* and set*
${#strings.unCapitalize(str)}                       // also array*, list* and set*
/*
 * Convert the first character of every word to upper-case
 */
${#strings.capitalizeWords(str)}                    // also array*, list* and set*
${#strings.capitalizeWords(str,delimiters)}         // also array*, list* and set*
/*
 * Escape the string
 */
${#strings.escapeXml(str)}                          // also array*, list* and set*
${#strings.escapeJava(str)}                         // also array*, list* and set*
${#strings.escapeJavaScript(str)}                   // also array*, list* and set*
${#strings.unescapeJava(str)}                       // also array*, list* and set*
${#strings.unescapeJavaScript(str)}                 // also array*, list* and set*
/*
 * Null-safe comparison and concatenation
 */
${#strings.equals(first, second)}
${#strings.equalsIgnoreCase(first, second)}
${#strings.concat(values...)}
${#strings.concatReplaceNulls(nullValue, values...)}
/*
 * Random
 */
${#strings.randomAlphanumeric(count)}
objects
//#objects:一般对象的实用方法
/*
 * Return obj if it is not null, and default otherwise
 * Also works with arrays, lists or sets
 */
${#objects.nullSafe(obj,default)}
${#objects.arrayNullSafe(objArray,default)}
${#objects.listNullSafe(objList,default)}
${#objects.setNullSafe(objSet,default)}
bools
/*
 * Evaluate a condition in the same way that it would be evaluated in a th:if tag
 * (see conditional evaluation chapter afterwards).
 * Also works with arrays, lists or sets
 */
${#bools.isTrue(obj)}
${#bools.arrayIsTrue(objArray)}
${#bools.listIsTrue(objList)}
${#bools.setIsTrue(objSet)}
/*
 * Evaluate with negation
 * Also works with arrays, lists or sets
 */
${#bools.isFalse(cond)}
${#bools.arrayIsFalse(condArray)}
${#bools.listIsFalse(condList)}
${#bools.setIsFalse(condSet)}
/*
 * Evaluate and apply AND operator
 * Receive an array, a list or a set as parameter
 */
${#bools.arrayAnd(condArray)}
${#bools.listAnd(condList)}
${#bools.setAnd(condSet)}
/*
 * Evaluate and apply OR operator
 * Receive an array, a list or a set as parameter
 */
${#bools.arrayOr(condArray)}
${#bools.listOr(condList)}
${#bools.setOr(condSet)}
arrays
//#arrays:数组的实用方法
/*
 * Converts to array, trying to infer array component class.
 * Note that if resulting array is empty, or if the elements
 * of the target object are not all of the same class,
 * this method will return Object[].
 */
${#arrays.toArray(object)}
/*
 * Convert to arrays of the specified component class.
 */
${#arrays.toStringArray(object)}
${#arrays.toIntegerArray(object)}
${#arrays.toLongArray(object)}
${#arrays.toDoubleArray(object)}
${#arrays.toFloatArray(object)}
${#arrays.toBooleanArray(object)}
/*
 * Compute length
 */
${#arrays.length(array)}
/*
 * Check whether array is empty
 */
${#arrays.isEmpty(array)}
/*
 * Check if element or elements are contained in array
 */
${#arrays.contains(array, element)}
${#arrays.containsAll(array, elements)}
lists
//#lists:列表的实用方法
/*
 * Converts to list
 */
${#lists.toList(object)}
/*
 * Compute size
 */
${#lists.size(list)}
/*
 * Check whether list is empty
 */
${#lists.isEmpty(list)}
/*
 * Check if element or elements are contained in list
 */
${#lists.contains(list, element)}
${#lists.containsAll(list, elements)}
/*
 * Sort a copy of the given list. The members of the list must implement
 * comparable or you must define a comparator.
 */
${#lists.sort(list)}
${#lists.sort(list, comparator)}
sets
//#sets:集合的实用方法
/*
 * Converts to set
 */
${#sets.toSet(object)}
/*
 * Compute size
 */
${#sets.size(set)}
/*
 * Check whether set is empty
 */
${#sets.isEmpty(set)}
/*
 * Check if element or elements are contained in set
 */
${#sets.contains(set, element)}
${#sets.containsAll(set, elements)}
maps
//#maps:图的实用方法
/*
 * Compute size
 */
${#maps.size(map)}
/*
 * Check whether map is empty
 */
${#maps.isEmpty(map)}
/*
 * Check if key/s or value/s are contained in maps
 */
${#maps.containsKey(map, key)}
${#maps.containsAllKeys(map, keys)}
${#maps.containsValue(map, value)}
${#maps.containsAllValues(map, value)}
aggregates
//#aggregates:在数组或集合上创建聚合的实用程序方法。
/*
 * Compute sum. Returns null if array or collection is empty
 */
${#aggregates.sum(array)}
${#aggregates.sum(collection)}
/*
 * Compute average. Returns null if array or collection is empty
 */
${#aggregates.avg(array)}
${#aggregates.avg(collection)}
ids
//#ids:用于处理 id 可能重复(例如,由于迭代的结果)的属性的实用方法
/*
 * Normally used in th:id attributes, for appending a counter to the id attribute value
 * so that it remains unique even when involved in an iteration process.
 */
${#ids.seq('someId')}
/*
 * Normally used in th:for attributes in <label> tags, so that these labels can refer to Ids
 * generated by means if the #ids.seq(...) function.
 *
 * Depending on whether the <label> goes before or after the element with the #ids.seq(...)
 * function, the "next" (label goes before "seq") or the "prev" function (label goes after 
 * "seq") function should be called.
 */
${#ids.next('someId')}
${#ids.prev('someId')}

示例

<p>
  Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>
<div th:fragment="alert (type, message)" th:assert="${!#strings.isEmpty(type) and !#strings.isEmpty(message)}" class="alert alert-dismissable" th:classappend="'alert-' + ${type}">
    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
    <span th:text="${message}">Test</span>
</div>

表达式转义 内联

内联
  • 有转义效果:[[${表达式}]]
  • 无转义效果:[(${表达式})]

Servlet代码:

request.setAttribute("reqAttrName", "<span>hello-value</span>");

页面代码:

<p>有转义效果:[[${reqAttrName}]]</p>
<p>无转义效果:[(${reqAttrName})]</p>

执行效果:

<p>有转义效果:&lt;span&gt;hello-value&lt;/span&gt;</p>
<p>无转义效果:<span>hello-value</span></p>
禁用内联

不过,可以禁用此机制,因为实际上在某些情况下,我们确实希望输出 [[…]] 或者 [(…)] 序列而不将其内容作为表达式处理。为此,我们将使用 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>
JS内联
<script th:inline="javascript">
    ...
    var username = [[${session.user.name}]];
    ...
</script>
css内联
<style th:inline="css">
    .[[${classname}]] {
      text-align: [[${align}]];
    }
</style>

结果为:

<style th:inline="css">
    .main\ elems {
      text-align: center;
    }
</style>

设置属性值

设置任何属性的值

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:* 属性,而这些属性的任务就是为特定属性设置值(但不仅仅是像 th:attr 这样的任何属性)

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

一次设置多个值

  • 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:title="#{logo}" th:alt="#{logo}" />

有了 th:alt-title 属性,则可以很方便的同时设置值:

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

追加和前置

Thymeleaf还提供 th:attrappendth: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:classappendth:styleappend 属性,用于在元素上添加CSS类或样式片段而不覆盖现有属性的值:

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

标准方言存在一些属性,这些属性可以通过评估条件来设置这些属性,因此,如果评估为 true,则该属性将设置为其固定值;如果评估为 false,则将不设置该属性:

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

设置任何属性

Thymeleaf提供了一个默认的属性处理器,即使我们在标准方言中没有为其定义任何特定的 th:* 处理器,它也允许我们设置任何属性的值 。

例如:

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

将解析为:

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

选择 迭代

th:each

<tr th:if="${#lists.isEmpty(session.gradelist)}">
    <td colspan="5">没有对应的信息!</td>
</tr>
<tr th:unless="${#lists.isEmpty(session.gradelist)}"  th:each="classandstu: ${session.gradelist}" >
    <td class="w20" th:text="${classandstu.cid}"></td >
    <td class="w20" th:text="${classandstu.cname}"></td>
    <td class="w20" th:text="${classandstu.credit}"></td>
    <td class="w20" th:text="${classandstu.sgrad}/20"></td>
    <td class="w20" th:text="${classandstu.sgrad}"></td>
</tr>

th:if

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

这将创建指向评论页面(URL为 /product/comments)的链接,其 prodId 参数为产品的 id,但前提是该产品要有评论

th:unless

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

th:switch / th:case

请注意,一旦一个 th:case 属性的值为 true,同一swicth上下文中的所有其它 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>
</div>

速成

属性名 作用
th:text 使用表达式设置文本标签体的数据
th:属性名 使用表达式运算得到的值设置HTML属性
th:if/th:unless 分支判断
th:each 迭代
<p th:text="标签体新值">标签体原始值</p>
<input type="text" name="username" th:value="文本框新值" value="文本框旧值" />
<img th:src="@{/upload/}+${session.id}+@{.png}" alt="暂无照片">
<p class="link" th:text="${session.count}">x</p>
<a class="link" th:href="${session.sy}">首页</a>
  • 不经过服务器解析,直接用浏览器打开HTML文件,看到的是『标签体原始值』
  • 经过服务器解析,Thymeleaf引擎根据th:text属性指定的『标签体新值』去替换『标签体原始值』
  • 任何HTML标签原有的属性,前面加上『th:』就都可以通过Thymeleaf来设定新值。
获取请求参数
<!--一个名字一个值-->
<p th:text="${param.username}">这里替换为请求参数的值</p>

<td th:text="${student.sid}">10000</td>
<td th:text="${student.sname}">测试人名</td>
<td th:text="${student.password}">123456</td>
<!--一个名字多个值-->
<p th:text="${param.team}">这里替换为请求参数的值</p>
<!--想要精确获取某一个值,可以使用数组下标-->
<p th:text="${param.team[0]}">这里替换为请求参数的值</p>
<p th:text="${param.team[1]}">这里替换为请求参数的值</p>
内置对象
<h3>表达式的基本内置对象</h3>
<p th:text="${#request.getClass().getName()}">这里显示#request对象的全类名</p>
<p th:text="${#request.getContextPath()}">调用#request对象的getContextPath()方法</p>
<p th:text="${#request.getAttribute('helloRequestAttr')}">调用#request对象的getAttribute()方法,读取属性域</p>
request.setAttribute("aNotEmptyList", Arrays.asList("aaa","bbb","ccc"));
request.setAttribute("anEmptyList", new ArrayList<>());
<p>#list对象isEmpty方法判断集合整体是否为空aNotEmptyList:<span th:text="${#lists.isEmpty(aNotEmptyList)}">测试#lists</span></p>
<p>#list对象isEmpty方法判断集合整体是否为空anEmptyList:<span th:text="${#lists.isEmpty(anEmptyList)}">测试#lists</span></p>
访问具体属性语法
对象类型 访问方式
普通对象包含使用getXxx()、setXxx()定义的属性 对象.属性名
List集合 [index]
数组 [下标]
Map集合 map.key Map集合[‘key’]
有方法的对象 对象.方法() 对象.方法(参数列表)

包含代码片段页面

声明代码片段

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    <!-- 使用th:fragment属性给代码片段命名 -->
    <div th:fragment="navigator">
        <a href="BookManagerServlet?method=showBookList" class="order">图书管理</a>
        <a href="order_manager.html" class="destory">订单管理</a>
        <a href="index.html" class="gohome">返回商城</a>
    </div>

</body>
</html>

引入代码片段

引入方式 作用
th:insert=“segment/admin-navigator :: navigator” 把目标代码片段整体包含到当前标签内部
th:include=“segment/admin-navigator :: navigator” 把目标代码片段内部的内容包含到当前标签内
th:replace=“segment/admin-navigator :: navigator” 使用目标代码片段替换当前标签

segment/admin-navigator代表目标代码片段的逻辑视图。

navigator代表目标代码片段中使用th:fragment指定的名称。

模板布局

使用th:fragment引用片段

在模板中,经常需要包含其他模板中的部分,例如页脚,页眉,菜单等部分。

为了做到这一点,Thymeleaf需要定义这些要包含的部分“片段”,可以使用 th:fragment 属性来完成

<div th:fragment="header">
    <p>被抽取出来的头部内容</p>
</div>
语法 效果
th:insert 把目标的代码片段整个插入到当前标签内部
th:replace 用目标的代码替换当前标签
th:include 把目标的代码片段去除最外层标签,然后再插入到当前标签内部
<!-- 代码片段所在页面的逻辑视图 :: 代码片段的名称 -->
<div id="badBoy" th:insert="segment :: header">
    div标签的原始内容
</div>

<div id="worseBoy" th:replace="segment :: header">
    div标签的原始内容
</div>

<div id="worstBoy" th:include="segment :: header">
    div标签的原始内容
</div>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <body>
  
    <div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
    </div>
  
  </body>
  
</html>
<body>
  ...
  <div th:insert="~{footer :: copy}"></div>
</body>

请注意,th:insert 需要一个片段表达式(**{…}**),这是一个包含片段的表达式。但是,在上面的示例中,是一个非复杂的片段表达式,()包围是完全可选的

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

片段表达式的语法非常简单。有三种不同的格式:

  • “~{templatename::selector}”,包括在名为 templatename 的模板上应用指定的标记选择器所产生的片段。请注意,selector 名称可能仅仅是片段名称,因此您可以使用 ~{templatename::fragmentname} 语法,像上面的 ~{footer :: copy} 一样指定简单的名称。

标记选择器语法由基础的AttoParser解析库定义,并且类似于XPath表达式或CSS选择器。有关更多信息,请参见附录:标签选择器语法。

  • “~{templatename}”,包括名为 templatename 的完整模板。

请注意,在 th:insert / th:replace 标记中使用的模板名称必须由模板引擎当前正在使用的模板解析器解析。

  • ~{::selector}~{this::selector},从同一模板插入一个片段,匹配 selector。如果在出现表达式的模板上未找到,则将模板调用(插入)堆栈遍历到原始处理的模板(root),直到 selector 在某个级别上匹配为止。

在上面的例子中,templatenameselector 都可以是全功能的表达式(甚至条件语句!),如:

<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>

再次注意 ~{…} 包围在 th:insert/th:replace 中是可选的

不使用th:fragment引用片段

...
<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>
...

我们可以使用上面的片段,简单地通过其 id 属性引用它,类似于CSS选择器:

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

th:insert、th:replace、th:include之间的差异

<footer th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</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; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>
  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>
  <div>
    &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>

可参数化的片段签名

为了为模板片段创建更类似于函数的机制,使用 th:fragment 定义的片段可以指定一组参数:

<div th:fragment="frag (onevar,twovar)">
    <p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

这需要使用th:insertth:replace两种语法之一来调用片段:

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

不带片段参数的片段局部变量

即使片段没有这样的参数定义:

<div th:fragment="frag">
    ...
</div>

我们可以使用上面指定的第二种语法来调用它们(并且只能用第二种):

<div th:replace="::frag (onevar=${value1},twovar=${value2})">

这将相当于组合 th:replaceth:with

<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">

th:assert模板内断言

th:assert 属性可以指定一个逗号分隔的表达式列表,应对其进行评估且每次评估都应为 true,否则将引发异常。

<div th:assert="${onevar},(${twovar} != 43)">...</div>

这对于验证片段签名中的参数非常有用:

<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...

更多标记片段

<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},~{})">
  <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>

删除模板片段

用于删除模拟行

th:remove 可以根据其值以五种不同的方式表现:

  • all:删除包含标签及其所有子标签。
  • body:请勿删除包含标签,而是删除其所有子标签。
  • tag:删除包含的标签,但不要删除其子级。
  • all-but-first:除去第一个标签以外的所有包含标签的子标签。
  • none: 什么也不做。该值对于动态评估很有用。
<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>

标签选择器语法

  • /x 表示名称为x的当前节点的直接子节点。
  • //x 表示任何深度的名称为x的当前节点的子节点。
  • x[@z=“v”] 表示名称为x的元素和名为z的属性,值为“v”。
  • x[@z1=“v1” and @z2=“v2”] 表示名称为x的元素,属性z1和z2分别为值“v1”和“v2”。
  • x[i] 表示名称为x的元素,位于其兄弟姐妹中的数字i中。
  • x[@z="v"][i] 表示名称为x的元素,属性z的值为“v”,并且在其兄弟姐妹中的数字i中也与此条件匹配。
  • x 完全等价于 //xx 在任何深度级别搜索具有名称或引用的元素,引用是属性 th:refth:fragment 属性)。
  • 选择器也允许没有元素名称/引用,只要它们包含参数规范。因此 [@class=‘oneclass’] 是一个有效的选择器,它使用带有值的class属性查找任何元素(标记)“oneclass”

局部变量

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 变量被创建为一个局部变量,并加入到上下文变量映射中,使得它可用于与在上下文中声明的任何其它变量一起评估,但仅在**<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>

优先级

优先级

注释和块

Thymeleaf模板中的任何位置都可以使用标准的HTML / XML注释 <!-- ... -->。这些注释中的所有内容均不会被Thymeleaf处理,并将一字不差的复制到结果页面

解析器级别的注释块是在Thymeleaf对其进行解析时,将从模板中简单删除的代码。

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

Thymeleaf将删除 之间的所有内容,因此,当模板静态打开时,这些注释块也可用于显示代码,因为Thymeleaf在处理模板时会将其删除

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

例如,对于带有很多 的表格进行原型制作可能会非常方便:

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

Thymeleaf仅原型注释块

Thymeleaf允许定义特殊注释块,当模板静态打开(即作为原型)时,标记为注释,但Thymeleaf在执行模板时将其视为正常标记。

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

th:block标签

th:block 仅是一个属性容器,允许模板开发人员指定所需的任何属性。Thymeleaf将执行这些属性,然后简单地使该块(而不是其内容)消失

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

示例

package servlets;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ViewBaseServlet extends HttpServlet {

    private TemplateEngine templateEngine;

    @Override
    public void init() throws ServletException {
        // 1.获取Servl etContext对象
        ServletContext servletContext = this.getServletContext();
        // 2.创建Thymeleaf解析器对象
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
        // 3.给解析器对象设置参数
        // ①HTML是默认模式,明确设置是为了代码更容易理解
        templateResolver.setTemplateMode(TemplateMode.HTML);
        // ②设置前缀
        String viewPrefix = servletContext.getInitParameter("view-prefix");
        templateResolver.setPrefix(viewPrefix);
        // ③设置后缀
        String viewSuffix = servletContext.getInitParameter("view-suffix");
        templateResolver.setSuffix(viewSuffix);
        // ④设置缓存过期时间(毫秒)
        templateResolver.setCacheTTLMs(60000L);
        // ⑤设置是否缓存
        templateResolver.setCacheable(true);
        // ⑥设置服务器端编码方式
        templateResolver.setCharacterEncoding("utf-8");
        // 4.创建模板引擎对象
        templateEngine = new TemplateEngine();
        // 5.给模板引擎对象设置模板解析器
        templateEngine.setTemplateResolver(templateResolver);
    }

    protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 1.设置响应体内容类型和字符集
        resp.setContentType("text/html;charset=UTF-8");
        // 2.创建WebContext对象
        WebContext webContext = new WebContext(req, resp, getServletContext());
        // 3.处理模板数据
        templateEngine.process(templateName, webContext, resp.getWriter());
    }
}
@WebServlet("/update")
public class UpdateServlet extends ViewBaseServlet{

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        // 设置响应内容类型
        req.setCharacterEncoding("utf-8");
        String sid = req.getParameter("sid");
        String cid = req.getParameter("cid");
        String grade = req.getParameter("grade");

        String sql="update classstu set sgrad =? where cid =? and sid=?;";
        JDBCCRUD.update(sql,grade,cid,sid);
        resp.sendRedirect("/teacher");
        super.processTemplate("teacherview",req,resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
}

其他

隐藏域

<input type="hidden" name ="fid" th:value="${fid}">

禁用按钮
当为首页时,禁用上一页

<input type="button" value="上一页" class="btn" 
	   th:onclick="|page(${session.page-1})|" 
	   th:disabled="${session.pagecount==1}" />