thymeleaf 基础教程-阅读官方教程(二)_thymeleaf菜鸟教程-程序员宅基地

技术标签: 【模板引擎】  

通过thymeleaf 基础教程-搭建杂货铺项目环境(一)我们把官方提供的示例项目进行部署 接下来我们跟着官方文档进行thymeleaf 学习。

我们这里从 2.1 版本的文档进行演示https://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html。 下面是通过游览器的翻译插件进行翻译教程api

1介绍Thymeleaf

1.1 什么是Thymeleaf

Thymeleaf是一个Java库。它是一个XML / XHTML / HTML5模板引擎,能够将一组转换应用于模板文件,以显示应用程序生成的数据和/或文本。

它更适合在Web应用程序中提供XHTML / HTML5,但它可以处理任何XML文件,无论是在Web中还是在独立应用程序中。

Thymeleaf的主要目标是提供一种优雅且格式良好的创建模板的方法。为了实现这一点,它基于XML标签和属性,它们定义了DOM(文档对象模型)上预定义逻辑的执行,而不是在模板中显式地将该逻辑写为代码。

其架构允许快速处理模板,依赖于解析文件的智能缓存,以便在执行期间使用尽可能少的I / O操作。

最后但并非最不重要的是,Thymeleaf的设计从一开始就考虑了XML和Web标准,如果您需要,可以创建完全验证的模板。

1.2 Thymeleaf过程可以使用哪种模板?

开箱即用,Thymeleaf允许您处理六种模板,每种模板称为模板模式:

  • XML
  • 有效的XML
  • XHTML
  • 有效的XHTML
  • HTML5
  • 旧版HTML5

所有这些模式都指的是格式良好的XML文件,但Legacy HTML5模式除外,它允许您处理HTML5文件,其中包括独立(非关闭)标记,没有值的标记属性或不在引号之间写入的标记属性。为了在这种特定模式下处理文件,Thymeleaf将首先执行转换,将您的文件转换为格式良好的XML文件,这些文件仍然是完全有效的HTML5(实际上是创建HTML5代码的推荐方法)1

另请注意,验证仅适用于XML和XHTML模板。

然而,这些并不是Thymeleaf可以处理的唯一模板类型,并且用户始终能够通过指定在此模式下解析模板的方法和编写结果的方式来定义他/她自己的模式。这样,任何可以建模为DOM树(无论是否为XML)的东西都可以被Thymeleaf有效地作为模板处理。

1.3方言:标准方言

Thymeleaf是一个极易扩展的模板引擎(实际上它应该更好地称为模板引擎框架),它允许您完全定义将在模板中处理的DOM节点以及它们的处理方式。

将一些逻辑应用于DOM节点的对象称为处理器,并且一组这些处理器 - 还有一些额外的工件 - 称为方言,其中Thymeleaf的核心库提供一个开箱即用的标准方言,这应该足以满足大部分用户的需求。

标准方言是本教程涵盖的方言。您将在以下页面中了解的每个属性和语法功能都由此方言定义,即使没有明确提及。

当然,如果用户希望在利用库的高级功能的同时定义自己的处理逻辑,则可以创建自己的方言(甚至扩展标准方言)。模板引擎可以一次配置多种方言。

官方的thymeleaf-spring3和thymeleaf-spring4集成包都定义了一种称为“SpringStandard方言”的方言,大部分等同于标准方言,但经过少量修改以更好地利用Spring Framework中的某些功能(例如,使用Spring Expression语言而不是Thymeleaf的标准OGNL)。因此,如果您是Spring MVC用户,那么您不会浪费时间,因为您在此处学习的几乎所有内容都将在Spring应用程序中使用。

Thymeleaf Standard Dialect可以在任何模式下处理模板,但特别适用于面向Web的模板模式(XHTML和HTML5模式)。除了HTML5,它还专门支持和验证以下XHTML规范:XHTML 1.0 TransitionalXHTML 1.0 StrictXHTML 1.0 FramesetXHTML 1.1

标准方言的大多数处理器都是属性处理器。这使得浏览器甚至可以在处理之前正确显示XHTML / HTML5模板文件,因为它们只会忽略其他属性。例如,虽然使用标记库的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}在模板的Thymeleaf处理期间评估得到的值代替。

如果需要,这将允许您的设计人员和开发人员处理相同的模板文件,并减少将静态原型转换为工作模板文件所需的工作量。这样做的能力通常称为自然模板

1.4整体架构

Thymeleaf的核心是DOM处理引擎。具体来说,它使用自己的高性能DOM实现 - 不是标准DOM API--用于构建模板的内存树表示,稍后通过遍历节点并在其上执行修改DOM的处理器来操作模板。当前配置和传递给模板的数据集,用于表示 - 称为上下文。

使用DOM模板表示使其非常适合Web应用程序,因为Web文档通常表示为对象树(实际上DOM树是浏览器在内存中表示网页的方式)。此外,基于大多数Web应用程序仅使用几十个模板的想法,这些模板不是大文件,并且在应用程序运行时它们通常不会更改,Thymeleaf使用解析模板DOM树的内存缓存允许它在生产环境中快速,因为大多数模板处理操作都需要很少的I / O(如果有的话)。

如果您想了解更多细节,本教程后面将有一整章专门介绍缓存以及Thymeleaf优化内存和资源使用的方式,以便更快地运行。

然而,存在一个限制:与其他模板解析/处理方法相比,此架构还需要为每个模板执行使用更大量的内存空间,这意味着您不应该使用该库来创建大数据XML文档(而不是网络文件)。作为一般经验法则(并且始终取决于JVM的内存大小),如果在单个模板执行中生成大小在几十兆字节的XML文件,则可能不应该使用Thymeleaf。

我们认为这种限制的原因仅适用于数据XML文件而不是Web XHTML / HTML5是因为您永远不应该生成如此大的Web文档以至于用户的浏览器设置为 ablaze 和/或 explode  - 请记住,这些浏览器也必须创建DOM树为您的页面!

1.5在继续之前,你应该阅读......
 

Thymeleaf特别适合在Web应用程序中工作。Web应用程序基于一系列标准,每个人都应该非常清楚,但很少有人 - 即使他们多年来一直与他们合作。

随着HTML5的出现,当今Web标准的最新技术比以往更加混乱...... 我们是否会从XHTML转向HTML?我们会放弃XML语法吗?为什么没有人再谈论XHTML 2.0了?

因此,在本教程中进一步讨论之前,强烈建议您阅读有关Thymeleaf网站上的文章“从HTML到HTML(通过HTML)”,您可以在以下地址找到该文章:http//www.thymeleaf.org /doc/articles/fromhtmltohtmlviahtml.html

2 The Good Thymes虚拟杂货店

2.1杂货店的网站

为了更好地解释使用Thymeleaf处理模板所涉及的概念,本教程将使用您可以从项目网站下载的演示应用程序。

此应用程序代表虚构的虚拟杂货店的网站,并将为我们提供足够的场景来举例说明各种Thymeleaf功能。

我们将需要一套非常简单的模型实体用于我们的应用程序:通过创建Products销售。我们还将管理这些:Customers Orders Comments Products

我们的小应用程序也将有一个非常简单的服务层,由Service包含以下方法的对象组成:

public class ProductService {

    ...

    public List<Product> findAll() {
        return ProductRepository.getInstance().findAll();
    }

    public Product findById(Integer id) {
        return ProductRepository.getInstance().findById(id);
    }
    
}

最后,在Web层,我们的应用程序将有一个过滤器,它将根据请求URL将执行委托给启用Thymeleaf的命令:

private boolean process(HttpServletRequest request, HttpServletResponse response)
        throws ServletException {
        
    try {
            
        /*
         * Query controller/URL mapping and obtain the controller
         * that will process the request. If no controller is available,
         * return false and let other filters/servlets process the request.
         */
        IGTVGController controller = GTVGApplication.resolveControllerForRequest(request);
        if (controller == null) {
            return false;
        }
        /*
         * Obtain the TemplateEngine instance.
         */
        TemplateEngine templateEngine = GTVGApplication.getTemplateEngine();
            
        /*
         * Write the response headers
         */
        response.setContentType("text/html;charset=UTF-8");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        /*
         * Execute the controller and process view template,
         * writing the results to the response writer.
         */
        controller.process(
                request, response, this.servletContext, templateEngine);

        return true;
            
    } catch (Exception e) {
        throw new ServletException(e);
    }
        
}    

这是我们的IGTVGController界面: 

public interface IGTVGController {

    public void process(
            HttpServletRequest request, HttpServletResponse response,
            ServletContext servletContext, TemplateEngine templateEngine);    
    
}

我们现在要做的就是创建IGTVGController接口的实现,从服务中检索数据并使用TemplateEngine对象处理模板。

最后,它看起来像这样:

但首先让我们看看该模板引擎是如何初始化的。

2.2创建和配置模板引擎

我们的过滤器中的process(...)方法包含这句话:

TemplateEngine templateEngine = GTVGApplication.getTemplateEngine();

这意味着GTVGApplication类负责创建和配置启用Thymeleaf的应用程序中最重要的对象之一:TemplateEngine实例。

我们的org.thymeleaf.TemplateEngine对象初始化如下:

public class GTVGApplication {
  
    
    ...
    private static TemplateEngine templateEngine;
    ...
    
    
    static {
        ...
        initializeTemplateEngine();
        ...
    }
    
    
    private static void initializeTemplateEngine() {
        
        ServletContextTemplateResolver templateResolver = 
            new ServletContextTemplateResolver();
        // XHTML is the default mode, but we set it anyway for better understanding of code
        templateResolver.setTemplateMode("XHTML");
        // This will convert "home" to "/WEB-INF/templates/home.html"
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        // Template cache TTL=1h. If not set, entries would be cached until expelled by LRU
        templateResolver.setCacheTTLMs(3600000L);
        
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        
    }
    
    ...

}

 当然,有很多方法可以配置TemplateEngine对象,但是现在这几行代码将足以告诉我们所需的步骤。

模板解析器

让我们从模板解析器开始:

ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();

 模板解析器是实现Thymeleaf API接口的对象,称为org.thymeleaf.templateresolver.ITemplateResolver

