Java 前端模板引擎学习:thymeleaf 模板引擎

模板引擎接口 ITemplateEngine

 

一、后台数据与外部数据

1.处理后台数据

$表达式是个变量表达式,用于处理在  request parameters and the request, session and application 中的变量

  • ${x} will return a variable x stored into the Thymeleaf context or as a request attribute.
  • ${param.x} will return a request parameter called x (which might be multivalued).
  • ${session.x} will return a session attribute called x.
  • ${application.x} will return a servlet context attribute called x.
<input type="text" name="userName" value="James Carrot" th:value="${user.name}" />

  

2.处理外部数据

外部化的片段通常叫作 messages,#表达式用于处理这类消息。外部消息可以从数据库中获取,或从 .properties files 中获取,这取决于 StandardMessageResolver 的实现。Thymeleaf 的默认实现为 StandardMessageResolver。

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

为了实现属性名的  i18n,消息解析器 StandardMessageResolver 将/WEB-INF/templates/home.html 映射于同文件夹同名文 propreties 上,比如

  • /WEB-INF/templates/home_en.properties for English texts.
  • /WEB-INF/templates/home_es.properties for Spanish language texts.
  • /WEB-INF/templates/home_pt_BR.properties for Portuguese (Brazil) language texts.
  • /WEB-INF/templates/home.properties for default texts (if the locale is not matched).

注意在 spring boot 中,国际化消息直接映射到资源目录下,如 /i18n/ messages.properties  或 /i18n/ messages _en.properties

 

二、文本数据处理

1.不转义

如果 $-expression 获取的值包含 < >等,默认会对其转义为 &lt; &gt;

比如:

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

使用 th:text  ,默认转义成返回

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

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

使用 th:utext  返回

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

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

  

三、# 表达式

#{...} expressions 外部消息常用于实现国际化

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

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

你可以指定 #-expression 的 key 从 $-表达式获取:

<p th:utext="#{${welcomeMsgKey}">
  Welcome to our grocery store, Sebastian Pepper!
</p>

#-expression 中夹带 变量进行结合输出,#{} 中使用 () 包裹传入的变量,多个变量使用 , 分隔

home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!

<p th:utext="#{home.welcome(${session.user.name})}">
  Welcome to our grocery store, Sebastian Pepper!
</p>

* 特殊情况

模板连接变量,形成消息

<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>

消息变成变量,预处理

<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>

 

 

四、$ 表达式

${...} expressions 用于操作变量,基于 OGNL表达式 (在 springmvc 中使用 spel 代替 ognl),对其相应的 context中的 变量进行映射

 

为了更灵活地使用 ognl,也包含了 7 个预置对象,

  • #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>.

 * 内置对象的使用技巧:

访问 param, session, application 属性时,不需要加 #

// 直接访问对象域的属性
${param.foo}              // Retrieves a String[] with the values of request parameter 'foo'
${param.number[0]}   // Retrieves a String with the values of request parameter 'number',
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}

${session.foo}                 // 获取 session 中的属性
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}

${application.foo}              // Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}

# 直接访问 javax.servlet.http.HttpSession 对象
${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}

v注意:返回的是一个字符串数组: 

 

除了 7 个预置顶级对象,还有 16 个工具对象:

  • #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).

工具对象的使用,请查看原文。 常用工具类对象的操作方法:

# 格式化数字
${#numbers.formatDecimal(num,3,2)} // 整数位显示位数与小数精确位数
${#numbers.formatCurrency(num)} // 货币
${#numbers.formatPercent(num, 3, 2)}  // 百分号

# 格式化日期
${#dates.second(date)}
${#dates.createNow()}
${#dates.create(year,month,day)}
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}

# 操作字符串
${#strings.toString(obj)}  //打印对象

${#strings.trim(str)} //去除首尾空白

${#strings.containsIgnoreCase(name,'ez')} // 判断是否包含

${#strings.startsWith(name,'Don')}                  // also array*, list* and set*
${#strings.endsWith(name,endingFragment)}  // 判断前缀与后缀

${#strings.abbreviate(str,10)}  // 大于此位数内容,以省略号 ...代替

${#strings.indexOf(name,frag)}   
${#strings.substring(name,3,5)} 
${#strings.substringAfter(name,prefix)}
${#strings.substringBefore(name,suffix)}   // 截取字符串
${#strings.replace(name,'las','ler')}   // 替换字符串

${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}

${#strings.toUpperCase(name)}                       // also array*, list* and set*
${#strings.toLowerCase(name)} 
${#strings.capitalizeWords(str)} //首字母大写

${#strings.arraySplit(namesStr,',')}       // 返回 String[],用于遍历


# 聚集函数
${#aggregates.sum(array)} // 求何
${#aggregates.avg(array)} // 平均数

#随机数
${#strings.randomAlphanumeric(count)}

#遍历数字的工具
${#numbers.sequence(from,to)}
${#numbers.sequence(from,to,step)}

# 类型转换
${#conversions.convert(object, 'java.util.TimeZone')}

#转义字符串,防止JavaScript or JSON 注入
${#strings.escapeXml(str)}
${#strings.escapeJava(str)}
${#strings.escapeJavaScript(str)}
${#strings.unescapeJava(str)}
${#strings.unescapeJavaScript(str)} 

 

五、* 表达式

当对象被选择(使用 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 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>

此外,如果没有对象被选择,则 *-expression 与 $-expression 完全相当

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

 

六、A 标签处理

1.标签 th:href 与 @{} expressions

<!-- 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="@{}"

使用模板引擎管理 link  地址,方便对链接进行额外处理(如版本处理之类)

2.注意不可变部分,需要作预处理

<a href="#" th:href="@{__${#request.requestURI}__/learn}">Learn</a>

 

七、一些特殊的表达式语法

① 模板字符串代替 + 连接

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">

<span th:text="|Welcome to our application, ${user.name}!|">

下文 https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expressions-on-selections-asterisk-syntax

 

②预编译(预处理)

<tr th:each="book, itemStat : *{books}">
    <td><input th:field="*{books[__${itemStat.index}__].title}" /></td>
    <td><input th:field="*{books[__${itemStat.index}__].author}" /></td>
</tr>

也可以拆开写成 th:name 与 th:value (不推荐)

<tr th:each="book, itemStat : ${form.books}">
    <td><input hidden th:name="|books[${itemStat.index}].id|" th:value="${book.getId()}"/></td>
    <td><input th:name="|books[${itemStat.index}].title|" th:value="${book.getTitle()}"/></td>
    <td><input th:name="|books[${itemStat.index}].author|" th:value="${book.getAuthor()}"/></td>
</tr>

 

③ 代数运算,可以使用 div (/), mod (%). 等

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

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

 

④ 比较运算,可以使用 gt (>), lt (<), ge(>=), le (<=), not (!). Also eq (==), neq/ne (!=)

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

 

⑤ 条件表达式,其中 else 部分也可以忽略不写,则当为否时返回 null

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


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

 

⑥ 默认值表达式,当变量没有赋任何值时,使用该默认值

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

 

⑦ 无操作符号 no-operation token ,即使用当前文字节点

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

相当于

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

 

⑧ 自动应用转换服务

服务端配置文件 —— application.yml (这种配置只在页面格式化管用,在向 controller 传参时并不管用)

spring:
  mvc:
    date-format: yyyy-mm-dd

或 JavaCofig (页面和传参都管用)

@Override
public void addFormatters(final FormatterRegistry registry) {
    super.addFormatters(registry);
    registry.addFormatter(varietyFormatter());
    registry.addFormatter(dateFormatter());
}

@Bean
public VarietyFormatter varietyFormatter() {
    return new VarietyFormatter();
}

@Bean
public DateFormatter dateFormatter() {
    return new DateFormatter();
}

页面使用 双括号 {{ 与  }}

<td th:text="${{sb.datePlanted}}">13/01/2011</td>

 

⑨ 静态方法调用与 Bean 方法调用

将字符串转换为 Integer

T(Integer).parseInt(param.pagenum)

 或者引用自定义工具类

th:text="${T(com.package.SUtils).reverseString('hello')}"

使用 @引用 Bean

<span th:text="${@sUtils.reverseString('hello')}"></span>

 

⑩ 对集合数组进行 map 与 filter 处理

  • selection means filtering each element of the collection using its properties. The result of such an operation is a subset of the initial collection, only matching elements are retained

.

  • projection means filtering the property of each element in the collection. The result of a projection operation is a a new collectionwith the same number of elements than the original onebut containing only the filtered property of the elements, not the whole element object itself

示例:

map projection )

<tr th:each="artist,rowStat : ${listArtits.![firstname+' '+lastname]}">
    <td class="center middle" th:text="${rowStat.count}">1</td>
    <td class="center middle" th:text="${artist}">Michael Jackson</td>
</tr>

filter selection )

<tr th:each="artist,rowStat : ${listArtits.?[alive == true]}">
    <td class="center middle" th:text="${rowStat.count}">1</td>
    <td class="center middle" th:text="${artist.name}">Michael Jackson</td>
    <td class="center middle" th:text="${artist.discography}">Got to Be There, Ben, Music & Me, Forever Michael...</td>
    <td class="center middle" th:text="${artist.bio}">Michael Joseph Jackson (August 29, 1958 - June 25, 2009) was an American recording artist, entertainer, and businessman...</td>
</tr>

 

八、操作标签的属性值

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>

多个属性用逗号分开

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

或者属性联写

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

也可以直接使用 th:value 、 th:src、th:action 特殊属性(见 setting-value-to-specific-attributes )

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

or

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

增加属性值(Appending and prepending)

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

如果 cssStyle 是 warning 的话,则 class 会增加一个值

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

也可是使用更简便的  th:classappend

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

 

九、遍历迭代

 普通的迭代

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

指定迭代状态量 th:each="prod,iterStat : ${prods}" 

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

注意状态的几种属性

index: the current iteration index, starting with 0 (zero)
count: the number of elements processed so far
size: the total number of elements in the list
even/odd: checks if the current iteration index is even or odd
first:  checks if the current iteration is the first one
last: checks if the current iteration is the last one

* 如果不手动设定,可以使用默认实现的 状态变量后缀Stat,比如这里的 prodStat

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

* 如果遍历的内容需要更多 tr标签,可以使用 th:block,比如:

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

  

十、判断语句

 th:if  

  • If value is a boolean and is true.
  • If value is a number and is non-zero
  • If value is a character and is non-zero
  • If value is a String and is not “false”, “off” or “no”
  • If value is not a boolean, a number, a character or a String.
<a href="comments.html"
   th:href="@{/product/comments(prodId=${prod.id})}" 
   th:if="${not #lists.isEmpty(prod.comments)}">view</a>

相反 th:unless

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

或者,使用 switch-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>

  

十一、模板布局

1.基本用法

使用 th:fragment 定义 partial 模板

<!DOCTYPE html>

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

  <body>
  
    <div th:fragment="copy">
      © 2011 The Good Thymes Virtual Grocery
    </div>
  
  </body>
  
</html>

使用 th:insert 或 th:replace 插入模板(th:include 也可以,但在 3.0 后并不建议使用)

<body>

  ...

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

或者也可以给引号部分加一个 ~{} ,将器封装起来

<body>

  ...

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

 

2. th:insert 、th:replace 与 th:include 区别

定义模板

<footer th:fragment="copy">
  © 2011 The Good Thymes Virtual Grocery
</footer>

使用  div 标签引用模板

<body>

  ...

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

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

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

分别渲染出的结果:

<body>

  ...

  <div>
    <footer>
      © 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    © 2011 The Good Thymes Virtual Grocery
  </footer>

  <div>
    © 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>

得出结论:

  • th:insert 包括自身标签和子标签,
  • th:replace 包括子标签但不包括自身标签,
  • th:include 则是包括自身标签不包括子标签。

 

3.没有 th:fragment

定义模板

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

使用 id 属性引用该模板

<body>

  ...

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

 

 

4.模板中传递参数

① 普通变量

定义模板

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

调用模板,并传递相应的变量(注意这里调用的是当前模板文件中的 fragment,因此可以省略写成 th:replace=":: 函数名称 "

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

② 局部变量

定义模板

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

传递局部变量

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

上面的表达式,相当于

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

 

5.常见的技巧

① head中引用 title 与 link 块

定义模板

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

调用模板(注意这里调用的模板文件名称是 base

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

渲染结果

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

* 传递到模板的参数,除了使用 ${} 符号引入变量,或者使用 ~{:: 嵌入当前的标签,还可以使用 no-operation token 或者  参数留空。比如:

无操作符号 no-operation token 

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

参数留空

<head th:replace="base :: common_header(~{::title},~{})">

  <title>Awesome - Main</title>

</head>
...

 

② 条件调用模板

根据条件决定是否调用模板

...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
...

或者条件不满足,调用默认模板

...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

 

③ 隐藏或移除  mock 数据(感觉跟 fragment 没多大关系)

通常在我们会定义一些 mock 数据,用来做静态网页测试,查看渲染效果。像下面这样后两段 <tr> 作为 mock 数据。

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

由于使用 th:remove 标记,该内容将在服务端渲染时被移除。

移除的形式有很多种:

  • all: Remove both the containing tag and all its children.
  • body: Do not remove the containing tag, but remove all its children.
  • tag: Remove the containing tag, but do not remove its children.
  • all-but-first: Remove all children of the containing tag except the first one.
  • none : Do nothing. This value is useful for dynamic evaluation.

鉴于此,在 tbody 标签上使用 all-but-first

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

* 根据条件移除

<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>

  

 ④ 单独文件作为 partial 模板

定义模板

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

调用模板,并传入相应的参数

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

 

 十二、局部变量

 定义一个或多个局部变量,并使用它们

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

一个实用的例子,比如根据 i18n 文件读取格式化方式,来格式化日期,比如:

home_en.properties

date.format=MMMM dd'','' yyyy

home_es.properties

date.format=dd ''de'' MMMM'','' yyyy

定义局部变量,并引用它

<p th:with="df=#{date.format}">
  Today is: <span th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>

或者

<p>
  Today is: 
  <span th:with="df=#{date.format}" 
        th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>

  

十三、特殊页面处理

1. 分页页面

2. 表单页面

① 基本用法

<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post">
    <input type="text" th:field="*{datePlanted}" />
    ...
</form>

checkbox 处理(需要传两个对象,一个是全部属性 allFeatures,一个是当前选择的 features)

<ul>
  <li th:each="feat : ${allFeatures}">
    <input type="checkbox" th:field="*{features}" th:value="${feat}" />
    <label th:for="${#ids.prev('features')}" 
           th:text="#{${'seedstarter.feature.' + feat}}">Heating</label>
  </li>
</ul>

radio 处理

<ul>
  <li th:each="ty : ${allTypes}">
    <input type="radio" th:field="*{type}" th:value="${ty}" />
    <label th:for="${#ids.prev('type')}" th:text="#{${'seedstarter.type.' + ty}}">Wireframe</label>
  </li>
</ul>

dropdown 处理

<select th:field="*{type}">
  <option th:each="type : ${allTypes}" 
          th:value="${type}" 
          th:text="#{${'seedstarter.type.' + type}}">Wireframe</option>
</select>

 

② 动态 field:

需求: 当 command 对象有一个 collection property,对这个属性进行遍历,需要生成不同的 field

 示例:

创建书籍(显示页面)

@GetMapping("/create")
public String showCreateForm(Model model) {
    BooksCreationDto booksForm = new BooksCreationDto();
 
    for (int i = 1; i <= 3; i++) {
        booksForm.addBook(new Book());
    }
 
    model.addAttribute("form", booksForm);
    return "books/createBooksForm";
}

表单页面

<form action="#" th:action="@{/books/save}" th:object="${form}"
  method="post">
    <fieldset>
        <input type="submit" id="submitButton" th:value="Save">
        <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
        <table>
            <thead>
                <tr>
                    <th> Title</th>
                    <th> Author</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="book, itemStat : *{books}">
                    <td><input th:field="*{books[__${itemStat.index}__].title}" /></td>
                    <td><input th:field="*{books[__${itemStat.index}__].author}" /></td>
                </tr>
            </tbody>
        </table>
    </fieldset>
</form>

* 说明:

上面的模板通过 * 表达式选择该 集合属性,然后使用__ 预处理__表达式生成不同的 field(即带有数组样式的 id 与 name)渲染结果类似

<input id="rows1.seedsPerCell" name="rows[1].seedsPerCell" type="text" value="" />

添加书籍(提交页面)

@PostMapping("/save")
public String saveBooks(@ModelAttribute BooksCreationDto form, Model model) {
    bookService.saveAll(form.getBooks());
 
    model.addAttribute("books", bookService.findAll());
    return "redirect:/books/all";
}

 

③ 表单验证(* 关于 controller 部分请参考文末参考链接)

判断错误是否存在

<input type="text" th:field="*{datePlanted}" 
                   th:class="${#fields.hasErrors('datePlanted')}? fieldError" />

或者直接使用 th:errorclass

<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />

遍历该 field 的错误

<li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />

直接输出该 field 的错误

<p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>

 获取全部 field 错误

<div th:if="${#fields.hasAnyErrors()}">
  <p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
</div>

注意:

#fields.hasErrors('*') <=> #fields.hasAnyErrors()

#fields.errors('*') <=> #fields.allErrors()

 或通过 detailedErrors 详细遍历

<ul>
    <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
        <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
        <span th:text="${e.message}">The error message</span>
    </li>
</ul>

 

 

233

参考链接


https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#integrating-thymeleaf-with-spring

https://spring.io/guides/gs/validating-form-input/

转载于:https://www.cnblogs.com/lemos/p/9696751.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值