public interface ITemplateResolver {

    ...
  
    /*
     * Templates are resolved by String name (templateProcessingParameters.getTemplateName())
     * Will return null if template cannot be handled by this template resolver.
     */
    public TemplateResolution resolveTemplate(
            TemplateProcessingParameters templateProcessingParameters);

}

这些对象负责确定如何访问模板,在这个GTVG应用程序中,org.thymeleaf.templateresolver.ServletContextTemplateResolver我们使用的实现指定我们将从Servlet上下文中检索我们的模板文件作为资源:存在的应用程序范围的javax.servlet.ServletContext对象在每个Java Web应用程序中,考虑到Web应用程序root作为资源路径的根,它可以解析资源。

但这并不是我们可以说的关于模板解析器的全部内容,因为我们可以在其上设置一些配置参数。首先,模板模式,标准模式之一:

 

templateResolver.setTemplateMode("XHTML");

 XHTML是默认的模板模式ServletContextTemplateResolver,但最好还是建立它,以便我们的代码清楚地记录正在发生的事情。

templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");

这些前缀后缀完全符合它的样子:修改我们将传递给引擎的模板名称,以获取要使用的实际资源名称。

使用此配置,模板名称“product / list”将对应于:

servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html")

(可选)可以通过cacheTTLMs属性在Template Resolver中配置生成在缓存中的解析模板被视为有效的时间量:

templateResolver.setCacheTTLMs(3600000L);

当然,如果达到最大高速缓存大小并且它是当前高速缓存的最旧条目,则在到达TTL之前可以从高速缓存中驱逐模板。

用户可以通过实现ICacheManager接口或仅StandardCacheManager默认修改对象集来管理缓存来定义缓存行为和大小。

我们稍后会详细了解模板解析器。现在让我们看一下Template Engine对象的创建。

模板引擎

Template Engine对象属于org.thymeleaf.TemplateEngine类,这些是在当前示例中创建引擎的行:

templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);

 

相当简单,不是吗?我们所需要的只是创建一个实例并将模板解析器设置为它。

模板解析器是唯一需要的参数TemplateEngine,尽管当然还有许多其他参数将被覆盖(消息解析器,缓存大小等)。现在,这就是我们所需要的。

我们的模板引擎现已准备就绪,我们可以使用Thymeleaf开始创建我们的页面。


3使用文本 

3.1多语言欢迎

我们的第一个任务是为我们的杂货网站创建一个主页。

我们将编写此页面的第一个版本非常简单:只是标题和欢迎消息。这是我们的/WEB-INF/templates/home.html文件:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      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>

你会在这里注意的第一件事是这个文件是XHTML,可以被任何浏览器正确显示,因为它不包含任何非XHTML标签(并且浏览器忽略了他们不理解的所有属性,例如th:text)。此外,浏览器将以标准模式(不是在quirks mode下)显示它,因为它具有格式良好的DOCTYPE声明。

接下来,这也是有效的 XHTML 2,因为我们已经指定了一个Thymeleaf DTD,它定义了类似的属性,th:text以便您的模板可以被认为是有效的。甚至更多:一旦模板被处理(并且所有th:*属性都被删除),Thymeleaf将自动用DOCTYPE标准的DTD声明替换该子句中的DTD声明XHTML 1.0 Strict(我们将在后面的章节中保留此DTD转换功能)。

还为th:*属性声明了thymeleaf命名空间:

 

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

请注意,如果我们根本不关心模板的有效性或格式良好,我们可以简单地指定一个标准XHTML 1.0 Strict DOCTYPE,而不是xmlns名称空间声明:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

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

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

</html>

......这仍然是Thymeleaf在XHTML模式下完全可以处理的(尽管我们的IDE可能会让我们的生活变得非常悲惨,并且在各处显示警告)。

但足够的验证。现在,对于模板中真正有趣的部分:让我们看看该th:text属性是关于什么的。

Using th:text and externalizing text

 

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

消息始终具有标识它们的键,Thymeleaf允许您使用以下#{...}语法指定文本应与特定消息相对应:

 

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

我们在这里看到的实际上是Thymeleaf标准方言的两个不同特征:

  • th:text属性评估其值表达式并将此评估的结果设置为它所在标记的主体,有效地替换了我们在代码中看到的“欢迎来到我们的杂货店!”文本。
  • #{home.welcome}表达,在指定的标准表达式语法,指定要由所使用的文本th:text属性应与该消息home.welcome对应于哪个语言环境,我们正在处理与模板键。

现在,这个外化文本在哪里?

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

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

此标准消息解析程序期望/WEB-INF/templates/home.html在同一文件夹中找到.properties文件中的消息,并使用与模板相同的名称,例如:

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

这就是我们将Thymeleaf流程作为模板所需的全部内容。让我们创建我们的Home控制器。

这里所说的 外化文本(externalizing text) 其实就是在模板路径下 和访问页面名称一模一样的配置文件 如下图

他会根据request中的getLocale()方法获取当前的地区 如果有具体国际化配置文件就取国际化的配置文件 如果没有就取 home.properties

因为HomeController 中locale 中的值是 zh_CN 但是模板目录下没有home_zh_CN.properties 配置文件所以home.welcome的值取得就是 home.properties 中的值

如果我们添加  home_zh_CN.properties 并且内容如下 

 

上下文

为了处理我们的模板,我们将创建一个HomeController实现IGTVGController我们之前看到的接口的类:

ublic class HomeController implements IGTVGController {

    public void process(
            HttpServletRequest request, HttpServletResponse response,
            ServletContext servletContext, TemplateEngine templateEngine) {
        
        WebContext ctx = 
            new WebContext(request, response, servletContext, request.getLocale());
        templateEngine.process("home", ctx, response.getWriter());
        
    }

}

我们在这里可以看到的第一件事是创建一个context。Thymeleaf context是实现org.thymeleaf.context.IContext接口的对象。context 应包含在变量映射中执行模板引擎所需的所有数据,并且还引用必须用于外部化消息的区域设置。 

public interface IContext {

    public VariablesMap<String,Object> getVariables();
    public Locale getLocale();
    ...
    
}

 这个界面有一个专门的扩展,org.thymeleaf.context.IWebContext

public interface IWebContext extends IContext {
    
    public HttpSerlvetRequest getHttpServletRequest();
    public HttpSession getHttpSession();
    public ServletContext getServletContext();
    
    public VariablesMap<String,String[]> getRequestParameters();
    public VariablesMap<String,Object> getRequestAttributes();
    public VariablesMap<String,Object> getSessionAttributes();
    public VariablesMap<String,Object> getApplicationAttributes();
    
}

Thymeleaf核心库提供了以下每个接口的实现: 

  • org.thymeleaf.context.Context implements IContext
  • org.thymeleaf.context.WebContext implements IWebContext

 正如您在控制器代码中看到的那样,WebContext我们将使用它。实际上我们必须这样做,因为使用a ServletContextTemplateResolver要求我们使用context 实现IWebContext

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

 

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

从接口定义我们可以看出,WebContext它将提供用于获取请求参数和请求,会话和应用程序属性的专用方法。但实际上WebContext会做的不仅仅是:

 

 

  • 将所有请求属性添加到上下文变量映射中。
  • 添加一个param包含所有请求参数的上下文变量。
  • 添加一个session包含所有会话属性的上下文变量。
  • 添加一个名为application包含所有ServletContext属性的上下文变量。

在执行之前,将一个特殊变量设置到所有上下文对象(实现IContext)中,包括两者,ContextWebContext称为执行信息(execInfo)。此变量包含两个可在模板中使用的数据:

  • 模板名称(${execInfo.templateName}),为引擎执行指定的名称,以及与正在执行的模板相对应的名称。
  • 当前日期和时间(${execInfo.now}),Calendar对应于模板引擎开始执行此模板的时刻的对象。 

执行模板引擎

准备好上下文对象后,我们需要的只是执行指定模板名称和上下文的模板引擎,并传递响应编写器以便可以将响应写入它:

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

 让我们使用西班牙语语言环境查看结果:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

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

3.2有关文本和变量的更多信息

未转义的文字

我们主页的最简单版本现在似乎已经准备就绪,但有一些我们没有想过的......如果我们有这样的消息怎么办?

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

如果我们像以前一样执行此模板,我们将获得: 

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

这不完全符合我们的预期,因为我们的<b>标签已被转义,因此它将在浏览器中显示。

这是th:text属性的默认行为。如果我们希望Thymeleaf尊重我们的XHTML标签而不是逃避它们,我们将不得不使用不同的属性:( th:utext对于“非转义文本”):

 使用和显示变量

现在让我们在主页上添加更多内容。例如,我们可能希望在欢迎消息下方显示日期,如下所示:

Welcome to our fantastic grocery store!

Today is: 12 july 2010

首先,我们必须修改控制器,以便将该日期添加为上下文变量:

我们在String上下文中添加了一个今日变量,现在我们可以在模板中显示它: 

正如您所看到的,我们仍在使用th:text作业的属性(这是正确的,因为我们想要替换标签的主体),但这次语法有点不同而不是#{...}表达式值,我们使用的是${...}一。这是一个变量表达式值,它包含一个名为OGNL(对象图导航语言)的语言表达式,该表达式将在上下文变量映射中执行。

${today}表达式只是表示“get the variable called today”,但这些表述可能更加复杂(如${user.name}“获取被叫用户的变量,并调用它的getName()方法”)。

属性值有很多可能性:消息,变量表达式......还有很多。下一章将向我们展示所有这些可能性。

 4标准表达式语法

 

我们将在我们的杂货虚拟商店的开发中稍作休息,以了解Thymeleaf标准方言中最重要的部分之一:Thymeleaf标准表达式语法。

我们已经看到在这种语法中表达的两种类型的有效属性值:消息和变量表达式:

但是我们还不知道还有更多类型的价值,还有更多有趣的细节来了解我们已经知道的。首先,让我们看一下标准表达式功能的快速摘要:

  • 简单表达:
    • 变量表达式: ${...}
    • 选择变量表达式: *{...}
    • 消息表达式: #{...}
    • 链接网址表达式: @{...}
  • 字面
    • 文本文字:'one text''Another one!',...
    • 号码文字:0343.012.3,...
    • 布尔文字:truefalse
    • 空字面: null
    • 文字标记:onesometextmain,...
  • 文字操作:
    • 字符串连接: +
    • 文字替换: |The name is ${name}|
  • 算术运算:
    • 二元运算符:+-*/%
    • 减号(一元运算符): -
  • 布尔运算:
    • 二元运算符:andor
    • 布尔否定(一元运算符): !not
  • 比较和平等:
    • 比较:><>=<=gtltgele
    • 平等运营商:==!=eqne

有条件的运营商:

  • IF-THEN: (if) ? (then)
  • IF-THEN-ELSE: (if) ? (then) : (else)
  • 默认: (value) ?: (defaultvalue) 

所有这些功能都可以组合和嵌套:

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

4.1 消息 

我们已经知道,#{...}消息表达式允许我们链接:

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

......对此: 

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

但是有一个方面我们还没有想到:如果消息文本不是完全静态会发生什么?例如,如果我们的应用程序知道谁是随时访问该网站的用户并且我们想要通过名字问候他/她怎么办? 

<p>¡Bienvenido a nuestra tienda de comestibles, John Apricot!</p>

这意味着我们需要在消息中添加一个参数。像这样: 

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

根据java.text.MessageFormat标准语法指定参数,这意味着您可以将格式添加到该类的API文档中指定的数字和日期。

为了为我们的参数指定一个值,并给定一个调用的HTTP会话属性user,我们将: 

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

如果需要,可以指定几个参数,用逗号分隔。实际上,消息密钥本身可以来自变量: 

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

4.2变量 

我们已经提到${...}表达式实际上是在上下文中包含的变量映射上执行的OGNL(对象 - 图形导航语言)表达式。

有关OGNL语法和功能的详细信息,请阅读OGNL语言指南:http//commons.apache.org/ognl/

从OGNL的语法,我们知道这个:

<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>

......实际上相当于: 

ctx.getVariables().get("today");

但是OGNL允许我们创建更强大的表达式,这就是: 

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

...实际上通过执行以下方式获取用户名: 

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

 但是getter方法导航只是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('-')}

表达式基本对象 

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

  • #ctx:上下文对象。
  • #vars: 上下文变量。
  • #locale:上下文区域设置。
  • #httpServletRequest:(仅限Web Contexts)HttpServletRequest对象。
  • #httpSession:(仅限Web Contexts)HttpSession对象。

 所以我们可以这样做:

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

 您可以在附录A中阅读这些对象的完整参考。

Expression Utility对象

 

除了这些基本对象,Thymeleaf还将为我们提供一组实用程序对象,帮助我们在表达式中执行常见任务。

  • #datesjava.util.Date对象的实用方法:格式化,组件提取等。
  • #calendars:类似于#dates,但java.util.Calendar对象。
  • #numbers:用于格式化数字对象的实用方法。
  • #stringsString对象的实用方法:contains,startsWith,prepending / appending等。
  • #objects:一般的对象的实用方法。
  • #bools:布尔评估的实用方法。
  • #arrays:数组的实用方法。
  • #lists:列表的实用方法。
  • #sets:集合的实用方法。
  • #maps:地图的实用方法。
  • #aggregates:用于在数组或集合上创建聚合的实用程序方法。
  • #messages:用于在变量表达式中获取外部化消息的实用程序方法,与使用#{...}语法获取它们的方式相同。
  • #ids:用于处理可能重复的id属性的实用程序方法(例如,作为迭代的结果)。

您可以在附录B中查看每个实用程序对象提供的功能。

在我们的主页中重新格式化日期

现在我们了解这些实用程序对象,我们可以使用它们来改变我们在主页中显示日期的方式。而不是在我们这样做HomeController

SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar cal = Calendar.getInstance();

WebContext ctx = new WebContext(request, servletContext, request.getLocale());
ctx.setVariable("today", dateFormat.format(cal.getTime()));

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

......我们可以做到这一点: 

WebContext ctx = new WebContext(request, servletContext, request.getLocale());
ctx.setVariable("today", Calendar.getInstance());

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

...然后在视图层本身中执行日期格式设置: 

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

4.3选择表达式(星号语法) 

变量表达式不仅可以用${...}表达式编写,还可以用于表达式*{...}

但是有一个重要的区别:星号语法评估所选对象的表达式而不是整个上下文变量映射。这是:只要没有选定对象,$和*语法就会完全相同。

什么是对象选择的东西?一个th:object属性。让我们在我们的用户个人资料(userprofile.html)页面中使用它:

 

  <div th:object="${session.user}">
    <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
    <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
    <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
  </div>

这完全等同于:

<div>
  <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>

 当然,$ 和 * 语法可以混合使用:

<div th:object="${session.user}">
  <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

当对象选择到位时,所选对象也可用作美元表达式作为#object表达式变量:

<div th:object="${session.user}">
  <p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

如上所述,如果没有执行任何对象选择,则美元和星号语法完全相同。

<div>
  <p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
  <p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>

 4.4链接URL

由于它们的重要性,URL是Web应用程序模板中的一等公民,而Thymeleaf Standard Dialect具有特殊的语法,@语法:@{...}

有不同类型的网址:

  • 绝对的URL,比如 http://www.thymeleaf.org
  • 相对URL,可以是:
    • 页面相对,像 user/login.html
    • 上下文相关,如/itemdetails?id=3(服务器中的上下文名称将自动添加)
    • 与服务器相关,~/billing/processInvoice(允许在同一服务器中调用另一个上下文(=应用程序)中的URL)。
    • 协议相对URL,如 //code.jquery.com/jquery-2.0.3.min.js

Thymeleaf可以在任何情况下处理绝对URL,但对于相对URL,它将要求您使用实现IWebContext接口的上下文对象,其中包含来自HTTP请求的一些信息以及创建相对链接所需的信息。

让我们使用这种新语法。符合th:href属性:

 

<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" 
   th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

 

这里要注意的一些事情:

  • th:href属性修饰符属性:一旦处理,它将计算要使用的链接URL,并将<a>标记的href属性设置为此URL。
  • 我们被允许对URL参数使用表达式(如您所见orderId=${o.id})。还将自动执行所需的URL编码操作。
  • 如果需要几个参数,这些参数将用逗号分隔 @{/order/process(execId=${execId},execType='FAST')}
  • URL路径中也允许使用变量模板,例如 @{/order/{orderId}/details(orderId=${orderId})}
  • /(like /order/details)开头的相对URL 将自动作为应用程序上下文名称的前缀。
  • 如果未启用cookie或尚未知道cookie,则";jsessionid=..."可能会在相对URL中添加后缀,以便保留会话。这称为URL重写,Thymeleaf允许您通过response.encodeURL(...)Servlet API为每个URL 插入自己的重写过滤器。
  • th:href标记允许我们(可选)有一个静态的工作href在我们的模板属性,所以,当为原型的目的直接打开我们的模板链接仍然通航由浏览器。

与消息语法(#{...})的情况一样,URL基数也可以是评估另一个表达式的结果:

 

<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

我们主页的菜单

现在我们知道如何创建链接URL,如何在我们的家中为网站中的其他一些页面添加一个小菜单?

<p>Please select an option</p>
<ol>
  <li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>
  <li><a href="order/list.html" th:href="@{/order/list}">Order List</a></li>
  <li><a href="subscribe.html" th:href="@{/subscribe}">Subscribe to our Newsletter</a></li>
  <li><a href="userprofile.html" th:href="@{/userprofile}">See User Profile</a></li>
</ol>

 服务器根目录相对URL

可以使用其他语法来创建服务器根相对(而不是上下文根相对)URL,以便链接到同一服务器中的不同上下文。这些URL将被指定为@{~/path/to/something}

4.5文字

 ###文字文字

文本文字只是在单引号之间指定的字符串。它们可以包含任何字符,但您应该将其中的任何单引号转义为\'

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

###布尔文字 

 布尔文字是truefalse。例如:

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

 请注意,在上面的例子中,它== false被写在括号之外,因此是Thymeleaf本身负责它。如果它是在大括号内写的,那么它将由OGNL / SpringEL引擎负责:

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

 

### null文字

null文本也可用于:

 

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

### Literal标记

实际上,数字,布尔和空文字是文字标记的特例。

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

好的部分?令牌不需要任何围绕它们的引号。所以我们可以这样做:

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

代替:

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

 4.6附加文本

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

th:text="'The name of the user is ' + ${user.name}"

 4.7字面替换

文字替换允许轻松格式化包含变量值的字符串,而无需附加文字'...' + '...'

这些替换必须用竖线(|)包围,如:

 

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

这实际上相当于:

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

 文字替换可以与其他类型的表达相结合:

<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">

注意:${...}|...|文字替换中只允许使用变量表达式()。没有其他文字('...'),布尔/数字标记,条件表达式等。 

4.8算术运算 

一些算术运算也可用:+-*/%

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

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

4.9比较器和平等

在表达式中的值可以与进行比较><>=<=符号,像往常一样,也是==!=运营商可以被用于检查平等(或缺乏)。请注意,XML确定不应在属性值中使用<>符号,因此应将它们替换为&lt;&gt;

<span style="color:#333333"><code class="language-html">th:if="${prodStat.count} &gt; 1"
th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')"</code></span>

 请注意,其中一些运算符存在文本别名:gt>),lt<),ge>=),le<=),not!)。还eq==),neqne!=)。

4.10条件表达式

 

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

让我们看一个示例片段(这次引入另一个属性修饰符th:class):

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

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

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

4.11默认表达式(Elvis运算符) 

一个默认的表情是一种特殊的条件值的没有那么一部分。它等同于某些语言(如Groovy)中存在的Elvis运算符,并允许指定两个表达式,即第二个仅在第一个返回null的情况下计算的表达式。

让我们在用户个人资料页面中看到它:

 

正如您所看到的,运算符是?:,并且我们在此处使用它来指定名称的默认值(在这种情况下为文字值),仅当评估结果*{age}为null时。因此,这相当于:

<p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>

与条件值一样,它们可以包含括号之间的嵌套表达式:

4.12预处理

除了用于表达处理的所有这些功能外,Thymeleaf还为我们提供了预处理表达式的可能性。

什么是预处理的东西?它是在正常表达式之前完成的表达式的执行,它允许修改最终将执行的实际表达式。

预处理表达式与普通表达式完全相同,但显示为双下划线符号(如__${expression}__)。

让我们假设我们有一个Messages_fr.properties包含OGNL表达式的i18n 条目,该表达式调用特定于语言的静态方法,如:

......和a Messages_es.properties equivalent

我们可以创建一个标记片段,根据语言环境评估一个表达式或另一个表达式。为此,我们将首先选择表达式(通过预处理),然后让Thymeleaf执行它:

请注意,法语区域设置的预处理步骤将创建以下等效项:

__可以使用在属性中对预处理字符串进行转义\_\_

5设置属性值

本章将解释我们可以在标记标记中设置(或修改)属性值的方式,这可能是设置标记正文内容后我们将需要的下一个最基本的功能。

5.1设置任何属性的值

假设我们的网站发布了一个时事通讯,我们希望我们的用户能够订阅它,所以我们创建一个/WEB-INF/templates/subscribe.html带有以下形式的模板:

<form action="subscribe.html">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe me!" />
  </fieldset>
</form>

它看起来很不错,但事实是这个文件看起来更像是静态XHTML页面而不是Web应用程序的模板。首先,我们表单中的action属性静态链接到模板文件本身,因此没有地方可以进行有用的URL重写。其次,提交按钮中的value属性使其显示英文文本,但我们希望将其国际化。 

然后输入th:attr属性,以及更改其设置的标记属性值的能力:

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe me!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

 这个概念非常简单:th:attr只需要一个为属性赋值的表达式。创建了相应的控制器和消息文件后,处理此文件的结果将如预期的那样:

<form action="/gtvg/subscribe">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="¡Suscríbeme!"/>
  </fieldset>
</form>

除了新的属性值之外,您还可以看到applicacion上下文名称已自动添加到URL基础中/gtvg/subscribe,如前一章所述。

但是,如果我们想一次设置多个属性呢?XML规则不允许您在标记中设置两次属性,因此th:attr将采用以逗号分隔的分配列表,例如:

 给定所需的消息文件,这将输出:

5.2为特定属性设置值

到现在为止,您可能会想到以下内容:

 

......是一个非常丑陋的标记。在属性值中指定赋值可能非常实用,但如果您必须始终执行此操作,则它不是创建模板的最佳方式。

Thymeleaf同意你的意见。这就是为什么事实上th:attr在模板中几乎没有使用。通常,您将使用th:*其任务设置特定标记属性的其他属性(而不仅仅是任何属性th:attr)。

标准方言为我们设置value按钮的属性提供了哪些属性?嗯,以一种相当明显的方式,它是th:value。我们来看一下:

这看起来好多了!让我们尝试actionform标记中的属性执行相同操作:

你还记得th:href我们home.html之前放过的东西吗?它们正是同样的属性:

 

有很多这样的属性,每个属性都针对特定的XHTML或HTML5属性:

 

th:abbr th:accept th:accept-charset
th:accesskey th:action th:align
th:alt th:archive th:audio
th:autocomplete th:axis th:background
th:bgcolor th:border th:cellpadding
th:cellspacing th:challenge th:charset
th:cite th:class th:classid
th:codebase th:codetype th:cols
th:colspan th:compact th:content
th:contenteditable th:contextmenu th:data
th:datetime th:dir th:draggable
th:dropzone th:enctype th:for
th:form th:formaction th:formenctype
th:formmethod th:formtarget th:frame
th:frameborder th:headers th:height
th:high th:href th:hreflang
th:hspace th:http-equiv th:icon
th:id th:keytype th:kind
th:label th:lang th:list
th:longdesc th:low th:manifest
th:marginheight th:marginwidth th:max
th:maxlength th:media th:method
th:min th:name th:optimum
th:pattern th:placeholder th:poster
th:preload th:radiogroup th:rel
th:rev th:rows th:rowspan
th:rules th:sandbox th:scheme
th:scope th:scrolling th:size
th:sizes th:span th:spellcheck
th:src th:srclang th:standby
th:start th:step th:style
th:summary th:tabindex th:target
th:title th:type th:usemap
th:value th:valuetype th:vspace
th:width th:wrap th:xmlbase
th:xmllang th:xmlspace

 5.3一次设置多个值

 

有两个叫比较特殊的属性th:alt-titleth:lang-xmllang可用于同时设置两个属性相同的值。特别:

  • th:alt-title将设置alttitle
  • th:lang-xmllang将设置langxml:lang

对于我们的GTVG主页,这将允许我们替换:

......或者这个,相当于:

…这样: 

5.4附加和预先 

th:attrThymeleaf 以与之相同的方式提供th:attrappendth:attrprepend属性,它们将评估结果附加(后缀)或前置(前缀)到现有属性值。

例如,您可能希望将要添加的CSS类的名称(未设置,仅添加)存储到上下文变量中的某个按钮,因为要使用的特定CSS类将取决于用户执行的操作。之前。简单:

如果您在cssStyle变量设置为的情况下处理此模板"warning",您将获得:

标准方言中还有两个特定的附加属性th:classappendth:styleappend属性,用于向元素添加CSS类或样式片段而不覆盖现有元素: 

(不要担心该th:each属性。它是一个迭代属性,我们稍后会讨论它。) 

 5.5固定值布尔属性

某些XHTML / HTML5属性的特殊之处在于,它们在元素中以特定和固定值存在,或者根本不存在。

例如,checked

除了"checked"根据XHTML标准允许的checked属性之外没有其他值(HTML5规则稍微宽松一点)。而同样的情况与disabledmultiplereadonlyselected

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

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

th:async th:autofocus th:autoplay
th:checked th:controls th:declare
th:default th:defer th:disabled
th:formnovalidate th:hidden th:ismap
th:loop th:multiple th:novalidate
th:nowrap th:open th:pubdate
th:readonly th:required th:reversed
th:scoped th:seamless th:selected

 

5.6支持HTML5友好的属性和元素名称

也可以使用完全不同的语法将处理器应用于模板,更加HTML5友好。

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

还有一种语法来指定自定义标签:{prefix}-{name},它遵循W3C自定义元素规范(较大的W3C Web组件规范的一部分)。例如,这可以用于th:block元素(或者也可以th-block),这将在后面的部分中解释。

重要提示:此语法是对命名空间语法的补充th:*,它不会替换它。完全没有意图在将来弃用命名空间语法。

 6迭代

到目前为止,我们已经创建了一个主页,一个用户个人资料页面以及一个允许用户订阅我们的新闻通讯的页面......但是我们的产品呢?我们不应该建立一个产品清单,让访客知道我们卖的是什么吗?嗯,显然是的。我们现在去了。

6.1迭代基础知识

要在我们的/WEB-INF/templates/product/list.html页面中列出我们的产品,我们需要一个table。我们的每个产品都会显示在一行(一个<tr>元素),因此对于我们的模板,我们需要创建一个模板行 - 一个将说明我们希望如何显示每个产品的模板 - 然后指示Thymeleaf 迭代它一次对于每个产品。

标准方言为我们提供了一个属性,th:each

使用th:each

对于我们的产品列表页面,我们需要一个控制器,它从服务层检索产品列表并将其添加到模板上下文中:

然后我们将th:each在我们的模板中使用迭代产品列表: 

 

prod : ${prods}你在上面看到的属性值是指“在评估结果中的每个元素${prods},重复模板的设置,元素转换成变量,名为PROD这个片段”。让我们给出一个我们看到的每个事物的名称:

  • 我们将调用${prods}迭代式迭代变量
  • 我们将调用prod迭代变量或者干脆ITER变量

请注意,proditer变量仅在<tr>元素内部可用(包括内部标记<td>)。

可重复的值

不仅java.util.List可以在Thymeleaf中使用对象进行迭代。实际上,有一组非常完整的对象被属性认为是可迭代th:each

  • 任何对象实现 java.util.Iterable
  • 任何对象实现java.util.Map。迭代地图时,iter变量将属于类java.util.Map.Entry
  • 任何数组
  • 任何其他对象都将被视为包含对象本身的单值列表。

6.2保持迭代状态

使用th:each,Thymeleaf时,提供了一种有助于跟踪迭代状态的机制:状态变量

状态变量在th:each属性中定义,包含以下数据:

  • 当前迭代索引,从0开始。这是index属性。
  • 当前迭代索引,从1开始。这是count属性。
  • 迭代变量中元素的总量。这是size属性。
  • 每次迭代的iter变量。这是current属性。
  • 当前迭代是偶数还是奇数。这些是even/odd布尔属性。
  • 当前迭代是否是第一个迭代。这是first布尔属性。
  • 当前迭代是否是最后一次迭代。这是last布尔属性。

让我们看看我们如何在前面的例子中使用它:

如您所见,状态变量(iterStat在此示例中)是在th:each属性中通过在iter变量本身之后写出其名称来定义的,用逗号分隔。与iter变量一样,状态变量仅在包含该th:each属性的标记定义的代码片段内可用。

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

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

  <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 colspan="1" rowspan="1">NAME</th>
        <th colspan="1" rowspan="1">PRICE</th>
        <th colspan="1" rowspan="1">IN STOCK</th>
      </tr>
      <tr>
        <td colspan="1" rowspan="1">Fresh Sweet Basil</td>
        <td colspan="1" rowspan="1">4.99</td>
        <td colspan="1" rowspan="1">yes</td>
      </tr>
      <tr class="odd">
        <td colspan="1" rowspan="1">Italian Tomato</td>
        <td colspan="1" rowspan="1">1.25</td>
        <td colspan="1" rowspan="1">no</td>
      </tr>
      <tr>
        <td colspan="1" rowspan="1">Yellow Bell Pepper</td>
        <td colspan="1" rowspan="1">2.50</td>
        <td colspan="1" rowspan="1">yes</td>
      </tr>
      <tr class="odd">
        <td colspan="1" rowspan="1">Old Cheddar</td>
        <td colspan="1" rowspan="1">18.75</td>
        <td colspan="1" rowspan="1">yes</td>
      </tr>
    </table>
  
    <p>
      <a href="/gtvg/" shape="rect">Return to home</a>
    </p>

  </body>
  
</html>

请注意,我们的迭代状态变量已经完美地工作,odd仅将CSS类建立到奇数行(行计数从0开始)。

<td>标签中的所有colspan和rowspan属性以及其中的形状<a>都由Thymeleaf根据所选XHTML 1.0 Strict标准的DTD自动添加,该标准将这些值建立为这些属性的默认值(请记住,我们的模板没有不要为它们设置一个值。完全不用担心它们,因为它们不会影响页面的显示。例如,如果我们使用HTML5(没有DTD),那么永远不会添加这些属性。

如果您没有显式设置状态变量,Thymeleaf将始终通过后缀Stat为迭代变量的名称为您创建一个:

 

 7条件评估

7.1简单条件:“if”和“unless

有时,如果满足某个条件,您只需要在模板中显示模板片段。

例如,假设我们希望在产品表中显示一列,其中包含每个产品的评论数量,如果有任何评论,则指向该产品的评论详细信息页面的链接。

为此,我们将使用以下th:if属性:

这里有很多东西要看,所以让我们关注重要的一点:

事实上,这个代码几乎无法解释:我们将创建一个指向评论页面(带有URL /product/comments)的链接,其prodId参数设置为id产品的参数,但前提是该产品有任何评论。

让我们看看生成的标记(删除默认视图的默认rowspancolspan属性):

 

<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <tr>
    <td>Fresh Sweet Basil</td>
    <td>4.99</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Italian Tomato</td>
    <td>1.25</td>
    <td>no</td>
    <td>
      <span>2</span> comment/s
      <a href="/gtvg/product/comments?prodId=2">view</a>
    </td>
  </tr>
  <tr>
    <td>Yellow Bell Pepper</td>
    <td>2.50</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Old Cheddar</td>
    <td>18.75</td>
    <td>yes</td>
    <td>
      <span>1</span> comment/s
      <a href="/gtvg/product/comments?prodId=4">view</a>
    </td>
  </tr>
</table>

完善!这正是我们想要的。

请注意,该th:if属性不仅会评估布尔条件。它的功能稍微超出了它,它将按照true以下规则评估指定的表达式:

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

另外,th:if有一个负面的对应物,th:unless我们可以在前面的例子中使用它而不是not在OGNL表达式中使用:

7.2切换语句

还有一种方法可以使用Java中的等效开关结构有条件地显示内容:th:switchth:case属性集。

它们完全按照您的预期工作:

请注意,只要th:case评估true一个th:case属性,就会将同一切换上下文中的每个其他属性评估为false

默认选项指定为th:case="*"

8模板布局 

8.1包括模板片段

定义和引用片段

 

我们经常希望在模板中包含来自其他模板的片段。常见的用途是页脚,标题,菜单......

为了做到这一点,Thymeleaf需要我们定义可用于包含的片段,我们可以通过使用该th:fragment属性来完成。

现在假设我们想在所有杂货页面添加标准的版权页脚,为此我们定义了一个/WEB-INF/templates/footer.html包含以下代码的文件:

上面的代码定义了一个叫做的片段copy,我们可以使用其中一个th:includeth:replace属性轻松地在我们的主页中包含它:

 

 这两个包含属性的语法非常简单。有三种不同的格式:

  • "templatename::domselector"或等效templatename::[domselector]包含在名为的模板上执行指定DOM选择器所产生的片段templatename
    • 请注意,domselector可以仅仅是一个片段的名字,所以你可以指定为简单的东西templatename::fragmentname就像在footer :: copy上面。

    DOM Selector语法类似于XPath表达式和CSS选择器,有关此语法的详细信息,请参阅附录C.

  • "templatename"包含名为的完整模板templatename

    请注意,您在th:includeth:replacetags中使用的模板名称必须由模板引擎当前使用的模板解析器解析。

  • ::domselector""this::domselector"包含来自同一模板的片段。

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

没有引用片段 th:fragment

此外,由于DOM选择器的强大功能,我们可以包含不使用任何th:fragment属性的片段。它甚至可以是来自不同应用程序的标记代码,完全不了解Thymeleaf:

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

th:include和之间的区别th:replace 

和之间有什么区别th:includeth:replace?虽然th:include将片段的内容包含在其主机标签中,但th:replace实际上将用片段替换主机标签。这样一个HTML5片段就像这样:

...在主机<div>标签中包含两次,如下所示:

......将导致: 

th:substituteby属性也可以用作别名th:replace,但建议使用后者。请注意,th:substituteby在将来的版本中可能会弃用。 

 8.2可参数化的片段签名

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

 这需要使用这两种语法之一的呼叫从片段th:includeth:replace

请注意,在最后一个选项中,顺序并不重要: 

###片段没有片段签名的局部变量

即使片段定义没有签名,如下所示:

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

 这将是,实际上,相当于一个组合th:includeth:with

 请注意,片段的局部变量的这种规范 - 无论它是否具有签名 - 都不会导致上下文先前被清空到其执行。片段仍然可以访问调用模板中使用的每个上下文变量,就像它们当前一样。

### th:断言in-template断言

th:assert属性可以指定一个以逗号分隔的表达式列表,这些表达式应该被评估并为每次评估生成true,否则会引发异常。

 

这对于验证片段签名的参数非常方便: 

8.3删除模板片段 

让我们重新审视我们的产品列表模板的最新版本:

这段代码作为一个模板很好,但作为一个静态页面(当浏览器直接打开而没有Thymeleaf处理它时)它就不会成为一个好的原型。

为什么?因为虽然浏览器可以完美显示,但该表只有一行,而且这行包含模拟数据。作为原型,它看起来不够逼真......我们应该有多个产品,我们需要更多行

所以让我们添加一些:

好的,现在我们有三个,对原型来说肯定更好。但是......当我们用Thymeleaf处理它时会发生什么?: 

 

最后两行是模拟行!嗯,当然它们是:迭代仅适用于第一行,所以没有理由为什么Thymeleaf应该删除其他两个。

我们需要一种在模板处理过程中删除这两行的方法。让我们th:remove在第二个和第三个<tr>标签上使用该属性:

 

<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
    <td>
      <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
      <a href="comments.html" 
         th:href="@{/product/comments(prodId=${prod.id})}" 
         th:unless="${#lists.isEmpty(prod.comments)}">view</a>
    </td>
  </tr>
  <tr class="odd" th:remove="all">
    <td>Blue Lettuce</td>
    <td>9.55</td>
    <td>no</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr th:remove="all">
    <td>Mild Cinnamon</td>
    <td>1.99</td>
    <td>yes</td>
    <td>
      <span>3</span> comment/s
      <a href="comments.html">view</a>
    </td>
  </tr>
</table>

 处理完毕后,所有内容都将按原样重复:

<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <tr>
    <td>Fresh Sweet Basil</td>
    <td>4.99</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Italian Tomato</td>
    <td>1.25</td>
    <td>no</td>
    <td>
      <span>2</span> comment/s
      <a href="/gtvg/product/comments?prodId=2">view</a>
    </td>
  </tr>
  <tr>
    <td>Yellow Bell Pepper</td>
    <td>2.50</td>
    <td>yes</td>
    <td>
      <span>0</span> comment/s
    </td>
  </tr>
  <tr class="odd">
    <td>Old Cheddar</td>
    <td>18.75</td>
    <td>yes</td>
    <td>
      <span>1</span> comment/s
      <a href="/gtvg/product/comments?prodId=4">view</a>
    </td>
  </tr>
</table>

 

all属性中的那个值又是什么意思呢?嗯,实际上th:remove可以采用五种不同的方式,具体取决于其价值:

  • all:删除包含标记及其所有子标记。
  • body:不要删除包含标记,但删除其所有子标记。
  • tag:删除包含标记,但不删除其子项。
  • all-but-first:除第一个子项外,删除包含标记的所有子项。
  • none: 没做什么。此值对于动态评估很有用。

这个all-but-first价值有什么用呢?它将让我们th:remove="all"在原型设计时节省一些:

 

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

th:remove属性可采取任何Thymeleaf标准表示,因为它返回允许字符串值中的一个,只要(alltagbodyall-but-firstnone)。

这意味着删除可能是有条件的,例如:

另请注意,th:remove考虑null同义词none,以便以下工作与上面的示例完全相同:

在这种情况下,如果${condition}为false,null将返回,因此不会执行删除。

9局部变量 

Thymeleaf将局部变量称为为模板的特定片段定义的变量,并且仅可用于在该片段内进行评估。

我们已经看到的一个例子是prod我们的产品列表页面中的iter变量:

prod变量仅在<tr>标签的键内可用。特别:

  • 它将可用于th:*在该标记中执行的任何其他属性,其优先级低于th:each(这意味着它们将在之后执行th:each)。
  • 它将可用于<tr>标记的任何子元素,例如<td>元素。

Thymeleaf为您提供了一种无需迭代即可声明局部变量的方法。它是th:with属性,其语法类似于属性值赋值:

 

th:with被处理时,该firstPer变量被创建为一个局部变量,并添加到地图从上下文来的变量,因此,它是可用于评估在从一开始上下文中声明任何其他变量,但仅在的边界包含<div>标签。

您可以使用通常的多重赋值语法同时定义多个变量:

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

我们在Grocery的主页上使用它!还记得我们为输出格式化日期而编写的代码吗?

那么,如果我们想要"dd MMMM yyyy"实际依赖于语言环境怎么办?例如,我们可能希望将以下消息添加到我们的home_en.properties

......和我们相同的一个home_es.properties

现在,让我们使用th:with将本地化的日期格式转换为变量,然后在th:text表达式中使用它:

那简洁干净。事实上,鉴于这一事实th:with具有较高的precedenceth:text,我们可以解决这一切的span标签:

你可能在想:优先权?我们还没有谈过这个!好吧,不要担心,因为这正是下一章的内容。

10属性优先级

th:*在同一个标​​签中写入多个属性会发生什么?例如:

当然,我们希望该th:each属性在之前执行,th:text以便我们得到我们想要的结果,但是鉴于DOM(文档对象模型)标准没有给出a的属性的顺序有任何意义。写入标记时,必须在属性本身中建立优先级机制,以确保它能按预期工作。

因此,所有Thymeleaf属性都定义了一个数字优先级,它确定了它们在标记中执行的顺序。这个顺序是:

 

订购 特征 属性
1 片段包含 th:include
th:replace
2 片段迭代 th:each
3 有条件的评估 th:if
th:unless
th:switch
th:case
4 局部变量定义 th:object
th:with
一般属性修改 th:attr
th:attrprepend
th:attrappend
6 具体属性修改 th:value
th:href
th:src
...
7 文字(标签正文修改) th:text
th:utext
8 片段规范 th:fragment
9 片段删除 th:remove

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

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

11. Comments and Blocks

11.1。标准HTML / XML注释

标准的HTML / XML注释<!-- ... -->可以在thymeleaf 模板中的任何地方使用。这些注释中的任何内容都不会被Thymeleaf和浏览器处理,并且只会逐字复制到结果中:

      11.2 Thymeleaf解析器级注释块

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

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

Thymeleaf将消除绝对之间的一切<!--/**/-->,所以这些注释块也可以用于显示当模板是静态开放代码,知道当thymeleaf处理它,它都将被删除:

 对于具有大量原型的表进行原型设计,这可能非常方便<tr>,例如:

11.3. Thymeleaf prototype-only comment blocks 

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

Thymeleaf allows the definition of special comment blocks marked to be comments when the template is open statically (i.e. as a prototype), but considered normal markup by Thymeleaf when executing the template.

这段感觉翻译的太恶心所以贴上原来英文解释

Thymeleaf的解析系统将简单地删除<!--/*//*/-->标记,但不删除其内容,因此将保留未注释。因此,在执行模板时,Thymeleaf实际上会看到:

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

11.4. Synthetic th:block tag

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

th:block是一个纯粹的属性容器,允许模板开发人员指定他们想要的任何属性。Thymeleaf将执行这些属性,然后简单地使块消失而无痕迹。

因此,在创建<tr>每个元素需要多个迭代表时,它可能很有用:

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

 注意这个解决方案如何让模板成为有效的HTML(不需要在<div>里面添加禁止的块<table>),并且在浏览器中作为原型静态打开时仍然可以正常工作!

12 Inlining(内连)

12.1 Text inlining(文本内连)

虽然标准方言允许我们通过使用标记属性来完成我们可能需要的几乎所有操作,但在某些情况下我们可能更喜欢将表达式直接写入HTML文本。例如,我们更喜欢这样写:

 ......而不是这个:

表达式之间[[...]]的表达式被认为是Thymeleaf中的表达式内联,并且在它们中,您可以使用在th:text属性中也有效的任何类型的表达式。 

为了使内联工作,我们必须使用th:inline属性激活它,该属性有三个可能的值或模式(textjavascriptnone)。我们来试试吧text

持有的标签th:inline不必是包含内联表达式的标签,任何父标签都可以: 

所以你现在可能会问:为什么我们从一开始就不这样做?它的代码少于所有这些 th:text 属性!好吧,小心那里,因为虽然你可能会发现内联非常有趣,你应该永远记住,当你静态打开它们时,内联表达式将逐字显示在你的HTML文件中,所以你可能无法再将它们用作原型了!

浏览器静态显示我们的代码片段而不使用内联的区别...

 

......并使用它...... 

Hello, [[${session.user.name}]]!

......很清楚。 

12.2 Script inlining (JavaScript and Dart)(脚本内联(JavaScript和Dart)

Thymeleaf为其内联功能提供了一系列“脚本”模式,因此您可以将数据集成到以某些脚本语言创建的脚本中。

当前的脚本模式是javascriptth:inline="javascript")和dartth:inline="dart")。

我们可以用脚本内联做的第一件事就是将表达式的值写入我们的脚本,例如:

/*[[...]]*/语法,指示Thymeleaf评估包含的表达式。但这里有更多含义:

  • 作为javascript comment(/*...*/),在浏览器中静态显示页面时,我们的表达式将被忽略。
  • 内联表达式('Sebastian')之后的代码将在静态显示页面时执行。
  • Thymeleaf将执行表达式并插入结果,但它也将删除内联表达式本身之后的行中的所有代码(静态显示时执行的部分)。

因此,执行此操作的结果将是:

 您也可以在没有相同效果的注释的情况下执行此操作,但这会使您的脚本在静态加载时失败:

请注意,此评估是智能的,不仅限于字符串。Thymeleaf会正确地用Javascript / Dart语法编写以下几种对象: 

  • 字符串
  • 数字
  • 布尔
  • 数组
  • 集合
  • 地图
  • Bean(具有gettersetter方法的对象)

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

 该${session.user}表达式将评估为一个User对象,Thymeleaf将正确地将其转换为Javascript语法:

添加代码 

使用javascript内联时的另一个功能是能够在特殊注释语法之间包含代码,/*[+...+]*/以便Thymeleaf在处理模板时自动取消注释该代码: 

将被执行为: 

您可以在这些注释中包含表达式,并将对它们进行评估:

 删除代码

也可以让Thymeleaf删除特殊/*[- *//* -]*/注释之间的代码,如下所示:

13 Validation and Doctypes ()

13.1 Validating templates(验证模板)

 

正如前面提到的,Thymeleaf为我们提供外的开箱即处理它们之前验证我们的模板两个标准的模板模式:VALIDXMLVALIDXHTML.这些模式要求我们的模板,不仅格式良好的XML(这是他们应该永远是),但在事实根据指定有效DTD

问题是,如果我们使用VALIDXHTML带有模板的模式,包括如下的DOCTYPE子句:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

...我们将获得验证错误,因为th:*标签不存在根据DTD.那是完全正常的,因为W3C显然没有理由在他们的标准中包含Thymeleaf的功能但是,我们如何解决它?通过改变DTD.

Thymeleaf包含一组DTD文件,这些文件反映了XHTML标准中的原始文件,但添加th:*了标准方言中的所有可用属性。这就是我们在模板中使用它的原因:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">

 

SYSTEM标识符指示Thymeleaf解析器解析特殊的Thymeleaf启用XHTML 1.0 Strict DTD文件并使用它来验证我们的模板。并且不要担心那http件事,因为那只是一个标识符,DTD文件将在本地读取Thymeleaf的jar文件。

请注意,因为此DOCTYPE声明是完全有效的声明,如果我们打开浏览器以静态显示我们的模板作为原型,它将以标准模式呈现。

在这里,您可以获得DTD所有受支持的XHTML风格的完整Thymeleaf 声明集:

 

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-transitional-thymeleaf-4.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-frameset-thymeleaf-4.dtd">
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml11-thymeleaf-4.dtd">

另请注意,为了让您的IDE满意,即使您没有在验证模式下工作,您还需要thhtml标记中声明命名空间: 

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

13.2 Doctype translation

我们的模板很适合DOCTYPE

 

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">

 

但是我们的Web应用程序将XHTML文档发送DOCTYPE到客户端浏览器并不合适,因为:

  • 它们不是PUBLIC(它们是SYSTEM DOCTYPEs),因此我们的网络无法通过W3C验证器验证。
  • 它们不是必需的,因为一旦处理完毕,所有th:*标签都会消失。

这就是为什么Thymeleaf包含DOCTYPE翻译机制的原因,它将自动将特定于你的特定叶片的XHTML翻译DOCTYPE成标准DOCTYPEs。

例如,如果您的模板是XHTML 1.0 Strict,则如下所示:

 

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">

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

在使Thymeleaf处理模板后,您生成的XHTML将如下所示:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
    ... 
</html>

 您无需为这些转换做任何事情:Thymeleaf将自动处理它们。

14我们的杂货店还有一些页面

 

现在我们对使用Thymeleaf了解很多,我们可以在我们的网站上添加一些新页面来进行订单管理。

请注意,我们将重点关注XHTML代码,但如果要查看相应的控制器,可以查看捆绑的源代码。

14.1订单清单

让我们从创建订单列表页面开始/WEB-INF/templates/order/list.html

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      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>

    <h1>Order list</h1>
  
    <table>
      <tr>
        <th>DATE</th>
        <th>CUSTOMER</th>
        <th>TOTAL</th>
        <th></th>
      </tr>
      <tr th:each="o : ${orders}" th:class="${oStat.odd}? 'odd'">
        <td th:text="${#calendars.format(o.date,'dd/MMM/yyyy')}">13 jan 2011</td>
        <td th:text="${o.customer.name}">Frederic Tomato</td>
        <td th:text="${#aggregates.sum(o.orderLines.{purchasePrice * amount})}">23.32</td>
        <td>
          <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
        </td>
      </tr>
    </table>
  
    <p>
      <a href="../home.html" th:href="@{/}">Return to home</a>
    </p>
    
  </body>
  
</html>

 这里没有什么可以让我们感到惊讶,除了这一点OGNL魔法:

<td th:text="${#aggregates.sum(o.orderLines.{purchasePrice * amount})}">23.32</td>

 

这样做,对于顺序中的每个订单行(OrderLine对象),将其purchasePriceamount属性相乘(通过调用相应的getPurchasePrice()getAmount()方法)并将结果返回到数字列表中,稍后由#aggregates.sum(...)函数聚合以获得订单总数价钱。

你必须喜欢OGNL的动力。(You’ve got to love the power of OGNL.

14.2订单详情

现在,对于订单详细信息页面,我们将在其中大量使用星号语法:

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      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 th:object="${order}">

    <h1>Order details</h1>

    <div>
      <p><b>Code:</b> <span th:text="*{id}">99</span></p>
      <p>
        <b>Date:</b>
        <span th:text="*{#calendars.format(date,'dd MMM yyyy')}">13 jan 2011</span>
      </p>
    </div>

    <h2>Customer</h2>

    <div th:object="*{customer}">
      <p><b>Name:</b> <span th:text="*{name}">Frederic Tomato</span></p>
      <p>
        <b>Since:</b>
        <span th:text="*{#calendars.format(customerSince,'dd MMM yyyy')}">1 jan 2011</span>
      </p>
    </div>
  
    <h2>Products</h2>
  
    <table>
      <tr>
        <th>PRODUCT</th>
        <th>AMOUNT</th>
        <th>PURCHASE PRICE</th>
      </tr>
      <tr th:each="ol,row : *{orderLines}" th:class="${row.odd}? 'odd'">
        <td th:text="${ol.product.name}">Strawberries</td>
        <td th:text="${ol.amount}" class="number">3</td>
        <td th:text="${ol.purchasePrice}" class="number">23.32</td>
      </tr>
    </table>

    <div>
      <b>TOTAL:</b>
      <span th:text="*{#aggregates.sum(orderLines.{purchasePrice * amount})}">35.23</span>
    </div>
  
    <p>
      <a href="list.html" th:href="@{/order/list}">Return to order list</a>
    </p>

  </body>
  
</html>

 除了这个嵌套对象选择之外,这里没什么新东西:

......这*{name}实际上相当于:

 15有关配置的更多信息

15.1模板解析器

对于Good Thymes Virtual Grocery,我们选择了一个ITemplateResolver名为的实现ServletContextTemplateResolver,它允许我们从Servlet Context获取模板作为资源。

除了让你能够通过实现ITemplateResolver,Thymeleaf 创建自己的模板解析器包括其他三个开箱即用的实现:

  • org.thymeleaf.templateresolver.ClassLoaderTemplateResolver,将模板解析为类加载器资源,如:

  • org.thymeleaf.templateresolver.FileTemplateResolver,将模板解析为文件系统中的文件,如:

 org.thymeleaf.templateresolver.UrlTemplateResolver,将模板解析为URL(甚至是非本地的),如:

 所有预绑定的实现都ITemplateResolver允许相同的配置参数集,包括:

前缀和后缀(已经看到):

允许使用与文件名不直接对应的模板名称的模板别名。如果同时存在后缀/前缀和别名,则将在前缀/后缀之前应用别名:

 读取模板时要应用的编码:

 默认模板模式,以及为特定模板定义其他模式的模式:

模板缓存的默认模式,以及用于定义特定模板是否可缓存的模式:

解析模板缓存条目的TTL(以毫秒为单位)源自此模板解析程序。如果未设置,则从缓存中删除条目的唯一方法是LRU(超出缓存最大大小且条目是最旧的)。

此外,可以为模板引擎指定多个模板解析器,在这种情况下,可以在它们之间建立订单以进行模板解析,这样,如果第一个无法解析模板,则会询问第二个,依此类推:

ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
classLoaderTemplateResolver.setOrder(Integer.valueOf(1));

ServletContextTemplateResolver servletContextTemplateResolver = new ServletContextTemplateResolver();
servletContextTemplateResolver.setOrder(Integer.valueOf(2));

templateEngine.addTemplateResolver(classLoaderTemplateResolver);
templateEngine.addTemplateResolver(servletContextTemplateResolver);

当应用多个模板解析器时,建议为每个模板解析器指定模式,以便Thymeleaf可以快速丢弃那些不打算解析模板的模板解析器,从而提高性能。这样做不是必需的,而是优化:

ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
classLoaderTemplateResolver.setOrder(Integer.valueOf(1));
// This classloader will not be even asked for any templates not matching these patterns 
classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/layout/*.html");
classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/menu/*.html");

ServletContextTemplateResolver servletContextTemplateResolver = new ServletContextTemplateResolver();
servletContextTemplateResolver.setOrder(Integer.valueOf(2));

 15.2消息解析器

我们没有为Grocery应用程序明确指定Message Resolver实现,正如之前所解释的那样,这意味着所使用的实现是一个org.thymeleaf.messageresolver.StandardMessageResolver对象。

这就StandardMessageResolver,是以已经解释过的方式查找与模板同名的消息文件,实际上是Thymeleaf核心提供的唯一消息解析器实现,但是当然你可以通过实现org.thymeleaf.messageresolver.IMessageResolver接口来创建自己的消息。

Thymeleaf + Spring集成包提供了一种IMessageResolver实现,它使用标准的Spring方法,通过使用MessageSource对象来检索外化消息。

如果要向模板引擎添加消息解析器(或更多),该怎么办?简单:

// For setting only one
templateEngine.setMessageResolver(messageResolver);

// For setting more than one
templateEngine.addMessageResolver(messageResolver);

 为什么你想拥有多个消息解析器?与模板解析器相同的原因:消息解析器是有序的,如果第一个无法解析特定消息,第二个将被询问,然后是第三个,等等。

15.3 Logging

Thymeleaf非常关注日志记录,并始终尝试通过其日志记录界面提供最大量的有用信息。

使用的日志库slf4j,实际上充当了您可能希望在应用程序中使用的任何日志记录实现的桥梁(例如,log4j)。

Thymeleaf班会记录TRACEDEBUGINFO-level信息,这取决于你的愿望的详细程度,并且除了一般的记录它会使用与TemplateEngine类,你可以为不同的目的而单独配置相关的三个特殊记录器:

  • org.thymeleaf.TemplateEngine.CONFIG 将在初始化期间输出库的详细配置。
  • org.thymeleaf.TemplateEngine.TIMER 将输出有关处理每个模板所用时间的信息(对基准测试很有用!)
  • org.thymeleaf.TemplateEngine.cache是一组记录器的前缀,用于输出有关高速缓存的特定信息。虽然缓存记录器的名称可由用户配置,因此可能会更改,但默认情况下它们是:
    • org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE
    • org.thymeleaf.TemplateEngine.cache.FRAGMENT_CACHE
    • org.thymeleaf.TemplateEngine.cache.MESSAGE_CACHE
    • org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE

使用Thymeleaf的日志记录基础结构的示例配置log4j可以是:

 16模板缓存

 

Thymeleaf的工作得益于DOM处理引擎和一系列处理器 - 每种类型的节点都需要应用逻辑 - 修改文档的DOM树,以便通过将此树与您的数据相结合来创建您期望的结果。

它还包括-by default-一个存储已解析模板的缓存,即在处理模板文件之前读取和解析模板文件所产生的DOM树。这在Web应用程序中工作时特别有用,并基于以下概念构建:

  • 输入/输出几乎总是任何应用程序中最慢的部分。与内存进程相比,内存进程非常快。
  • 克隆现有的内存中DOM树总是比读取模板文件,解析它并为其创建新的DOM对象树要快得多。
  • Web应用程序通常只有几十个模板。
  • 模板文件是中小型的,并且在应用程序运行时不会修改它们。

这一切都导致了这样的想法,即在不浪费大量内存的情况下缓存Web应用程序中最常用的模板是可行的,并且它还将节省大量时间,这些时间将花费在一小组文件上的输入/输出操作上事实上,这永远不会改变。

我们如何控制这个缓存?首先,我们之前已经了解到,我们可以在模板解析器中启用或禁用它,甚至只对特定模板执行操作:

 此外,我们可以通过建立自己的缓存管理器对象来修改其配置,该对象可以是默认StandardCacheManager实现的实例:

org.thymeleaf.cache.StandardCacheManager有关配置缓存的更多信息,请参阅javadoc API 。

可以从模板缓存中手动删除条目:

17附录A:表达式基本对象 

一些对象和变量映射始终可以在变量表达式中调用(由OGNL或SpringEL执行)。我们来看看他们:

基础对象

  • #ctx:上下文对象。它将是一个实现org.thymeleaf.context.IContextorg.thymeleaf.context.IWebContext取决于我们的环境(独立或Web)。如果我们使用Spring集成模块,它将是一个实例org.thymeleaf.spring[3|4].context.SpringWebContext

  • #locale:直接访问java.util.Locale与当前请求关联的。

 

#varsorg.thymeleaf.context.VariablesMap上下文中所有变量的实例(通常包含在#ctx.variables加上本地变量中的变量)。

针对此对象评估非限定表达式。事实上,${something}完全等同于(但比更美丽)${#vars.something}

#root 是同一个对象的同义词。

请求/会话属性的Web上下文命名空间等。

 

在Web环境中使用Thymeleaf时,我们可以使用一系列快捷方式来访问请求参数,会话属性和应用程序属性:

请注意,这些不是上下文对象,而是作为变量添加到上下文中的映射,因此我们不使用它们#。因此,在某种程度上,它们充当命名空间

  • param:用于检索请求参数。${param.foo}是一个String[]带有foo请求参数值的,因此${param.foo[0]}通常用于获取第一个值。

  • session:用于检索会话属性。

  • application:用于检索应用程序/ servlet上下文属性。

 

 请注意,无需为访问请求属性(与请求参数相对)指定名称空间,因为所有请求属性都会自动作为上下文根中的变量添加到上下文中:

 Web上下文对象

 

在Web环境中,还可以直接访问以下对象(请注意这些是对象,而不是映射/命名空间):

  • #httpServletRequest:直接访问javax.servlet.http.HttpServletRequest与当前请求关联的对象。

  • #httpSession:直接访问javax.servlet.http.HttpSession与当前请求关联的对象。

Spring上下文对象(Spring context objects)

如果您从Spring使用Thymeleaf,您还可以访问这些对象:

  • #themes:提供与Spring spring:themeJSP标记相同的功能。

 Spring beans

Thymeleaf还允许以Spring EL定义的标准方式访问在Spring Application Context中注册的bean,该方法使用语法@beanName,例如:

18附录B:表达式实用程序对象

日期

  • #datesjava.util.Date对象的实用方法:

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.Dates
 * ======================================================================
 */

/*
 * 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对象:

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.Calendars
 * ======================================================================
 */

/*
 * 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:数字对象的实用方法:

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.Numbers
 * ======================================================================
 */

/*
 * ==========================
 * Formatting integer 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')}



/*
 * ==========================
 * Utility methods
 * ==========================
 */

/*
 * Create a sequence (array) of integer numbers going
 * from x to y
 */
${
     #numbers.sequence(from,to)}
${
     #numbers.sequence(from,to,step)}

字符串

  • #stringsString对象的实用方法:

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.Strings
 * ======================================================================
 */

/*
 * 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:一般对象的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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:布尔评估的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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:数组的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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:列表的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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:集合的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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)}

map

  • #maps:地图的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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:用于在数组或集合上创建聚合的实用程序方法

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.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)}

消息

  • #messages:用于在变量表达式中获取外部化消息的实用程序方法,与使用#{...}语法获取它们的方式相同。

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.Messages
 * ======================================================================
 */

/*
 * Obtain externalized messages. Can receive a single key, a key plus arguments,
 * or an array/list/set of keys (in which case it will return an array/list/set of 
 * externalized messages).
 * If a message is not found, a default message (like '??msgKey??') is returned.
 */
${
     #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)}

/*
 * Obtain externalized messages or null. Null is returned instead of a default
 * message if a message for the specified key is not found.
 */
${
     #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)}

标识

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

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.expression.Ids
 * ======================================================================
 */

/*
 * 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')}

19附录C:DOM选择器语法

 

DOM选择器借用XPATH,CSS和jQuery的语法特性,以便提供一种功能强大且易于使用的方式来指定模板片段。

例如,以下选择器将在标记内的每个位置选择每个<div>content

 

源自XPath的基本语法包括:

  • /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在任何深度级别搜索具有名称或引用的元素)。

  • 选择器也允许没有元素名称/引用,只要它们包含参数规范。所以[@class='oneclass']是一个有效的选择器,它查找具有值为“oneclass”的class属性的任何元素(标签)。

高级属性选择功能:

  • 除了=(相等)之外,其他比较运算符也是有效的:( !=不等于),^=(以...开头)和$=(以...结尾)。例如:x[@class^='section']表示具有名称的元素x和以... class开头的属性值section

  • 可以从@(XPath样式)和不带(jQuery样式)开始指定属性。所以x[z='v']相当于x[@z='v']

  • 多属性修饰符既可以与and(XPath样式)连接,也可以通过链接多个修饰符(jQuery样式)来连接。所以x[@z1='v1' and @z2='v2']实际上相当于x[@z1='v1'][@z2='v2'](也是x[z1='v1'][z2='v2'])。

类似jQuery的直接选择器:

  • x.oneclass相当于x[class='oneclass']

  • .oneclass相当于[class='oneclass']

  • x#oneid相当于x[id='oneid']

  • #oneid相当于[id='oneid']

  • x%oneref表示节点 - 不仅仅是元素 - 名称x 根据指定的实现匹配引用onerefDOMSelector.INodeReferenceChecker

  • %oneref表示节点 - 不仅仅是元素 - 根据指定的实现,具有与引用oneref匹配的任何名称DOMSelector.INodeReferenceChecker。请注意,这实际上相当于oneref因为可以使用引用而不是元素名称。

  • 直接选择器和属性选择器可以混合使用:a.external[@href^='https']

上面的DOM Selector表达式:

可以写成:

###多值类匹配

DOM选择器将类属性理解为多值,因此即使元素具有多个类值,也允许在此属性上应用选择器。

例如,div[class='two']将匹配<div class="one two three" />

###可选括号

片段包含属性的语法将每个片段选择转换为DOM选择,因此[...]不需要括号(尽管允许)。

所以下面没有括号,相当于上面看到的括号选择器:

总结一下,这个:

将寻找th:fragment="myfrag"片段签名。但是myfrag如果它们存在的话,它们也会寻找带有名称的标签(它们不是HTML格式的标签)。注意区别:

它实际上会寻找任何元素class="myfrag",而不关心th:fragment签名。

  1. 鉴于XHTML5只是使用application / xhtml + xml内容类型的XML格式HTML5,我们也可以说Thymeleaf支持XHTML5。

  2. 请注意,虽然此模板是有效的XHTML,但我们之前选择的模板模式为“XHTML”而不是“VALIDXHTML”。现在,我们可以关闭验证 - 但与此同时我们不希望我们的IDE抱怨太多。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ljk126wy/article/details/83789806

智能推荐

云计算的三种云部署模型和三种服务模式_基础设施即服务(iaas)从部署的情况来分,可以分为那三种?-程序员宅基地

文章浏览阅读1.1k次。云计算的三种主要服务模式——基础设施即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS这三者在存储和资源池方面可以为企业提供的服务存在明显差异,但它们也可以相互交互以形成一个全面的云计算模型。1、基础设施即服务(IaaS)基础设施即服务有时缩写为IaaS,包含云IT的基本构建块,通常提供对联网功能、计算机(虚拟或专用硬件)以及数据存储空间的访问。基础设施即服务提供最高等级的灵活性和对IT资源的管理控制,其机制与现今众多IT部门和开发人员所熟悉的现有。_基础设施即服务(iaas)从部署的情况来分,可以分为那三种?

正点原子的串口助手XCOM V2.0编码问题-程序员宅基地

文章浏览阅读8k次,点赞4次,收藏11次。该串口助手文本和16进制之间的转换是通过GBK2312来实现的,我还一直以为是Unicode方式如下以“博客园”三个汉字为例:_xcom v2.0

最新opencv-c++安装及配置教程(VS2019 C++ & opencv4.4.0)_c++ opencv配置-程序员宅基地

文章浏览阅读5.2w次,点赞104次,收藏458次。以前写过opencv python的安装教程,后来有一些同学开始私信我如何安装及配置opencv c++。本文是以最新的版本入手,一步步详解opencv c++ 的安装及配置过程。:第一步,下载解压opencv 算法库进入到以下链接:https://opencv.org/releases/ , 点击Windows,即可下载。其他系统可忽略本教程。笔者下载的是opencv 4.4.0 ,如果想尝试预发行版,可以选择opencv 4.5.0。下载之后双击,在抽取文件的目录中选择你想要存放的磁盘和文件即_c++ opencv配置

小蔺的米哈游数据分析师之路——MYSQL篇增删改查篇-程序员宅基地

文章浏览阅读609次。开三方了,开始逼签[牛泪],秋招基本结束,先签一个保底了,三方只能违约一次留给成都华子。内推成功率大大增加!马上面试了[赞][赞][赞][赞][赞][赞][赞]内推码:NTAM81Q投递地址如下!公司是华橙网络,岗位是嵌入式软件开发,学历是硕士985,地点是杭州,校招薪资是20*15,每天餐补17,晚上10点以后打车报销,公积金12%。投递岗位:结构工程师base:东莞投递时间:2023/8/11OPPO三面是hr面,整体感觉挺糟糕的,面试官全程很严肃,问你问题你回答她始终低头记录,对于你的回。

spark常见RDD练习_spark rdd有哪些经典练习案例-程序员宅基地

文章浏览阅读2.1k次。Spark 常用RDD练习其实还是推荐这个网站,写的很棒,点我一、Transformation1 map Applies a transformation function on each item of the RDD and returns the result as a new RDD. (返回一个新的RDD,该RDD有每一个输入元素经过func函数转换后组成)def map[..._spark rdd有哪些经典练习案例

(python)正则表达式提取字符串中的各种信息(持续更新)_正则表达式 获取所有内容(1)-程序员宅基地

文章浏览阅读275次,点赞4次,收藏6次。前两位数字代表省级行政区,中间两位数字代表市级行政区,后两位数字代表县级行政区、县市辖区或直辖市的行政区划。手机号码有自己特定的特征,比如1开头,手机号码长度 11位,一般来说,中国的邮政编码由6位数字组成。省级行政区 市级行政区 县级行政区。不同运营商的号段分布。

随便推点

Python利用fitz库提取pdf中的图片(针对多种类型pdf)_import fitz-程序员宅基地

文章浏览阅读2.3w次,点赞17次,收藏98次。目录一. 安装fitz二. pdf文件格式问题2.1 pdf文件存在多种格式2.2 分析问题三. 代码一. 安装fitz安装:需要安装fitz和PyMuPDF,否则会报如下错误:ModuleNotFoundError: No module named ‘frontend’pip install fitz PyMuPDF二. pdf文件格式问题2.1 pdf文件存在多种格式pdf文件的格式有好几种,用Adobe Acrobat比较正常的如下所示:这种类型的pdf文件可以比较正常地提取里面的图片_import fitz

for循环倒序java_for循环-程序员宅基地

文章浏览阅读5.4k次。除了while和do while循环,Java使用最广泛的是for循环。for循环的功能非常强大,它使用计数器实现循环。for循环会先初始化计数器,然后,在每次循环前检测循环条件,在每次循环后更新计数器。计数器变量通常命名为i。我们把1到100求和用for循环改写一下:// for----public class Main {public static void main(String[] arg..._java for 倒序

VS中未定义标识符cout,endl_未定义标识符 "endl-程序员宅基地

文章浏览阅读1w次,点赞6次,收藏10次。VS中未定义标识符vs2017中显示未定义标识符cout,endl。一种方法是:先看有没有包含输入输出流#include,以及命名空间using namespace std;第二种:如果上面都已包含,还是显示未定义标识符的话,检查一下,#include"pch.h"是否是在#include上面我就是犯了第二个错误..._未定义标识符 "endl

python 实现AES-CMAC算法验证_aescmac算法验证-程序员宅基地

文章浏览阅读797次,点赞7次,收藏14次。如果你也是看准了Python,想自学Python,在这里为大家准备了丰厚的免费。_aescmac算法验证

VUE实现网页中滚动鼠标时导航背景颜色透明度的改变_vue可以监听鼠标滚轮滑动,导航条透明度变化-程序员宅基地

文章浏览阅读2.9k次,点赞11次,收藏28次。1、HTML<div id="topNav" :style="topNavBg"> 这里是导航内容</div>2、JSexport default { data () { return { topNavBg: { backgroundColor: '' } } }, mounted () { window.addEventListener('scroll', this.handleScroll) // 监听_vue可以监听鼠标滚轮滑动,导航条透明度变化

【数据结构】单链表-练习_设 l 为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值-程序员宅基地

文章浏览阅读191次。2. 每当访问一个结点时,先递归输出它后面的结点,再输出该结点自身,这样链表就反向输出了。2. 将上述单链表中的元素按从头到尾的顺序,使用头插法新建一个链表 reverse;【题目】设 L 为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。3. 打印输出 reverse 中的元素。【思路】 1. 尾插法建立单链表 L;【答案】 1. 建立一个单链表;_设 l 为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值