Thymeleaf Tutorial 文檔 中文翻譯


原文檔地址:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html Thymeleaf官網地址:https://www.thymeleaf.org/

1 引入Thymeleaf

1.1 Thymeleaf是什么?

Thymeleaf是一個現代的服務器端Java模板引擎的web和獨立的環境,能夠處理HTML, XML, JavaScript, CSS,甚至純文本。

Thymeleaf的主要目標是提供一種優雅的和高度可維護的方式來創建模板。為了實現這一點,它構建在自然模板的概念上,以不影響模板作為設計原型使用的方式將其邏輯注入模板文件。這改進了設計的交流,並在設計和開發團隊之間架起了橋梁。

Thymeleaf的設計從一開始就考慮了Web標准——尤其是HTML5——允許你創建完全驗證模板,如果你需要的話。

1.2 Thymeleaf可以處理什么樣的模板?

開箱即用,Thymeleaf允許您處理六種模板,其中每一種被稱為模板模式:

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

有兩種標記模板模式(HTML和XML)、三種文本模板模式(文本、JAVASCRIPT和CSS)和一種無操作模板模式(RAW)。

HTML模板模式將允許任何類型的HTML輸入,包括HTML5、HTML 4和XHTML。將不執行任何驗證或格式良好性檢查,並且將在輸出中盡可能尊重模板代碼/結構。

XML模板模式將允許XML輸入。在這種情況下,代碼應該是格式良好的—沒有未關閉的標記,沒有未引用的屬性,等等—如果發現格式良好性違規,解析器將拋出異常。注意,將不執行任何驗證(針對DTD或XML模式)。

文本模板模式將允許對非標記性質的模板使用特殊語法。此類模板的示例可能是文本電子郵件或模板化文檔。注意,HTML或XML模板也可以作為文本處理,在這種情況下,它們不會被解析為標記,而每個標記、DOCTYPE、注釋等都將被視為純文本。

JAVASCRIPT模板模式將允許在Thymeleaf應用程序中處理JAVASCRIPT文件。這意味着能夠像在HTML文件中一樣在JavaScript文件中使用模型數據,但是要使用特定於JavaScript的集成,比如專門的轉義或自然腳本。JAVASCRIPT模板模式被認為是文本模式,因此使用與文本模板模式相同的特殊語法。

CSS模板模式將允許處理Thymeleaf應用程序中涉及的CSS文件。與JAVASCRIPT模式類似,CSS模板模式也是一種文本模式,並使用來自文本模板模式的特殊處理語法。

原始模板模式根本不會處理模板。它用於將未觸及的資源(文件、URL響應等)插入正在處理的模板中。例如,可以將HTML格式的外部非控制資源包含到應用程序模板中,但要確保這些資源可能包含的任何Thymeleaf代碼都不會被執行。

1.3 方言:標准方言

Thymeleaf是一個非常可擴展的模板引擎(事實上它可以被稱為模板引擎框架),它允許你定義和自定義的方式,你的模板將被處理到一個精細的細節級別。

將一些邏輯應用到標記工件(標記、一些文本、注釋,如果模板不是標記,則僅僅是占位符)的對象稱為處理程序,這些處理程序的集合—加上一些額外的工件—通常是方言的組成部分。Thymeleaf的核心庫提供了一種稱為標准方言的方言,這對大多數用戶來說應該足夠了。

注意,方言實際上可能沒有處理器,並且完全由其他類型的工件組成,但是處理器絕對是最常見的用例。

本教程介紹標准方言。在下面的頁面中,您將了解到的每個屬性和語法特性都是由這種方言定義的,即使沒有明確提到。

當然,如果希望在利用庫的高級特性的同時定義自己的處理邏輯,用戶可以創建自己的方言(甚至擴展標准的方言)。Thymeleaf也可以配置成同時使用幾種方言。

官方thymeleaf-spring3和thymeleaf-spring4集成包都定義一個方言稱為“SpringStandard方言”,大部分是一樣的標准方言,但小適應更好地利用Spring框架的一些特性(例如,通過使用Spring表達式語言或圖像代替OGNL展示出)。因此,如果您是Spring MVC用戶,您就不會浪費時間,因為您在這里學到的幾乎所有東西都將在您的Spring應用程序中使用。

標准方言的大多數處理器都是屬性處理器。這允許瀏覽器在處理之前正確顯示HTML模板文件,因為它們將直接忽略額外的屬性。例如,一個使用標記庫的JSP可能包含一段不能被瀏覽器直接顯示的代碼,比如:

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

Thymeleaf標准方言將允許我們實現相同的功能與:

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

這不僅可以被瀏覽器正確顯示,但這也讓我們(可選)指定一個值屬性(“James Carrot”,在這種情況下),將顯示靜態原型時在瀏覽器中打開, 在處理模板期間,將取代 ${user.name}

這有助於設計人員和開發人員處理相同的模板文件,並減少將靜態原型轉換為工作模板文件所需的工作。這樣做的能力稱為自然模板。

2 一個很棒的虛擬雜貨店設計

本文所示示例的源代碼,以及本指南的后續章節,可以在 Good Thymes Virtual Grocery GitHub repository.

2.1 一個雜貨店的網站

為了更好地解釋使用Thymeleaf處理模板所涉及的概念,本教程將使用一個演示應用程序,您可以從項目的網站下載。

這個應用程序是一個虛擬雜貨店的web站點,它將為我們提供許多場景來展示Thymeleaf的許多特性。

首先,我們的應用程序需要一組簡單的模型實體:通過創建訂單向客戶銷售的產品。我們還將管理這些產品的評論: 示例應用程序模型

我們的應用程序還將有一個非常簡單的服務層,由包含以下方法的服務對象組成:

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 {

        // This prevents triggering engine executions for resource URLs
        if (request.getRequestURI().startsWith("/css") ||
                request.getRequestURI().startsWith("/images") ||
                request.getRequestURI().startsWith("/favicon")) {
            return false;
        }

        
        /*
         * 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 = this.application.resolveControllerForRequest(request);
        if (controller == null) {
            return false;
        }

        /*
         * Obtain the TemplateEngine instance.
         */
        ITemplateEngine templateEngine = this.application.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) {
        try {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final IOException ignored) {
            // Just ignore this
        }
        throw new ServletException(e);
    }
    
}

這是我們的IGTVGController接口:

public interface IGTVGController {

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

我們現在要做的就是創建IGTVGController接口的實現,從服務中檢索數據,並使用ITemplateEngine對象處理模板。

最后,它看起來是這樣的: 示例應用程序主頁

但首先讓我們看看模板引擎是如何初始化的。

2.2 創建和配置模板引擎

我們的過濾器中的process(…)方法包含這一行:

ITemplateEngine templateEngine = this.application.getTemplateEngine();

這意味着GTVGApplication類負責創建和配置Thymeleaf應用程序中最重要的對象之一:TemplateEngine實例(ITemplateEngine接口的實現)。

我們的org.thymeleaf.TemplateEngine對象初始化如下:

public class GTVGApplication {
  
    
    ...
    private final TemplateEngine templateEngine;
    ...
    
    
    public GTVGApplication(final ServletContext servletContext) {

        super();

        ServletContextTemplateResolver templateResolver = 
                new ServletContextTemplateResolver(servletContext);
        
        // HTML is the default mode, but we set it anyway for better understanding of code
        templateResolver.setTemplateMode(TemplateMode.HTML);
        // 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
        templateResolver.setCacheTTLMs(Long.valueOf(3600000L));
        
        // Cache is set to true by default. Set to false if you want templates to
        // be automatically updated when modified.
        templateResolver.setCacheable(true);
        
        this.templateEngine = new TemplateEngine();
        this.templateEngine.setTemplateResolver(templateResolver);
        
        ...

    }

}

配置TemplateEngine對象的方法有很多,但是現在,這幾行代碼就足以告訴我們所需的步驟。

模板解析器

讓我們從模板解析器開始:

ServletContextTemplateResolver templateResolver = 
        new ServletContextTemplateResolver(servletContext);

模板解析器是實現來自調用的Thymeleaf API的接口的對象

org.thymeleaf.templateresolver.ITemplateResolver

public interface ITemplateResolver {

    ...
  
    /*
     * Templates are resolved by their name (or content) and also (optionally) their 
     * owner template in case we are trying to resolve a fragment for another template.
     * Will return null if template cannot be handled by this template resolver.
     */
    public TemplateResolution resolveTemplate(
            final IEngineConfiguration configuration,
            final String ownerTemplate, final String template,
            final Map<String, Object> templateResolutionAttributes);
}

這些對象負責決定如何訪問模板,在這個GTVG應用程序中,org.thymeleaf.templateresolver.ServletContextTemplateResolver 意味着我們要從Servlet上下文中獲取模板文件作為資源:應用程序范圍的javax.servlet.ServletContext對象存在於每個Java web應用程序中,它從web應用程序根目錄解析資源。

但這還不是我們對模板解析器所能說的全部,因為我們可以在它上面設置一些配置參數。

一、模板模式:

templateResolver.setTemplateMode(TemplateMode.HTML);

HTML是ServletContextTemplateResolver的默認模板模式,但是最好還是建立它,以便我們的代碼清楚地記錄正在發生的事情。

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

前綴和后綴修改我們將傳遞給引擎的模板名稱,以獲得要使用的實際資源名稱。

使用此配置,模板名稱“product/list”將對應於:

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

可選地,在模板解析器中通過cacheTTLMs屬性配置一個解析后的模板在緩存中的生存時間:

templateResolver.setCacheTTLMs(3600000L);

如果達到了最大緩存大小,並且它是當前緩存中最老的條目,那么在到達TTL之前仍然可以從緩存中刪除模板。

用戶可以通過實現ICacheManager接口或修改StandardCacheManager對象來管理默認緩存來定義緩存行為和大小。

關於模板解析器還有很多要學習的,但是現在讓我們來看看模板引擎對象的創建。

模板引擎

模板引擎對象是org.thymeleaf.ITemplateEngine接口的實現。其中一個實現是由Thymeleaf核心:org.thymeleaf.ITemplateEngine,我們創建了一個實例,它在這里:

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

很簡單,不是嗎?我們只需要創建一個實例並將模板解析器設置為它。

模板解析器是TemplateEngine惟一需要的參數,盡管后面還會介紹許多其他參數(消息解析器、緩存大小等)。現在,這就是我們所需要的。

我們的模板引擎現在已經准備好了,我們可以開始使用Thymeleaf創建我們的頁面。

3 使用文本

3.1 多語言的歡迎

我們的第一個任務是為我們的雜貨站點創建一個主頁。

這個頁面的第一個版本將非常簡單:只有一個標題和一條歡迎信息。

這是我們的/WEB-INF/templates/home.html文件:

<!DOCTYPE html>

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

  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all" 
          href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
  </head>

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

</html>

首先你會注意到這個文件是HTML5,它可以被任何瀏覽器正確顯示,因為它不包含任何非html標簽(瀏覽器會忽略所有他們不理解的屬性,比如th:text)。

但是你可能也注意到這個模板並不是一個真正有效的HTML5文檔,因為我們在th:*表單中使用的這些非標准屬性是HTML5規范所不允許的。事實上,我們甚至添加了一個xmlns:th屬性到我們的<html>標簽,一些絕對非html5的東西:

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

它在模板處理中沒有任何影響,但是作為一個incantation,這就避免了我們的IDE抱怨th:*這些屬性缺少命名空間定義。

那么,如果我們想讓這個模板html5有效呢?簡單:切換到Thymeleaf的數據屬性語法,使用數據前綴的屬性名稱和連字符(-)分隔符,而不是分號(:):

<!DOCTYPE html>

<html>

  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all" 
          href="../../css/gtvg.css" data-th-href="@{/css/gtvg.css}" />
  </head>

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

</html>

自定義data-前綴屬性是HTML5規范所允許的,因此,有了上述代碼,我們的模板將是一個有效的HTML5文檔。

這兩種表示法完全等價並且可以互換,但是為了代碼示例的簡單性和緊湊性,本教程將使用名稱空間表示法(th:*)。此外,th:*符號更通用,在每個Thymeleaf模板模式(XML,文本…),而數據符號只允許在HTML模式。

使用th: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將使用基於文件的實現,但是例如,如果我們想從數據庫中獲取消息,則可以創建自己的實現。

但是,我們尚未在初始化期間為模板引擎指定消息解析器,這意味着我們的應用程序使用的是由實現的 標准消息解析器thymeleaf.messageresolver.StandardMessageResolver

標准消息解析器希望/WEB-INF/templates/home.html在與模板相同名稱和名稱的文件夾中的屬性文件中查找消息,例如:

  • /WEB-INF/templates/home_en.properties 用於英文文本。
  • /WEB-INF/templates/home_es.properties 西班牙語文本。
  • /WEB-INF/templates/home_pt_BR.properties 用於葡萄牙語(巴西)語言文本。
  • /WEB-INF/templates/home.properties 用於默認文本(如果語言環境不匹配)。

讓我們看一下我們的home_es.properties文件:

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

這就是使Thymeleaf加工成為模板所需要的。然后創建我們的Home控制器。

語境(上下文)

為了處理我們的模板,我們將創建一個HomeController實現IGTVGController之前看到的接口的類:

public class HomeController implements IGTVGController {

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

}

我們首先看到的是上下文的創建。Thymeleaf上下文是實現org.thymeleaf.context.IContext接口的對象。上下文應在變量映射中包含執行模板引擎所需的所有數據,並且還應引用必須用於外部化消息的語言環境。

public interface IContext {

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

該接口有一個專門的擴展,org.thymeleaf.context.IWebContext可以在基於ServletAPI的Web應用程序(如SpringMVC)中使用。

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

Thymeleaf核心庫提供了以下每個接口的實現:

  • org.thymeleaf.context.Context 實施 IContext
  • org.thymeleaf.context.WebContext 實施 IWebContext

正如您在控制器代碼中看到的那樣,這WebContext是我們使用的代碼。實際上,我們必須這樣做,因為使用a ServletContextTemplateResolver要求我們使用上下文實現IWebContext

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

這四個構造函數參數中只有三個是必需的,因為如果未指定默認語言環境,則將使用系統的默認語言環境(盡管您絕對不應在實際應用程序中讓這種情況發生)。

我們可以使用一些專門的表達式從WebContext模板中的獲取請求參數以及請求,會話和應用程序屬性。例如:

  • ${x}將返回x存儲在Thymeleaf上下文中或作為請求屬性的變量。
  • ${param.x}將返回一個名為(可能是多值)的請求參數x。
  • ${session.x}將返回名為的會話屬性x。
  • ${application.x}將返回名為的Servlet上下文屬性x。

執行模板引擎

准備好上下文對象之后,現在我們可以告訴模板引擎使用上下文處理模板(按其名稱),並將其傳遞給響應編寫器,以便可以將響應寫入其中:

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

讓我們使用西班牙語語言環境查看結果:

<!DOCTYPE html>

<html>

  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
    <link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" />
  </head>

  <body>
  
    <p>¡Bienvenido a nuestra tienda de comestibles!</p>

  </body>

</html>

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尊重我們的HTML標記而不是對其進行轉義,我們將不得不使用不同的屬性:(th:utext用於“未轉義的文本”):

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

這將輸出我們的消息,就像我們想要的那樣:

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

使用和顯示變量

現在,讓我們向主頁添加更多內容。例如,我們可能希望在歡迎消息下方顯示日期,如下所示:

Welcome to our fantastic grocery store!

Today is: 12 july 2010

首先,我們將必須修改控制器,以便我們將該日期添加為上下文變量:

public void process(
            final HttpServletRequest request, final HttpServletResponse response,
            final ServletContext servletContext, final ITemplateEngine templateEngine)
            throws Exception {
        
    SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
    Calendar cal = Calendar.getInstance();
        
    WebContext ctx = 
            new WebContext(request, response, servletContext, request.getLocale());
    ctx.setVariable("today", dateFormat.format(cal.getTime()));
        
    templateEngine.process("home", ctx, response.getWriter());
        
}

我們在上下文中添加了一個String類型的變量today,現在我們可以在模板中顯示它:

<body>

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

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

如您所見,我們仍在使用th:text屬性來工作(這是正確的,因為我們要替換標簽的主體),但是這次的語法略有不同#{...},我們使用的不是表達式值${...}。這是一個變量表達式,它包含一種稱為OGNL(對象圖導航語言)的語言的表達式,該表達式將在我們之前提到的上下文變量映射上執行。

${today}表達式僅表示“獲取今天調用的變量”,但是這些表達式可能更復雜(例如${user.name}“獲取稱為用戶的變量並調用其getName()方法”)。

屬性值有很多可能性:消息,變量表達式等等。下一章將向我們展示所有這些可能性。

4 標准表達式語法

我們將在雜貨店虛擬商店的開發中稍作休息,以了解Thymeleaf標准方言最重要的部分之一:Thymeleaf Standard Expression語法。

我們已經看到了用這種語法表示的兩種有效屬性值:消息和變量表達式:

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

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

但是有更多類型的表達式,還有更多有趣的細節來了解我們已經知道的表達式。首先,讓我們看一下標准表達式功能的快速摘要:

  • 簡單表達式:
    • 變量表達式: $
    • 選擇變量表達式: *
    • 消息表達: #{...}
    • 鏈接URL表達式: @
    • 片段表達式: ~
  • 文字
    • 文本文字:'one text','Another one!',...
    • 號碼文字:0,34,3.0,12.3,...
    • 布爾文字:true,false
    • 空文字: null
    • 文字標記:one,sometext,main,...
  • 文字操作:
    • 字符串串聯: +
    • 文字替換: |The name is $|
  • 算術運算:
    • 二元運算符:+,-,*,/,%
    • 減號(一元運算符): -
  • 布爾運算:
    • 二元運算符:and,or
    • 布爾否定(一元運算符): !,not
  • 比較和相等:
    • 比較:>,<,>=,<=(gt,lt,ge,le)
    • 等號運算符:==,!=(eq,ne)
  • 條件運算符:
    • 如果-則: (if) ? (then)
    • 如果-則-否則: (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文檔中為java.text.*包中類指定的格式格式化數字和日期。

為了給我們的參數指定一個值,並給定一個HTTP會話屬性user,我們可以擁有:

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

注意th:utext這里的使用意味着格式化的消息將不會被轉義。本示例假定user.name已被轉義。

可以指定幾個參數,以逗號分隔。

消息密鑰本身可以來自變量:

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

4.2 變量

我們已經提到過,${...}表達式實際上是在上下文中包含的變量映射上執行的OGNL(對象圖導航語言)表達式。

有關OGNL語法和功能的詳細信息,請閱讀 《OGNL語言指南》 在啟用Spring MVC的應用程序中,OGNL將替換為SpringEL,但是其語法與OGNL的語法非常相似(實際上,對於大多數常見情況而言,它們是完全相同的)。

根據OGNL的語法,我們知道以下表達式:

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

…實際上等於:

ctx.getVariable("today");

但是OGNL允許我們創建功能更強大的表達式,這就是這種方式:

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

…通過執行以下操作獲取用戶名:

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

但是,getter方法導航只是OGNL的功能之一。讓我們看看更多:

/*
 * 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:上下文語言環境。
  • #request:(僅在Web上下文中)HttpServletRequest對象。
  • #response:(僅在Web上下文中)HttpServletResponse對象。
  • #session:(僅在Web上下文中)HttpSession對象。
  • #servletContext:(僅在Web上下文中)ServletContext對象。

因此,我們可以這樣做:

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

您可以在附錄A中閱讀這些對象的完整參考。

表達工具對象

除了這些基本對象之外,Thymeleaf將為我們提供一組實用程序對象,這些對象將幫助我們在表達式中執行常見任務。

  • #execInfo:有關正在處理的模板的信息。
  • #messages:用於獲取變量表達式內的外部化消息的方法,與使用#{…}語法獲得消息的方法相同。
  • #uris:用於轉義部分URL / URI的方法
  • #conversions:用於執行已配置的轉換服務(如果有)的方法。
  • #datesjava.util.Date對象的方法:格式化,組件提取等。
  • #calendars:類似於#dates,但用於java.util.Calendar對象。
  • #numbers:格式化數字對象的方法。
  • #strings:String對象的方法:包含,startsWith,前置/追加等。
  • #objects:一般對象的方法。
  • #bools:布爾值評估的方法。
  • #arrays:數組方法。
  • #lists:列表方法。
  • #sets:套方法。
  • #maps:地圖方法。
  • #aggregates:用於在數組或集合上創建聚合的方法。
  • #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, response, 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是Web應用程序模板中的一等公民,而Thymeleaf Standard Dialect對它們有一種特殊的語法,該@語法為:@{...}

URL有不同類型:

  • 絕對網址: http://www.thymeleaf.org
  • 相對URL,可以是:
    • 相對頁面: user/login.html
    • 上下文相關:(/itemdetails?id=3服務器中的上下文名稱將自動添加)
    • 相對於服務器:(~/billing/processInvoice允許在同一服務器中的另一個上下文(=應用程序)中調用URL。
    • 相對協議網址: //code.jquery.com/jquery-2.0.3.min.js

這些表達式的實際處理以及它們到將要輸出的URL的轉換是通過org.thymeleaf.linkbuilder.ILinkBuilder注冊到ITemplateEngine所使用的對象的接口實現來完成的。

默認情況下,該類的該接口的單個​​實現被注冊org.thymeleaf.linkbuilder.StandardLinkBuilder,這對於脫機(非Web)和基於Servlet API的Web場景都足夠。其他場景(例如與非ServletAPI Web框架集成)可能需要鏈接構建器接口的特定實現。

讓我們使用這種新語法。符合th:href屬性:

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

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

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

這里要注意一些事情:

  • th:href是修飾符屬性:處理后,它將計算要使用的鏈接URL,並將該值設置href為<a>標記的屬性。
  • 我們被允許對URL參數使用表達式(如您在中所見orderId=${o.id})。所需的URL參數編碼操作也將自動執行。
  • 如果需要幾個參數,這些參數將以逗號分隔:@{/order/process(execId=${execId},execType='FAST')}
  • URL路徑中也允許使用變量模板: @{/order/{orderId}/details(orderId=${orderId})}
  • 以/(例如:)開頭的相對URL /order/details將自動以應用程序上下文名稱作為前綴。
  • 如果未啟用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 碎片

片段表達式是表示標記片段並將其在模板中移動的簡便方法。這使我們能夠復制它們,並將它們作為參數傳遞給其他模板,依此類推。

最常見的用途是使用th:insertth:replace(在后面的部分中有更多關於)的片段插入:

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

但是它們可以在任何地方使用,就像其他任何變量一樣:

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

在本教程的后面,將有一個完整的章節專門介紹“模板布局”,包括對片段表達式的更深入的說明。

4.6 文本

文字文本

文本文字只是在單引號之間指定的字符串。它們可以包含任何字符,但是您應該使用來對其中的任何單引號進行轉義'。

<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 y是寫在花括號外的,因此Thymeleaf負責處理。如果將其寫在花括號內,則OGNL / SpringEL引擎應負責:

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

空文本

該null文本也可用於:

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

文字代幣

實際上,數字,布爾值和null文字是文字標記的一種特殊情況。

這些標記允許在標准表達式中進行一些簡化。它們的工作原理與文本文字('...')完全相同,但是它們僅允許使用字母(A-Z和a-z),數字(0-9),方括號([和]),點(.),連字符(-)和下划線(_)。因此,沒有空格,沒有逗號等。

好的部分?令牌不需要任何引號引起來。因此,我們可以這樣做:

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

代替:

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

4.7 附加文本

文本,無論它們是文字還是評估變量或消息表達式的結果,都可以使用+運算符輕松附加:

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

4.8 文本替代

文字替換可以輕松格式化包含變量值的字符串,而無需在文字后面附加 '...' + '...'

這些替換項必須用豎線(|)包圍,例如:

<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.9 算術運算

一些算術運算也可用:+,-,*,/和%

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

請注意,這些運算符也可以在OGNL變量表達式內部應用(在這種情況下,將由OGNL代替Thymeleaf標准表達式引擎執行):

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

請注意,其中一些運算符存在文本別名:div(/)mod(%)

4.10 比較器和平等

在表達式中的值可以與進行比較><>=和<=符號,以及==!=運營商可以被用來檢查是否相等(或缺乏)。請注意,XML規定,不得在屬性值中使用<和>符號,因此應將其替換為&lt;&gt;

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

一個更簡單的替代方法可能是使用以下某些運算符存在的文本別名:gt(>)lt(<)ge(>=)le(<=)not(!)。還有eq(==)neq/ ne(!=)

4.11 條件表達式

條件表達式旨在僅根據兩個條件的求值結果來求值(它本身就是另一個表達式)。 讓我們來看一個例子片段(引入另一個屬性修改器,th:class):

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

條件表達式的所有三個部分(condition,then和else)本身的表達式,這意味着它們可以是變量(${...},*{...})消息(#{...})網址(@{...})文字('...')

也可以使用括號嵌套條件表達式:

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

其他表達式也可以省略,在這種情況下,如果條件為false,則返回null值:

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

4.12 默認表達式(Elvis運算符)

一個默認的表情是一種特殊的條件值的沒有那么一部分。它等效於某些語言(如Groovy)中出現的Elvis運算符,可讓您指定兩個表達式:如果第一個表達式的計算結果不為null,則使用第一個表達式;如果第二個表達式使用,則使用第二個表達式。

讓我們在我們的用戶個人資料頁面中看到它的實際效果:

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

如您所見,運算符為?:,並且僅當求*{age}值結果為null時,才在此處使用它來指定名稱的默認值(在這種情況下為文字值)。因此,這等效於:

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

與條件值一樣,它們可以在括號之間包含嵌套表達式:

<p>
  Name: 
  <span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>

4.13 無操作令牌

No-Operation令牌由下划線符號(_)表示。

該標記背后的想法是指定表達式的期望結果什么也不做,即完全就像可處理屬性(例如th:text)根本不存在一樣。

除其他可能性外,這還使開發人員可以將原型文本用作默認值。例如,代替:

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

…我們可以直接將“未經用戶身份驗證”用作原型文本,從設計的角度來看,這使得代碼既簡潔又通用:

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

4.14 數據轉換/格式化

Thymeleaf 為變量()和選擇()表達式定義了雙括號語法,使我們能夠通過配置的轉換服務來應用數據轉換。${...} *{...}

它基本上是這樣的:

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

注意到那里有雙括號了嗎?:${{...}}。指示Thymeleaf將user.lastAccessDate表達式的結果傳遞給轉換服務,並要求它執行格式操作(將轉換為String),然后再寫入結果。

假設user.lastAccessDate類型為java.util.Calendar,如果已注冊轉換服務(的實現IStandardConversionService)並且包含的​​有效轉換Calendar -> String,則將應用。

IStandardConversionService(StandardConversionService該類)的默認實現僅對.toString()轉換為的任何對象執行String。有關如何注冊自定義轉換服務實現的更多信息,請參見 更多關於配置部分。

官方的thymeleaf-spring3和thymeleaf-spring4集成軟件包將Thymeleaf的轉換服務機制與Spring自己的轉換服務基礎結構透明地集成在一起,以便在Spring配置中聲明的轉換服務和格式化程序將自動提供給${{...}}*{{...}}表達式。

4.15 預處理

除了用於表達式處理的所有這些功能之外,Thymeleaf還具有預處理表達式的功能。

預處理是在普通表達式之前執行的表達式的執行,該表達式允許修改最終將要執行的表達式。

預處理表達式與普通表達式完全一樣,但是出現了一個雙下划線符號(如__${expression}__)。

假設我們有一個i18n Messages_fr.properties條目,其中包含一個OGNL表達式,該表達式調用特定於語言的靜態方法,例如:

article.text=@myapp.translator.Translator@translateToFrench({0})

Messages_es.properties equivalent

article.text=@myapp.translator.Translator@translateToSpanish({0})

我們可以創建一個標記片段,該片段根據語言環境評估一個表達式或另一個表達式。為此,我們將首先選擇表達式(通過預處理),然后讓Thymeleaf執行它:

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

請注意,法語語言環境的預處理步驟將創建以下等效項:

<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>

__可以使用來在屬性中對預處理字符串進行轉義__。

5 設置屬性值

本章將說明在標記中設置(或修改)屬性值的方法。

5.1 設置任何屬性的值

假設我們的網站發布了新聞通訊,我們希望用戶能夠訂閱該新聞通訊,所以我們創建/WEB-INF/templates/subscribe.html具有以下形式的模板:

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

與Thymeleaf一樣,此模板的開始更像是靜態原型,而不是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!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

這個概念非常簡單:th:attr只需采用一個為屬性分配值的表達式。創建了相應的控制器和消息文件后,處理該文件的結果將是:

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

除了新的屬性值,您還可以看到應用程序上下文名稱已自動前綴為中的URL庫/gtvg/subscribe,如上一章所述。

但是,如果我們想一次設置多個屬性怎么辦?XML規則不允許您在標記中設置屬性兩次,因此th:attr將采用逗號分隔的分配列表,例如:

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

5.2 將值設置為特定屬性

到現在為止,您可能會認為類似:

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

…是相當難看的標記。在屬性值內指定分配可能非常實用,但如果必須始終這樣做,則不是創建模板的最優雅的方法。

Thymeleaf同意您的觀點,這就是為什么th:attr模板中很少使用它的原因。通常,您將使用其他th:*屬性,這些屬性的任務是設置特定的標記屬性(而不僅僅是像的任何屬性th:attr)。

例如,要設置value屬性,請使用th:value

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

看起來好多了!讓我們嘗試action對form標記中的屬性執行相同的操作:

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

您還記得th:href我們home.html以前放過的那些嗎?它們是完全一樣的屬性:

<li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>

有很多這樣的屬性,每個屬性都針對特定的HTML5屬性:


th:abbr	th:accept	th:accept-charset
th:accesskey	th:action	th:align
th:alt	th:archive	th:audio
th:autocomplete	th:axis	th:background
th:bgcolor	th:border	th:cellpadding
th:cellspacing	th:challenge	th:charset
th:cite	th:class	th:classid
th:codebase	th:codetype	th:cols
th:colspan	th:compact	th:content
th:contenteditable	th:contextmenu	th:data
th:datetime	th:dir	th:draggable
th:dropzone	th:enctype	th:for
th:form	th:formaction	th:formenctype
th:formmethod	th:formtarget	th:fragment
th:frame	th:frameborder	th:headers
th:height	th:high	th:href
th:hreflang	th:hspace	th:http-equiv
th:icon	th:id	th:inline
th:keytype	th:kind	th:label
th:lang	th:list	th:longdesc
th:low	th:manifest	th:marginheight
th:marginwidth	th:max	th:maxlength
th:media	th:method	th:min
th:name	th:onabort	th:onafterprint
th:onbeforeprint	th:onbeforeunload	th:onblur
th:oncanplay	th:oncanplaythrough	th:onchange
th:onclick	th:oncontextmenu	th:ondblclick
th:ondrag	th:ondragend	th:ondragenter
th:ondragleave	th:ondragover	th:ondragstart
th:ondrop	th:ondurationchange	th:onemptied
th:onended	th:onerror	th:onfocus
th:onformchange	th:onforminput	th:onhashchange
th:oninput	th:oninvalid	th:onkeydown
th:onkeypress	th:onkeyup	th:onload
th:onloadeddata	th:onloadedmetadata	th:onloadstart
th:onmessage	th:onmousedown	th:onmousemove
th:onmouseout	th:onmouseover	th:onmouseup
th:onmousewheel	th:onoffline	th:ononline
th:onpause	th:onplay	th:onplaying
th:onpopstate	th:onprogress	th:onratechange
th:onreadystatechange	th:onredo	th:onreset
th:onresize	th:onscroll	th:onseeked
th:onseeking	th:onselect	th:onshow
th:onstalled	th:onstorage	th:onsubmit
th:onsuspend	th:ontimeupdate	th:onundo
th:onunload	th:onvolumechange	th:onwaiting
th:optimum	th:pattern	th:placeholder
th:poster	th:preload	th:radiogroup
th:rel	th:rev	th:rows
th:rowspan	th:rules	th:sandbox
th:scheme	th:scope	th:scrolling
th:size	th:sizes	th:span
th:spellcheck	th:src	th:srclang
th:standby	th:start	th:step
th:style	th:summary	th:tabindex
th:target	th:title	th:type
th:usemap	th:value	th:valuetype
th:vspace	th:width	th:wrap
th:xmlbase	th:xmllang	th:xmlspace

5.3 一次設置多個值

有兩個叫比較特殊的屬性th:alt-titleth:lang-xmllang可用於同時設置兩個屬性相同的值。特別:

th:alt-title將設置alttitleth:lang-xmllang將設置langxml:lang。 對於我們的GTVG主頁,這將使我們可以替換為:

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

…或與此等效的:

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

…有了這個:

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

5.4 追加和前置

Thymeleaf還提供th:attrappendth:attrprepend屬性,將對它們求值的結果附加(后綴)或前綴(前綴)到現有屬性值。

例如,您可能希望將要添加(未設置,只是添加)的CSS類的名稱存儲在上下文變量中,因為要使用的特定CSS類將取決於用戶所做的操作之前:

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

如果將cssStyle變量設置為來處理此模板"warning",則會得到:

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

標准方言中還有兩個特定的附加屬性:th:classappendth:styleappend屬性,用於在元素上添加CSS類或樣式片段而不覆蓋現有屬性:

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

(不必擔心該th:each屬性。它是一個迭代屬性,我們將在后面討論。)

5.5 固定值布爾屬性

HTML具有布爾屬性的概念,即沒有值的屬性,並且首字母縮寫為1表示值是“ true”。在XHTML中,這些屬性僅取1值,這就是它本身。

例如checked

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

標准方言包括一些屬性,這些屬性使您可以通過評估條件來設置這些屬性,因此,如果評估為true,則該屬性將設置為其固定值;如果評估為false,則將不設置該屬性:

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

標准方言中存在以下固定值布爾屬性:


th: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 設置任何屬性的值(默認屬性處理器)

Thymeleaf提供了一個默認的屬性處理器,即使我們在標准方言中沒有為其定義任何特定的處理器,它也允許我們設置任何屬性的值th:*

所以像這樣:

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

將導致:

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

5.7 支持HTML5友好的屬性和元素名稱

也可以使用完全不同的語法,以更友好的HTML5方式將處理器應用於模板。

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

data-{prefix}-{name}語法是標准的方式在HTML5寫自定義屬性,而無需開發人員使用任何命名空間的名稱,如th:*。Thymeleaf使此語法自動適用於所有方言(不僅限於標准方言)。

還有一種用於指定自定義標記的語法:{prefix}-{name},它遵循W3C自定義元素規范(是更大的W3C Web組件規范的一部分)。例如,這可以用於th:block元素(或th-block),這將在后面的部分中進行說明。

重要提示:此語法是命名空間語法的補充th:*,它不會替代它。完全沒有打算將來棄用命名空間的語法。

6 迭代

到目前為止,我們已經創建了一個主頁,一個用戶個人資料頁面以及一個允許用戶訂閱我們的新聞通訊的頁面……但是我們的產品呢?為此,我們將需要一種方法來遍歷集合中的項目以構建我們的產品頁面。

6.1 迭代基礎

為了在我們的/WEB-INF/templates/product/list.html頁面中顯示產品,我們將使用一個表格。我們的每種產品都將顯示在一行(一個<tr>元素)中,因此對於我們的模板,我們需要創建一個模板行 -一個示例行,以舉例說明我們希望每種產品的顯示方式,然后指示Thymeleaf重復該行,每個產品一次。

標准方言為我們提供了一個確切的屬性:th:each

每個使用

對於我們的產品列表頁面,我們將需要一個控制器方法,該方法將從服務層檢索產品列表並將其添加到模板上下文中:

public void process(
        final HttpServletRequest request, final HttpServletResponse response,
        final ServletContext servletContext, final ITemplateEngine templateEngine)
        throws Exception {
    
    ProductService productService = new ProductService();
    List<Product> allProducts = productService.findAll(); 
    
    WebContext ctx = new WebContext(request, response, servletContext, request.getLocale());
    ctx.setVariable("prods", allProducts);
    
    templateEngine.process("product/list", ctx, response.getWriter());
    
}

然后,我們將th:each在模板中使用它來遍歷產品列表:

<!DOCTYPE html>

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

  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all" 
          href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
  </head>

  <body>

    <h1>Product list</h1>
  
    <table>
      <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
      </tr>
      <tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
      </tr>
    </table>
  
    <p>
      <a href="../home.html" th:href="@{/}">Return to home</a>
    </p>

  </body>

</html>

prod : ${prods}你看到上述手段的屬性值“為在評估的結果的每個元素${prods},重復模板的該片段中,使用一個稱為PROD可變當前元素”。讓我們給每一個看到的事物命名:

我們將調用${prods}的迭代式或迭代變量。 我們將調用prod的迭代變量或者干脆ITER變量。

請注意,proditer變量的作用域為<tr>元素,這意味着它可用於內部標記(如)<td>

迭代值

java.util.List班是不是可以在Thymeleaf用於迭代onlyvalue。有相當完整的一組對象,這些對象被屬性視為可迭代的th:each

  • 任何實現的對象 java.util.Iterable
  • 任何實現的對象java.util.Enumeration。
  • 實現的任何對象java.util.Iterator,其值將由迭代器返回,而無需在內存中緩存所有值。
  • 任何實現的對象java.util.Map。迭代地圖時,迭代變量將屬於class java.util.Map.Entry。
  • 任何數組。
  • 任何其他對象都將被視為包含該對象本身的單值列表。

6.2 保持迭代狀態

使用時th:each,Thymeleaf提供了一種用於跟蹤迭代狀態的有用機制:status變量。

狀態變量在th:each屬性中定義,並且包含以下數據:

  • 當前的迭代索引,從0開始。這是index屬性。
  • 從1開始的當前迭代索引。這是count屬性。
  • 迭代變量中元素的總數。這是size財產。
  • 每次迭代的iter變量。這是current財產。
  • 當前迭代是偶數還是奇數。這些是even/odd布爾屬性。
  • 當前迭代是否是第一個。這是first布爾屬性。
  • 當前迭代是否為最后一個。這是last布爾屬性。

讓我們看看如何在上一個示例中使用它:

<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
  </tr>
  <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
  </tr>
</table>

iterStat在th:each屬性中定義狀態變量(在此示例中),方法是在iter變量本身之后寫入名稱,並用逗號分隔。就像iter變量一樣,status變量的范圍也由持有th:each屬性的標簽所定義的代碼片段組成。

讓我們看一下處理模板的結果:

<!DOCTYPE html>

<html>

  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
    <link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" />
  </head>

  <body>

    <h1>Product list</h1>
  
    <table>
      <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
      </tr>
      <tr class="odd">
        <td>Fresh Sweet Basil</td>
        <td>4.99</td>
        <td>yes</td>
      </tr>
      <tr>
        <td>Italian Tomato</td>
        <td>1.25</td>
        <td>no</td>
      </tr>
      <tr class="odd">
        <td>Yellow Bell Pepper</td>
        <td>2.50</td>
        <td>yes</td>
      </tr>
      <tr>
        <td>Old Cheddar</td>
        <td>18.75</td>
        <td>yes</td>
      </tr>
    </table>
  
    <p>
      <a href="/gtvg/" shape="rect">Return to home</a>
    </p>

  </body>
  
</html>

請注意,我們的迭代狀態變量運行良好,odd僅對奇數行建立了CSS類。

如果您未明確設置狀態變量,則Thymeleaf將始終通過Stat為迭代變量的名稱添加后綴來為您創建一個:

<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
  </tr>
  <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
  </tr>
</table>

6.3 通過延遲檢索數據進行優化

有時我們可能想優化數據集合的檢索(例如從數據庫中),以便僅在真正要使用這些集合時才檢索這些集合。

實際上,這可以應用於任何數據,但是考慮到內存中集合可能具有的大小,對於這種情況,檢索要迭代的集合是最常見的情況。

為了支持這一點,Thymeleaf提供了一種延遲加載上下文變量的機制。實現該ILazyContextVariable接口的上下文變量(很可能是通過擴展其LazyContextVariable默認實現)將在執行時解決。例如:

context.setVariable(
     "users",
     new LazyContextVariable<List<User>>() {
         @Override
         protected List<User> loadValue() {
             return databaseRepository.findAllUsers();
         }
     });

可以在不了解其惰性的情況下使用此變量,例如:

<ul>
  <li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>

但是同時,loadValue()如果在以下代碼中condition計算為,則將永遠不會初始化(永遠不會調用其方法)false:

<ul th:if="${condition}">
  <li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>

7 條件評估

7.1 簡單條件:“如果”和“除非”

有時,您需要模板的一部分才能僅在滿足特定條件的情況下出現在結果中。

例如,假設我們要在產品表中顯示一列,其中包含每個產品的評論數量,如果有評論,則指向該產品的評論詳細信息頁面的鏈接。

為了做到這一點,我們將使用以下th:if屬性:

<table>
  <tr>
    <th>NAME</th>
    <th>PRICE</th>
    <th>IN STOCK</th>
    <th>COMMENTS</th>
  </tr>
  <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
    <td th:text="${prod.name}">Onions</td>
    <td th:text="${prod.price}">2.41</td>
    <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
    <td>
      <span th:text="${#lists.size(prod.comments)}">2</span> comment/s
      <a href="comments.html" 
         th:href="@{/product/comments(prodId=${prod.id})}" 
         th:if="${not #lists.isEmpty(prod.comments)}">view</a>
    </td>
  </tr>
</table>

在這里可以看到很多東西,所以讓我們集中在重要的一行上:

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

這將創建指向注釋頁面(帶有URL /product/comments)的鏈接,其prodId參數設置為id產品的,但前提是該產品具有任何注釋。

讓我們看一下結果標記:

<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還有一個inverse屬性,th:unless我們可以在前面的示例中使用它,而不是not在OGNL表達式內部使用:

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

7.2 切換語句

還有一種方法可以使用Java 中的開關結構的等效條件來顯示內容:th:switch/ th:case屬性集。

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
</div>

請注意,一旦一個th:case屬性的值為true,th:case同一切換上下文中的所有其他屬性的值為false。

默認選項指定為th:case="*":

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

8 模板布局

8.1 包括模板片段

定義和引用片段

在我們的模板中,我們經常需要包含其他模板中的部分,例如頁腳,頁眉,菜單等部分。

為了做到這一點,Thymeleaf需要我們定義這些要包含的部分“片段”,可以使用該th:fragment屬性來完成。

假設我們要在所有雜貨店頁面中添加標准的版權頁腳,因此我們創建一個/WEB-INF/templates/footer.html包含以下代碼的文件:

<!DOCTYPE html>

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

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

上面的代碼定義了一個片段copy,我們可以使用th:insert或th:replace屬性之一輕松地將其包含在主頁中(並且th:include,盡管從Thymeleaf 3.0開始不再建議使用它):

<body>

  ...

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

請注意,th:insert需要一個片段表達式(),這是一個產生片段的表達式。但是,在上面的示例中,它是一個非復雜的片段表達式,({,})包圍是完全可選的,因此上面的代碼等效於:

<body>

  ...

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

片段規范語法

片段表達式的語法非常簡單。有三種不同的格式:

  • ""包括在名為的模板上應用指定的標記選擇器所產生的片段templatename。請注意,該selector名稱可能僅僅是片段名稱,因此您可以像~上面一樣指定簡單的名稱。

標記選擇器語法由基礎的AttoParser解析庫定義,並且類似於XPath表達式或CSS選擇器。有關更多信息,請參見附錄C。

  • "~"包括名為的完整模板templatename。

請注意,您在th:insert/ th:replace標記中使用的模板名稱必須由模板引擎當前正在使用的模板解析器解析。

  • "或""從同一模板插入一個片段,匹配selector。如果在出現表達式的模板上未找到,則將模板調用(插入)堆棧遍歷到原始處理的模板(root),直到selector在某個級別上匹配為止。

雙方templatename並selector在上面的例子可以是全功能的表達式(甚至條件語句!),如:

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

再次注意周圍的~信封在th:insert/中是可選的th:replace。

片段可以包含任何th:*屬性。一旦將片段包含到目標模板(帶有th:insert/ th:replace屬性的片段)中,就會評估這些屬性,並且它們將能夠引用此目標模板中定義的任何上下文變量。

這種片段處理方法的一大優勢是,您可以將片段寫在瀏覽器可以完美顯示的頁面中,並具有完整甚至有效的標記結構,同時仍保留使Thymeleaf將其包含在其他模板中的功能。

引用片段不帶 th:fragment

由於標記選擇器的強大功能,我們可以包含不使用任何th:fragment屬性的片段。甚至可能是完全不了解Thymeleaf的來自不同應用程序的標記代碼:

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

我們可以使用上面的片段,簡單地通過其id屬性引用它,類似於CSS選擇器:

<body>

  ...

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

th:insert和th:replace(和th:include)之間的差異

和之間有什么區別th:insertth:replace(和th:include,因為3.0不推薦)?

th:insert 最簡單:它將簡單地將指定的片段作為其host標簽的主體插入。

th:replace實際上將其主機標簽替換為指定的片段。

th:include與相似th:insert,但不插入片段,而是僅插入該片段的內容。

因此,HTML片段如下所示:

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

…在主機<div>標簽中包含了3次,如下所示:

<body>

  ...

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

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

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

…將導致:

<body>

  ...

  <div>
    <footer>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>

  <div>
    &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>

8.2 可參數化的片段簽名

為了為模板片段創建更類似於函數的機制,使用定義的片段th:fragment可以指定一組參數:

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

這需要使用以下兩種語法之一來從th:insert或調用片段th:replace:

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

請注意,在最后一個選項中順序並不重要:

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

片段局部變量,不帶片段參數

即使片段沒有這樣的參數定義:

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

我們可以使用上面指定的第二種語法來調用它們(並且只有第二種):

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

這將相當於組合th:replace和th:with:

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

請注意,這種對片段的局部變量的規范(無論它是否具有參數簽名)都不會導致上下文在執行之前被清空。片段仍將能夠像當前一樣訪問在調用模板中使用的每個上下文變量。

聲明模板內斷言

該th:assert屬性可以指定一個逗號分隔的表達式列表,應對其進行評估,並為每次評估生成true,否則將引發異常。

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

這對於驗證片段簽名中的參數非常有用:

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

8.3 靈活的布局:不僅僅是插入片段

借助片段表達式,我們可以為片段指定參數,這些參數不是文本,數字,bean對象……而是標記片段。

這允許我們以一種方式來創建我們的片段,以便可以使用來自調用模板的標記來豐富它們,從而產生非常靈活的模板布局機制。

請注意以下片段中title和links變量的使用:

<head th:fragment="common_header(title,links)">

  <title th:replace="${title}">The awesome application</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
  <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
  <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>

  <!--/* Per-page placeholder for additional links */-->
  <th:block th:replace="${links}" />

</head>

現在,我們可以將該片段稱為:

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

  <title>Awesome - Main</title>

  <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
  <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">

</head>
...

…結果將使用調用模板中的實際<title><link>標記作為title和links變量的值,從而導致我們的片段在插入過程中被自定義:

...
<head>

  <title>Awesome - Main</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">
  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">

</head>
...

使用空片段

一個特殊的片段表達式,空片段(~),可用於指定無標記。使用前面的示例:

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

  <title>Awesome - Main</title>

</head>
...

請注意片段(links)的第二個參數如何設置為空片段,因此沒有為該<th:block th:replace="${links}" />塊編寫任何內容:

...
<head>

  <title>Awesome - Main</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

</head>
...

使用無操作令牌

如果我們只想讓我們的片段使用其當前標記作為默認值,那么no-op也可以用作片段的參數。再次使用common_header示例:

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

  <title>Awesome - Main</title>

  <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
  <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">

</head>
...

查看title參數(common_header片段的第一個參數)如何設置為no-op(_),這將導致片段的這一部分根本不執行(title= no-operation):

<title th:replace="${title}">The awesome application</title>

因此結果是:

...
<head>

  <title>The awesome application</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">
  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">

</head>
...

片段的高級條件插入

空片段和無操作令牌的可用性使我們能夠以非常容易且優雅的方式有條件地插入片段。

例如,我們可以做到這一點,以便插入我們的common :: adminhead片段只有當用戶是管理員,並插入任何內容(空片段)如果不是:

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

同樣,我們可以使用no-operation令牌來僅在滿足指定條件時插入片段,而在不滿足條件的情況下不做任何修改就保留標記:

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

另外,如果我們已經配置了模板解析器以通過其標志- 檢查模板資源checkExistence的存在,我們可以使用片段本身的存在作為默認操作中的條件:

...
<!-- The body of the <div> will be used if the "common :: salutation" fragment  -->
<!-- does not exist (or is empty).                                              -->
<div th:insert="~{common :: salutation} ?: _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

8.4 刪除模板片段

回到示例應用程序,讓我們重新訪問產品列表模板的最新版本:

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

這段代碼作為模板很好用,但是作為靜態頁面(當瀏覽器直接打開而不由Thymeleaf處理時)將不能成為一個好的原型。

為什么?因為盡管該表可被瀏覽器完美顯示,但該表僅具有一行,並且該行具有模擬數據。作為原型,它看起來根本不夠現實……我們應該有多個產品,我們需要更多行。

因此,我們添加一些:

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

好的,現在我們有三個,對於原型來說絕對更好。但是……當我們用Thymeleaf處理它時會發生什么?:

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

最后兩行是模擬行!好吧,它們當然是:迭代僅應用於第一行,因此沒有理由Thymeleaf應該刪除其他兩行。

我們需要一種在模板處理期間刪除這兩行的方法。讓我們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標准表示,只要它返回所允許的字符串值中的一個(all,tag,body,all-but-first或none)。

這意味着刪除可能是有條件的,例如:

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

還要注意,它th:remove考慮null了的同義詞none,因此以下內容與上面的示例相同:

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

在這種情況下,如果$為false,null將被返回,因此不會執行刪除。

8.5 布局繼承

為了能夠將單個文件作為布局,可以使用片段。具有title和content使用th:fragment和的簡單布局的示例th:replace:

<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">Layout Title</title>
</head>
<body>
    <h1>Layout H1</h1>
    <div th:replace="${content}">
        <p>Layout content</p>
    </div>
    <footer>
        Layout footer
    </footer>
</body>
</html>

此示例聲明一個名為layout的片段,其中標題和內容為參數。在下面的示例中,這兩者都將在頁面上被繼承的片段表達式替換,並繼承它。

<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
    <title>Page Title</title>
</head>
<body>
<section>
    <p>Page content</p>
    <div>Included on page</div>
</section>
</body>
</html>

在這個文件中,該html標簽將被替換的布局,但在布局title和content將已被替換title,並section分別塊。

如果需要,布局可以由幾個片段組成,例如header和footer。

9 局部變量

Thymeleaf將局部變量稱為為模板的特定片段定義的變量,並且僅可用於該片段內部的評估。

我們已經看到的示例是prod產品列表頁面中的iter變量:

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

該prod變量僅在<tr>標記范圍內可用。特別:

  • 該th:*標簽可用於在該標簽中執行的所有其他屬性,該屬性的優先級低於th:each(這意味着它們將在之后執行th:each)。
  • 它將可用於<tr>標簽的任何子<td>元素,例如任何元素。

Thymeleaf為您提供了一種使用th:with屬性聲明局部變量而無需迭代的方法,其語法類似於屬性值分配的語法:

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

當th:with被處理時,該firstPer變量被創建為一個局部變量,並加入到變量映射從上下文來,使得它可用於評估與在上下文中聲明的任何其它變量一起,但僅在含有的邊界<div>標記。

您可以使用通常的多重賦值語法同時定義幾個變量:

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

該th:with屬性允許重用在同一屬性中定義的變量:

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

讓我們在雜貨店的首頁中使用它!還記得我們編寫的用於輸出格式化日期的代碼嗎?

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

好吧,如果我們希望它"dd MMMM yyyy"實際上取決於語言環境呢?例如,我們可能想向我們添加以下消息home_en.properties:

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

…和我們的同等產品home_es.properties:

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

現在,讓我們使用th:with將本地化的日期格式轉換為變量,然后在th:text表達式中使用它:

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

那很干凈很容易。實際上,考慮到th:with具有precedence大於的事實th:text,我們可以在span標記中解決所有問題:

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

您可能在想:優先?我們還沒有談論這個!好吧,不用擔心,因為這正是下一章的內容。

10 屬性優先級

當您th:*在同一個標​​記中寫入多個屬性時會發生什么?例如:

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

我們希望該th:each屬性在之前執行,th:text以便獲得所需的結果,但是考慮到HTML / XML標准沒有給標記中的屬性寫入順序賦予任何含義,因此優先必須在屬性本身中建立機制,以確保這將按預期工作。

因此,所有Thymeleaf屬性都定義了數字優先級,從而確定了它們在標簽中執行的順序。該順序是:

這種優先機制意味着,如果屬性位置反轉,則上述迭代片段將給出完全相同的結果(盡管可讀性稍差):

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

11 注釋和塊(Comments and Blocks)

11.1 標准HTML / XML注釋

<!-- ... -->Thymeleaf模板中的任何位置都可以使用標准的HTML / XML注釋。這些注釋中的所有內容均不會被Thymeleaf處理,並將逐字復制到結果中:

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

11.2 Thymeleaf解析器級注釋塊

解析器級別的注釋塊是在Thymeleaf對其進行解析時,將從模板中簡單刪除的代碼。他們看起來像這樣:

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

Thymeleaf將刪除<!--/*和之間的所有內容*/-->,因此,當模板靜態打開時,這些注釋塊也可用於顯示代碼,因為知道Thymeleaf處理模板時會將其刪除:

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

<tr>例如,對於帶有很多的表進行原型制作可能會非常方便:

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

11.3 Thymeleaf僅原型注釋塊

Thymeleaf允許定義特殊注釋塊的定義,當模板靜態打開(即作為原型)時,標記為注釋,但Thymeleaf在執行模板時將其視為正常標記。

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

Thymeleaf的解析系統將僅刪除<!--/*//*/-->標記,但不會刪除其內容,因此不會對其進行注釋。因此,在執行模板時,Thymeleaf實際上會看到以下內容:

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

與解析器級注釋塊一樣,此功能與方言無關。

11.4 合成th:block標簽

Thymeleaf的標准方言中唯一的元素處理器(不是屬性)是th:block。

th:block僅是一個屬性容器,允許模板開發人員指定所需的任何屬性。Thymeleaf將執行這些屬性,然后簡單地使該塊(而不是其內容)消失。

因此,例如在創建<tr>每個表都需要多個表的迭代表時,它可能會很有用:

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

與僅原型注釋塊結合使用時特別有用:

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

請注意,此解決方案如何使模板成為有效的HTML(無需<div>在內添加禁止塊<table>),並且當在瀏覽器中作為原型靜態打開時,仍可以正常使用!

12 內聯

12.1 表達式內聯

盡管標准方言允許我們使用標記屬性來執行幾乎所有操作,但是在某些情況下,我們更喜歡直接將表達式寫到HTML文本中。例如,我們可能更喜歡這樣編寫:

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

…代替此:

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

在Thymeleaf中,[[...]]或之間的表達式[(...)]被認為是內聯表達式,在它們內部,我們可以使用在th:textor th:utext屬性中也有效的任何類型的表達式。

請注意,盡管[[...]]對應於th:text(即結果將被HTML轉義),但[(...)]對應於th:utext並且將不執行任何HTML轉義。因此msg = 'This is <b>great!</b>',給定該片段,使用諸如的變量:

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

結果將使那些<b>標簽未轉義,因此:

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

而如果像這樣逃脫了:

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

結果將轉義為HTML:

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

請注意,默認情況下,文本內聯在標記中每個標簽的主體(而不是標簽本身)中處於活動狀態,因此我們無需執行任何操作即可啟用它。

內聯與自然模板

如果您來自以這種方式輸出文本為標准的其他模板引擎,您可能會問:我們為什么不從一開始就這樣做?比所有這些 屬性更少的代碼th:text !

好吧,要小心,因為盡管您可能會發現內聯非常有趣,但是您應該始終記住,當靜態打開內聯表達式時,它們會逐字顯示在HTML文件中,因此您可能無法將它們用作設計原型不再!

瀏覽器不使用內聯靜態顯示代碼片段的方式之間的區別...

Hello, Sebastian!

…並使用它…

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

……在設計實用性方面非常清楚。

禁用內聯

不過,可以禁用此機制,因為實際上在某些情況下,我們確實希望輸出[[...]]or [(...)]序列而不將其內容作為表達式處理。為此,我們將使用th:inline="none":

<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

這將導致:

<p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

12.2 文字內聯

文本內聯與我們剛剛看到的表達式內聯功能非常相似,但實際上增加了更多功能。必須使用明確啟用它th:inline="text"。

文本內聯不僅使我們能夠使用與剛才看到的相同的內聯表達式,而且實際上就像在模板模式下處理標簽主體一樣處理標簽主體TEXT,這使我們能夠執行基於文本的模板邏輯(不僅是輸出表達式)。

我們將在下一章有關文本模板模式的內容中看到更多有關此內容的信息。

12.3 JavaScript內聯

JavaScript內聯允許<script>在以HTML模板方式處理的模板中更好地集成JavaScript 塊。

與文本內聯一樣,這實際上等同於將腳本內容當作JAVASCRIPT模板模式下的模板來處理,因此,文本模板模式的所有功能(請參閱下一章)將近在咫尺。但是,在本節中,我們將重點介紹如何使用它將Thymeleaf表達式的輸出添加到JavaScript塊中。

必須使用th:inline="javascript"以下命令顯式啟用此模式:

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

這將導致:

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

上面的代碼中有兩點需要注意:

首先,JavaScript內聯不僅會輸出所需的文本,而且還會用引號將其括起來,並對其內容進行JavaScript轉義,以便將表達式結果輸出為格式良好的JavaScript文字。

其次,發生這種情況是因為我們將${session.user.name}表達式輸出為轉義的,即使用雙括號表達式:[[${session.user.name}]]。如果相反,我們使用未轉義的形式:

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

結果如下所示:

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

…這是格式錯誤的JavaScript代碼。但是,如果我們通過附加內聯表達式來構建腳本的某些部分,則可能需要輸出未轉義的內容,因此手頭有此工具是件好事。

JavaScript自然模板

所提到的JavaScript內聯機制的智能遠不止於應用特定於JavaScript的轉義並將表達式結果輸出為有效文字。

例如,我們可以將(轉義的)內聯表達式包裝在JavaScript注釋中,例如:

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

而且Thymeleaf將忽略注釋之后和分號之前的所有內容(在本例中為'Gertrud Kiwifruit'),因此執行此操作的結果將與未使用包裝注釋時的情況完全相同:

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

但是,請仔細查看原始模板代碼:

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

注意這是有效的JavaScript代碼。當您以靜態方式打開模板文件(無需在服務器上執行)時,它將完美執行。

因此,這里提供的是一種制作JavaScript自然模板的方法!

高級內聯評估和JavaScript序列化

關於JavaScript內聯的重要注意事項是,此表達式求值是智能的,並且不僅限於字符串。Thymeleaf將使用JavaScript語法正確編寫以下類型的對象:

  • Strings
  • Numbers
  • Booleans
  • Arrays
  • Collections
  • Maps
  • Beans (objects with getter and setter methods) 例如,如果我們有以下代碼:
<script th:inline="javascript">
    ...
    var user = /*[[${session.user}]]*/ null;
    ...
</script>

${session.user}表達式將求值為User對象,Thymeleaf會將其正確轉換為Javascript語法:

<script th:inline="javascript">
    ...
    var user = {"age":null,"firstName":"John","lastName":"Apricot",
                "name":"John Apricot","nationality":"Antarctica"};
    ...
</script>

完成該JavaScript序列化的方式是通過org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer接口的實現,該接口可以StandardDialect在模板引擎使用實例的實例中進行配置。

該JS序列化機制的默認實現將在類路徑中查找Jackson庫,如果有的話,將使用它。如果沒有,它將應用內置的序列化機制,該機制可以滿足大多數方案的需求並產生相似的結果(但靈活性較差)。

12.4 CSS內聯

Thymeleaf還允許在CSS <style>標簽中使用內聯,例如:

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

例如,假設我們將兩個變量設置為兩個不同的String值:

classname = 'main elems'
align = 'center'

我們可以像這樣使用它們:

<style th:inline="css">
    .[[${classname}]] {
      text-align: [[${align}]];
    }
</style>

結果將是:

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

請注意,CSS內聯也像JavaScript一樣具有一定的智能。具體來說,通過轉義的表達式(例如)輸出的表達式[[${classname}]]將作為CSS標識符轉義。這就是為什么我們classname = 'main elems'變成了main\ elems上面的代碼片段。

進階功能:CSS自然模板等

與之前解釋JavaScript的方式相同,CSS內聯還允許我們的<style>標簽靜態和動態地工作,即通過將內聯表達式包裝在注釋中而成為CSS自然模板。看到:

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

13 文字模板模式

13.1 文字語法

在Thymeleaf的三種模板模式被認為是文字:TEXT,JAVASCRIPT和CSS。這將它們與標記模板模式區分開:HTML和XML。

文本模板模式和標記模式之間的主要區別在於,在文本模板中,沒有標簽可以插入屬性形式的邏輯,因此我們必須依靠其他機制。

這些機制的第一個也是最基本的是內聯,我們已經在上一章中進行了詳細介紹。內聯語法是在文本模板模式下輸出表達式結果的最簡單方法,因此,這是文本電子郵件的完美有效模板。

Dear [(${name})],

  Please find attached the results of the report you requested
  with name "[(${report.name})]".

  Sincerely,
    The Reporter.

即使沒有標簽,上面的示例也是一個完整且有效的Thymeleaf模板,可以在TEXT模板模式下執行。

但是,為了包含比單純的輸出表達式更復雜的邏輯,我們需要一種新的非基於標記的語法:

[# th:each="item : ${items}"]
  - [(${item})]
[/]

實際上是更冗長的精簡版本:

[#th:block th:each="item : ${items}"]
  - [#th:block th:utext="${item}" /]
[/th:block]

請注意,這種新語法是如何基於聲明為的元素(即可處理標簽)[#element ...]<element ...>。元素的打開方式類似於[#element ...]和閉合的方式一樣[/element],並且可以通過將open元素最小化來聲明獨立標簽/,該方式幾乎等同於XML標簽:[#element ... /]

標准方言僅包含以下元素之一的處理器:眾所周知的th:block,盡管我們可以在方言中擴展它並以通常的方式創建新元素。另外,th:block元素([#th:block ...] ... [/th:block])可以縮寫為空字符串([# ...] ... [/]),因此上述代碼塊實際上等效於:

[# th:each="item : ${items}"]
  - [# th:utext="${item}" /]
[/]

給定[# th:utext="${item}" /]等效於內聯的未轉義表達式,我們可以使用它來減少代碼量。因此,我們結束了上面看到的代碼的第一個片段:

[# th:each="item : ${items}"]
  - [(${item})]
[/]

請注意,文本語法要求元素平衡(沒有未關閉的標簽)和帶引號的屬性 – XML樣式比HTML樣式更多。

我們來看一個更完整的TEXT模板示例,即純文本電子郵件模板:

Dear [(${customer.name})],

This is the list of our products:

[# th:each="prod : ${products}"]
   - [(${prod.name})]. Price: [(${prod.price})] EUR/kg
[/]

Thanks,
  The Thymeleaf Shop

執行后,其結果可能類似於:

Dear Mary Ann Blueberry,

This is the list of our products:

   - Apricots. Price: 1.12 EUR/kg
   - Bananas. Price: 1.78 EUR/kg
   - Apples. Price: 0.85 EUR/kg
   - Watermelon. Price: 1.91 EUR/kg

Thanks,
  The Thymeleaf Shop

JAVASCRIPT模板模式下的另一個示例(greeter.js文件)將作為文本模板進行處理,然后從HTML頁面調用該結果。請注意,這不是<script> HTML模板中的塊,而是.js單獨作為模板處理的文件:

var greeter = function() {

    var username = [[${session.user.name}]];

    [# th:each="salut : ${salutations}"]    
      alert([[${salut}]] + " " + username);
    [/]

};

執行后,其結果可能類似於:

var greeter = function() {

    var username = "Bertrand \"Crunchy\" Pear";

      alert("Hello" + " " + username);
      alert("Ol\u00E1" + " " + username);
      alert("Hola" + " " + username);

};

轉義的元素屬性

為了避免與模板的其他部分可能會以其他方式處理的交互(例如,text在HTML模板內部的-mode內聯),Thymeleaf 3.0允許轉義其文本語法中元素的屬性。所以:

  • TEXT模板模式下的屬性將采用HTML轉換格式。
  • JAVASCRIPT模板模式下的屬性將是JavaScript非轉義的。
  • CSS模板模式下的屬性將采用CSS換碼。

因此,這在TEXT-mode模板中是完全可以的(請注意&gt;):

[# th:if="${120&lt;user.age}"]
     Congratulations!
  [/]

當然,&lt;在實際的文本模板中這沒有任何意義,但是如果我們正在使用th:inline="text"包含上面代碼的代碼塊處理HTML模板,並且要確保我們的瀏覽器不會將它<user.age用作名稱的話,這是一個好主意。靜態打開文件作為原型時的open標簽。

13.2 可擴展性

這種語法的優點之一是它與標記語法一樣可擴展。開發人員仍然可以使用自定義元素和屬性來定義自己的方言,為它們應用前綴(可選),然后在文本模板模式下使用它們:

  [#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]

13.3 純文本原型注釋塊:添加代碼

在JAVASCRIPT和CSS模板模式(不適用於TEXT),允許包括一個特殊的注釋語法之間的代碼/*[+...+]*/,這樣Thymeleaf會處理模板時自動取消注釋這樣的代碼:

var x = 23;

/*[+

var msg  = "This is a working application";

+]*/

var f = function() {
    ...

將執行為:

var x = 23;

var msg  = "This is a working application";

var f = function() {
...

您可以在這些注釋中包含表達式,它們將被評估:

var x = 23;

/*[+

var msg  = "Hello, " + [[${session.user.name}]];

+]*/

var f = function() {
...

13.4 文本解析器級注釋塊:刪除代碼

在類似於僅原型的注釋塊的方式,所有三個文本模板模式(TEXT,JAVASCRIPT和CSS)使其能夠指示Thymeleaf特殊之間移除代碼/*[- *//* -]*/標志,就像這樣:

var x = 23;

/*[- */

var msg  = "This is shown only when executed statically!";

/* -]*/

var f = function() {
...

或在TEXT模式下:

...
/*[- Note the user is obtained from the session, which must exist -]*/
Welcome [(${session.user.name})]!
...

13.5 自然的JavaScript和CSS模板

如上一章所述,JavaScript和CSS內聯提供了將內聯表達式包含在JavaScript / CSS注釋中的可能性,例如:

...
var username = /*[[${session.user.name}]]*/ "Sebastian Lychee";
...

…這是有效的JavaScript,執行后的外觀如下:

...
var username = "John Apricot";
...

實際上,可以將這種將內聯表達式包含在注釋中的相同技巧可用於整個文本模式語法:

 /*[# th:if="${user.admin}"]*/
     alert('Welcome admin');
  /*[/]*/

如果模板是靜態打開的(因為它是100%有效的JavaScript),並且如果用戶是管理員運行模板,則將在上面的代碼中顯示該警報。它等效於:

[# th:if="${user.admin}"]
     alert('Welcome admin');
[/]

…實際上是模板解析期間將初始版本轉換為的代碼。

但是請注意,在注釋中包裝元素並不會;像內聯輸出表達式那樣清除它們所在的行(直到找到a為止,一直在右邊)。該行為僅保留給內聯輸出表達式。

因此Thymeleaf 3.0允許以自然模板的形式開發復雜的JavaScript腳本和CSS樣式表,這些模板既可以作為原型也可以作為工作模板使用。

14 雜貨店的更多頁面

現在我們對使用Thymeleaf有了很多了解,我們可以在我們的網站上添加一些新頁面以進行訂單管理。

請注意,我們將專注於HTML代碼,但是如果您想查看相應的控制器,則可以查看捆綁的源代碼。

14.1 訂單清單

讓我們從創建訂單列表頁面開始/WEB-INF/templates/order/list.html

<!DOCTYPE html>

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

  <head>

    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all" 
          href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
  </head>

  <body>

    <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對象),將其purchasePrice和amount屬性相乘(通過調用相應的getPurchasePrice()和getAmount()方法),然后將結果返回到數字列表中,然后由該#aggregates.sum(...)函數進行匯總,以獲取訂單總數價錢。

您必須喜歡OGNL的強大功能。

14.2 訂單明細

現在進入訂單詳細信息頁面,在該頁面中,我們將大量使用星號語法:

<!DOCTYPE html>

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

  <head>
    <title>Good Thymes Virtual Grocery</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" media="all" 
          href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
  </head>

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

除了嵌套對象選擇之外,這里沒有太多新的東西:

<body th:object="${order}">

  ...

  <div th:object="*{customer}">
    <p><b>Name:</b> <span th:text="*{name}">Frederic Tomato</span></p>
    ...
  </div>

  ...
</body>

*{name}等於

<p><b>Name:</b> <span th:text="${order.customer.name}">Frederic Tomato</span></p>

15 有關配置的更多信息

15.1 模板解析器

對於我們的Good Thymes虛擬雜貨店,我們選擇了一個ITemplateResolver實現ServletContextTemplateResolver,該實現允許我們從Servlet上下文中獲取模板作為資源。

除了使我們能夠通過實現ITemplateResolver,Thymeleaf 來創建自己的模板解析器之外,還包括以下四種實現:

  • org.thymeleaf.templateresolver.ClassLoaderTemplateResolver,它將模板解析為類加載器資源,例如:
return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);
  • org.thymeleaf.templateresolver.FileTemplateResolver,它將模板解析為來自文件系統的文件,例如:
return new FileInputStream(new File(template));
  • org.thymeleaf.templateresolver.UrlTemplateResolver,它將模板解析為URL(甚至是非本地的URL),例如:
return (new URL(template)).openStream();
  • org.thymeleaf.templateresolver.StringTemplateResolver,它直接將模板解析String為指定為的名稱template(或模板名稱,在這種情況下,顯然不僅僅是一個簡單的名稱):
return new StringReader(templateName);

所有預先捆綁的實現都ITemplateResolver允許使用相同的配置參數集,其中包括:

  • 前綴和后綴(如前所述):
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
  • 模板別名允許使用與文件名不直接對應的模板名。如果后綴/前綴和別名都存在,則別名將在前綴/后綴之前應用:
templateResolver.addTemplateAlias("adminHome","profiles/admin/home");
templateResolver.setTemplateAliases(aliasesMap);
  • 讀取模板時要應用的編碼:
templateResolver.setEncoding("UTF-8");
  • 使用的模板模式:
// Default is HTML
templateResolver.setTemplateMode("XML");
  • 模板緩存的默認模式,以及用於定義特定模板是否可緩存的模式:
// Default is true
templateResolver.setCacheable(false);
templateResolver.getCacheablePatternSpec().addPattern("/users/*");
  • 源自此模板解析器的已解析模板緩存條目的TTL(以毫秒為單位)。如果未設置,則從緩存中刪除條目的唯一方法是超過緩存的最大大小(最舊的條目將被刪除)。
// Default is no TTL (only cache size exceeded would remove entries)
templateResolver.setCacheTTLMs(60000L);

Thymeleaf + Spring集成軟件包提供了一個SpringResourceTemplateResolver實現,該實現使用所有Spring基礎結構來訪問和讀取應用程序中的資源,這是在支持Spring的應用程序中推薦的實現。

鏈模板解析器

此外,模板引擎可以指定多個模板解析器,在這種情況下,可以在它們之間建立順序以進行模板解析,這樣,如果第一個解析器無法解析模板,則要求第二個解析器,依此類推:

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

ServletContextTemplateResolver servletContextTemplateResolver = 
        new ServletContextTemplateResolver(servletContext);
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(servletContext);
servletContextTemplateResolver.setOrder(Integer.valueOf(2));

如果未指定這些可解析的模式,我們將依賴於ITemplateResolver我們所使用的每個實現的特定功能。請注意,並非所有的實現都能夠在解析之前確定模板的存在,因此始終可以將模板視為可解析的,並打破了解析鏈(不允許其他解析器檢查同一模板),但是卻無法閱讀實際資源。

所有ITemplateResolver附帶核心Thymeleaf實現包括一種機制,將使我們能夠使解析器真正檢查如果資源考慮之前存在解析。它是checkExistence標志,其工作方式如下:

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

該checkExistence標志強制解析器在解析階段對資源是否存在進行真正的檢查(如果存在檢查返回false,則調用鏈中的以下解析器)。盡管這在每種情況下聽起來都不錯,但在大多數情況下,這將意味着對資源本身的雙重訪問(一次檢查是否存在,另一次讀取它),並且在某些情況下可能會成為性能問題,例如,基於遠程URL模板資源–潛在的性能問題可能會通過使用模板緩存而在很大程度上得到緩解(在這種情況下,僅在首次訪問模板時才能解決模板問題)。

15.2 郵件解析器

我們沒有為Grocery應用程序明確指定Message Resolver實現,並且如前所述,這意味着所使用的實現是一個org.thymeleaf.messageresolver.StandardMessageResolver對象。

StandardMessageResolver是IMessageResolver接口的標准實現,但是如果需要,我們可以創建自己的接口,以適應應用程序的特定需求。

Thymeleaf + Spring集成軟件包默認提供一種IMessageResolver實現,該實現使用標准Spring方法來檢索外部化消息,方法是使用MessageSource在Spring Application Context聲明的bean。

標准消息解析器

那么,如何StandardMessageResolver查找在特定模板上請求的消息?

如果模板名稱為home,並且位於中/WEB-INF/templates/home.html,並且請求的語言環境為,gl_ES則此解析器將按以下順序在以下文件中查找消息:

/WEB-INF/templates/home_gl_ES.properties
/WEB-INF/templates/home_gl.properties
/WEB-INF/templates/home.properties

StandardMessageResolver有關完整的消息解析機制如何工作的更多詳細信息,請參閱該類的JavaDoc文檔。

配置消息解析器

如果我們想向模板引擎添加消息解析器(或更多)怎么辦?簡單:

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

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

為什么我們要擁有多個消息解析器?出於與模板解析器相同的原因:訂購了消息解析器,如果第一個無法解析特定的消息,則將詢問第二個,然后詢問第三個,依此類推。

15.3 轉換服務

該轉換服務,使我們用的手段來進行數據轉換和格式化操作雙括號語法(${{...}})實際上是標准方言的特點,而不是Thymeleaf模板引擎本身。

這樣,配置它的方法是通過IStandardConversionService直接將我們的接口的自定義實現設置StandardDialect為正配置到模板引擎中的接口的實例。喜歡:

IStandardConversionService customConversionService = ...

StandardDialect dialect = new StandardDialect();
dialect.setConversionService(customConversionService);

templateEngine.setDialect(dialect);

請注意,thymeleaf-spring3和thymeleaf-spring4軟件包包含SpringStandardDialect,並且該方言已經預先配置了IStandardConversionService將Spring自己的Conversion Service基礎結構集成到Thymeleaf中的實現。

15.4 記錄

Thymeleaf非常重視日志記錄,並始終嘗試通過其日志記錄界面提供盡可能多的有用信息。

slf4j,實際上,所使用的日志記錄庫實際上充當了我們想要在應用程序中使用的任何日志記錄實現的橋梁(例如log4j)。

Thymeleaf班會記錄TRACE,DEBUG並INFO-level信息,這取決於我們希望的詳細程度,並且除了一般的記錄它會使用與TemplateEngine類,我們可以為不同的目的而單獨配置相關的三個特殊記錄器:

  • org.thymeleaf.TemplateEngine.CONFIG 在初始化期間將輸出庫的詳細配置。
  • org.thymeleaf.TemplateEngine.TIMER 將輸出有關處理每個模板所需時間的信息(可用於基准測試!)
  • org.thymeleaf.TemplateEngine.cache是一組記錄器的前綴,該記錄器輸出有關緩存的特定信息。盡管緩存記錄器的名稱可由用戶配置,因此可以更改,但是默認情況下它們是: *org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE *org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE

使用的Thymeleaf日志記錄基礎結構的示例配置log4j可能是:

log4j.logger.org.thymeleaf=DEBUG
log4j.logger.org.thymeleaf.TemplateEngine.CONFIG=TRACE
log4j.logger.org.thymeleaf.TemplateEngine.TIMER=TRACE
log4j.logger.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE=TRACE

16 模板緩存

Thymeleaf的工作要歸功於一組解析器(用於標記和文本),該解析器將模板解析為事件序列(打開標簽,文本,關閉標簽,注釋等)和一系列處理器(每種需要一種行為)應用–修改模板解析的事件序列,以便通過將原始模板與我們的數據結合來創建我們期望的結果。

默認情況下,它還包括存儲已解析模板的緩存;在處理模板文件之前讀取和解析模板文件所導致的事件順序。在Web應用程序中工作時,此功能特別有用,它基於以下概念:

  • 輸入/輸出幾乎始終是所有應用程序中最慢的部分。相比之下,內存中處理非常快。
  • 克隆現有的內存中事件序列總是比讀取模板文件,對其進行解析並為其創建新的事件序列要快得多。
  • Web應用程序通常只有幾十個模板。
  • 模板文件大小不一,在應用程序運行時不會被修改。

所有這些都導致了這樣的想法,即在不浪費大量內存的情況下在Web應用程序中緩存最常用的模板是可行的,並且這將節省大量時間,而這些時間將花費在少量文件的輸入/輸出操作上實際上,它永遠不會改變。

以及我們如何控制此緩存?首先,我們已經了解到可以在模板解析器上啟用或禁用它,甚至只對特定模板起作用:

// Default is true
templateResolver.setCacheable(false);
templateResolver.getCacheablePatternSpec().addPattern("/users/*");

同樣,我們可以通過建立自己的緩存管理器對象來修改其配置,該對象可以是默認StandardCacheManager實現的一個實例:

// Default is 200
StandardCacheManager cacheManager = new StandardCacheManager();
cacheManager.setTemplateCacheMaxSize(100);
...
templateEngine.setCacheManager(cacheManager);

org.thymeleaf.cache.StandardCacheManager有關配置緩存的更多信息,請參考的Javadoc API 。

可以從模板緩存中手動刪除條目:

// Clear the cache completely
templateEngine.clearTemplateCache();

// Clear a specific template from the cache
templateEngine.clearTemplateCacheFor("/users/userList");

17 解耦模板邏輯

17.1 解耦邏輯:概念

到目前為止,我們已經為食品雜貨店工作,模板以通常的方式完成,邏輯以屬性的形式插入模板中。

但是Thymeleaf還允許我們將模板標記與其邏輯完全分離,從而允許在和模板模式下創建完全無邏輯的標記模板。HTMLXML

主要思想是模板邏輯將在單獨的邏輯文件中定義(更確切地說是邏輯資源,因為它不必是file)。默認情況下,該邏輯資源將是與模板文件位於同一位置(例如,文件夾)的附加文件,其名稱相同,但.th.xml擴展名為:

/templates
+->/home.html
+->/home.th.xml

因此,該home.html文件可以完全沒有邏輯。它可能看起來像這樣:

<!DOCTYPE html>
<html>
  <body>
    <table id="usersTable">
      <tr>
        <td class="username">Jeremy Grapefruit</td>
        <td class="usertype">Normal User</td>
      </tr>
      <tr>
        <td class="username">Alice Watermelon</td>
        <td class="usertype">Administrator</td>
      </tr>
    </table>
  </body>
</html>

那里絕對沒有Thymeleaf代碼。這是沒有Thymeleaf或模板知識的設計人員可以創建,編輯和/或理解的模板文件。或某些外部系統完全沒有Thymeleaf鈎子提供的HTML片段。

現在,home.html通過創建如下所示的其他home.th.xml文件,將該模板轉換為Thymeleaf模板:

<?xml version="1.0"?>
<thlogic>
  <attr sel="#usersTable" th:remove="all-but-first">
    <attr sel="/tr[0]" th:each="user : ${users}">
      <attr sel="td.username" th:text="${user.name}" />
      <attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" />
    </attr>
  </attr>
</thlogic>

在這里,我們可以看到<attr>一個thlogic塊內有很多標簽。這些<attr>標簽對通過其屬性選擇的原始模板的節點執行屬性注入,這些sel屬性包含Thymeleaf 標記選擇器(實際上是AttoParser標記選擇器)。

另請注意,<attr>可以嵌套標簽,以便附加選擇器。即sel="/tr[0]"上述中,例如,將被處理為sel="#usersTable/tr[0]"。用戶名的選擇器<td>將被處理為sel="#usersTable/tr[0]//td.username"。

因此,一旦合並,上面看到的兩個文件將與以下內容相同:

<!DOCTYPE html>
<html>
  <body>
    <table id="usersTable" th:remove="all-but-first">
      <tr th:each="user : ${users}">
        <td class="username" th:text="${user.name}">Jeremy Grapefruit</td>
        <td class="usertype" th:text="#{|user.type.${user.type}|}">Normal User</td>
      </tr>
      <tr>
        <td class="username">Alice Watermelon</td>
        <td class="usertype">Administrator</td>
      </tr>
    </table>
  </body>
</html>

與創建兩個單獨的文件相比,這看起來更熟悉,並且確實不那么冗長。但是,解耦模板的優勢在於,我們可以使模板完全獨立於Thymeleaf,因此從設計的角度來看,它具有更好的可維護性。

當然,仍然需要設計人員或開發人員之間的一些合同,例如,用戶<table>將需要一個合同id="usersTable",但是在許多情況下,純HTML模板將是設計團隊和開發團隊之間更好的溝通工具。

17.2 配置解耦的模板

啟用解耦的模板

默認情況下,不會期望每個模板都使用去耦邏輯。取而代之的是,配置的模板解析器(的實現ITemplateResolver)將需要使用解耦邏輯將要解析的模板專門標記為。

除了StringTemplateResolver(不允許解耦邏輯)外,的所有其他現成實現都ITemplateResolver將提供一個稱為的標志useDecoupledLogic,該標志將將該解析器解析的所有模板標記為可能將其全部或部分邏輯存儲在單獨的資源中:

final ServletContextTemplateResolver templateResolver = 
        new ServletContextTemplateResolver(servletContext);
...
templateResolver.setUseDecoupledLogic(true);

混合耦合和解耦邏輯

啟用后,解耦模板邏輯不是必需的。啟用后,這意味着引擎將查找包含解耦邏輯的資源,如果存在,則將其解析並與原始模板合並。如果解耦的邏輯資源不存在,則不會引發任何錯誤。

同樣,在同一模板中,我們可以混合使用耦合邏輯和解耦邏輯,例如,通過在原始模板文件中添加一些Thymeleaf屬性,而將其他屬性留給單獨的解耦邏輯文件。最常見的情況是使用new(在v3.0中)th:ref屬性。

17.3 th:ref屬性

th:ref只是標記屬性。從處理的角度來看,它什么也沒做,只是在處理模板后消失,但是它的作用在於它充當標記引用,即可以通過標記選擇器中的名稱來解析,就像標記名或片段一樣。(th:fragment)。

因此,如果我們有一個選擇器,例如:

  <attr sel="whatever" .../>

這將匹配:

  • 任何<whatever>標簽。
  • 具有th:fragment="whatever"屬性的任何標簽。
  • 具有th:ref="whatever"屬性的任何標簽。

錨,這一事實最終可能會污染我們的輸出。

從同樣的意義上說,它的缺點是th:ref什么?好吧,很顯然,我們將在模板中添加一些Thymeleaf邏輯(“邏輯”)。

請注意,該th:ref屬性的適用性不僅適用於解耦的邏輯模板文件:它在其他類型的場景中也一樣工作,例如在片段表達式(~)中。

17.4 解耦模板的性能影響

影響極小。當一個已解析的模板被標記為使用解耦邏輯並且不被緩存時,該模板邏輯資源將首先被解析,解析並處理為一系列內存中的指令:基本上是要注入到每個標記選擇器的屬性列表。

但這是唯一需要執行的附加步驟,因為在此之后,將解析真實模板,並且在解析這些模板時,由於AttoParser中節點選擇的高級功能,這些屬性將由解析器本身即時注入。。因此,已解析的節點將從解析器中出來,就像它們的注入屬性寫在原始模板文件中一樣。

這樣最大的優勢?將模板配置為要緩存時,它將緩存已包含注入屬性的模板。因此,一旦對高速緩存的模板使用解耦的模板進行緩存,其開銷將絕對為零。

17.5 解耦邏輯的解析

Thymeleaf解析與每個模板相對應的解耦邏輯資源的方式可由用戶配置。它由擴展點決定org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver,為其提供了默認實現:StandardDecoupledTemplateLogicResolver。

此標准實現有什么作用?

  • 首先,它將a prefix和a 應用於模板資源suffix的基本名稱(通過其ITemplateResource#getBaseName()方法獲得)。前綴和后綴都可以配置,默認情況下,前綴為空,后綴為.th.xml。
  • 其次,它要求模板資源通過其方法來解析具有所計算名稱的相對資源ITemplateResource#relative(String relativeLocation)。

IDecoupledTemplateLogicResolver可以TemplateEngine輕松配置要使用的具體實現:

final StandardDecoupledTemplateLogicResolver decoupledresolver = 
        new StandardDecoupledTemplateLogicResolver();
decoupledResolver.setPrefix("../viewlogic/");
...
templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);

18 附錄A:表達式基本對象

某些對象和變量映射始終可被調用。讓我們看看他們:

基礎對象

  • #ctx:上下文對象。一種實現org.thymeleaf.context.IContext或org.thymeleaf.context.IWebContext取決於我們的環境(獨立或網絡)。 注意#vars和#root是同一個對象的同義字,但#ctx建議使用。
/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.context.IContext
 * ======================================================================
 */

${#ctx.locale}
${#ctx.variableNames}

/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.context.IWebContext
 * ======================================================================
 */

${#ctx.request}
${#ctx.response}
${#ctx.session}
${#ctx.servletContext}
  • #locale:直接訪問java.util.Locale與當前請求關聯的內容。
${#locale}

請求/會話屬性等的Web上下文名稱空間

在Web環境中使用Thymeleaf時,我們可以使用一系列快捷方式來訪問請求參數,會話屬性和應用程序屬性:

請注意,這些不是上下文對象,而是作為變量添加到上下文中的映射,因此我們不使用即可訪問它們#。它們以某種方式充當命名空間。

  • param:用於檢索請求參數。\({param.foo}是String[]帶有foorequest參數值的a ,因此\){param.foo[0]}通常用於獲取第一個值。
/*
 * ============================================================================
 * See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap
 * ============================================================================
 */

${param.foo}              // Retrieves a String[] with the values of request parameter 'foo'
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
...
  • session:用於獲取會話屬性。
/*
 * ======================================================================
 * See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap
 * ======================================================================
 */

${session.foo}                 // Retrieves the session atttribute 'foo'
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}
...
  • application:用於檢索應用程序/ servlet上下文屬性。
/*
 * =============================================================================
 * See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap
 * =============================================================================
 */

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

請注意,無需指定用於訪問請求屬性的名稱空間(與request參數相反),因為所有請求屬性都作為變量自動添加到上下文根目錄中的上下文中:

${myRequestAttribute}

Web上下文對象

在Web環境中,還可以直接訪問以下對象(請注意,這些是對象,而不是映射/命名空間):

  • #request:直接訪問javax.servlet.http.HttpServletRequest與當前請求關聯的對象。
${#request.getAttribute('foo')}
${#request.getParameter('foo')}
${#request.getContextPath()}
${#request.getRequestName()}
...
  • #session:直接訪問javax.servlet.http.HttpSession與當前請求關聯的對象。
${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}
...
  • #servletContext:直接訪問javax.servlet.ServletContext與當前請求關聯的對象。
${#servletContext.getAttribute('foo')}
${#servletContext.contextPath}
...

19 附錄B:Expression Utility對象

Execution Info

#execInfo:表達式對象,提供有關Thymeleaf標准表達式中正在處理的模板的有用信息。 #execInfo : expression object providing useful information about the template being processed inside Thymeleaf Standard Expressions.

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

/*
 * Return the name and mode of the 'leaf' template. This means the template
 * from where the events being processed were parsed. So if this piece of
 * code is not in the root template "A" but on a fragment being inserted
 * into "A" from another template called "B", this will return "B" as a
 * name, and B's mode as template mode.
 */
${#execInfo.templateName}
${#execInfo.templateMode}

/*
 * Return the name and mode of the 'root' template. This means the template
 * that the template engine was originally asked to process. So if this
 * piece of code is not in the root template "A" but on a fragment being
 * inserted into "A" from another template called "B", this will still 
 * return "A" and A's template mode.
 */
${#execInfo.processedTemplateName}
${#execInfo.processedTemplateMode}

/*
 * Return the stacks (actually, List<String> or List<TemplateMode>) of
 * templates being processed. The first element will be the 
 * 'processedTemplate' (the root one), the last one will be the 'leaf'
 * template, and in the middle all the fragments inserted in nested
 * manner to reach the leaf from the root will appear.
 */
${#execInfo.templateNames}
${#execInfo.templateModes}

/*
 * Return the stack of templates being processed similarly (and in the
 * same order) to 'templateNames' and 'templateModes', but returning
 * a List<TemplateData> with the full template metadata.
 */
${#execInfo.templateStack}

Messages

#messages:實用程序方法,用於獲取變量表達式內的外部化消息,其方式與使用#語法獲得消息的方式相同。 #messages : utility methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using # syntax.

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

URI / URL

#uris:在Thymeleaf標准表達式內執行URI / URL操作(尤其是轉義/轉義)的實用程序對象。 #uris : utility object for performing URI/URL operations ( esp. escaping/unescaping) inside Thymeleaf Standard Expressions.

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

/*
 * Escape/Unescape as a URI/URL path
 */
${#uris.escapePath(uri)}
${#uris.escapePath(uri, encoding)}
${#uris.unescapePath(uri)}
${#uris.unescapePath(uri, encoding)}

/*
 * Escape/Unescape as a URI/URL path segment (between '/' symbols)
 */
${#uris.escapePathSegment(uri)}
${#uris.escapePathSegment(uri, encoding)}
${#uris.unescapePathSegment(uri)}
${#uris.unescapePathSegment(uri, encoding)}

/*
 * Escape/Unescape as a Fragment Identifier (#frag)
 */
${#uris.escapeFragmentId(uri)}
${#uris.escapeFragmentId(uri, encoding)}
${#uris.unescapeFragmentId(uri)}
${#uris.unescapeFragmentId(uri, encoding)}

/*
 * Escape/Unescape as a Query Parameter (?var=value)
 */
${#uris.escapeQueryParam(uri)}
${#uris.escapeQueryParam(uri, encoding)}
${#uris.unescapeQueryParam(uri)}
${#uris.unescapeQueryParam(uri, encoding)}

Conversions

#conversions:實用程序對象,允許在模板的任何位置執行轉換服務: #conversions : utility object that allows the execution of the Conversion Service at any point of a template:

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

/*
 * Execute the desired conversion of the 'object' value into the
 * specified class.
 */
${#conversions.convert(object, 'java.util.TimeZone')}
${#conversions.convert(object, targetClass)}

Dates

#dates:java.util.Date對象的實用程序方法: #dates : utility methods for java.util.Date objects:

/*
 * ======================================================================
 * 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

#calendars:類似於#dates,但對於java.util.Calendar對象: #calendars : analogous to #dates, but for java.util.Calendar objects:

/*
 * ======================================================================
 * 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

#numbers:用於數字對象的實用方法: #numbers : utility methods for number objects:

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


/* 
 * =====================
 * Formatting currencies
 * =====================
 */

${#numbers.formatCurrency(num)}
${#numbers.arrayFormatCurrency(numArray)}
${#numbers.listFormatCurrency(numList)}
${#numbers.setFormatCurrency(numSet)}


/* 
 * ======================
 * Formatting percentages
 * ======================
 */

${#numbers.formatPercent(num)}
${#numbers.arrayFormatPercent(numArray)}
${#numbers.listFormatPercent(numList)}
${#numbers.setFormatPercent(numSet)}

/* 
 * Set minimum integer digits and (exact) decimal digits.
 */
${#numbers.formatPercent(num, 3, 2)}
${#numbers.arrayFormatPercent(numArray, 3, 2)}
${#numbers.listFormatPercent(numList, 3, 2)}
${#numbers.setFormatPercent(numSet, 3, 2)}


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

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

Strings

#strings:String對象的實用方法: #strings : utility methods for String objects:

/*
 * ======================================================================
 * 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

#objects:一般對象的實用方法 #objects : utility methods for objects in general

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

Booleans

#bools:用於布爾值評估的實用方法 #bools : utility methods for boolean evaluation

/*
 * ======================================================================
 * 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

#arrays:數組的實用方法 #arrays : utility methods for 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

#lists:清單的實用方法 #lists : utility methods for 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

#sets:集合的實用方法 #sets : utility methods for 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)}

Maps

#maps : utility methods for 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 : utility methods for creating aggregates on arrays or collections

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

IDs

#ids : utility methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

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

20 附錄C:標記選擇器語法

Thymeleaf的標記選擇器直接從Thymeleaf的解析庫AttoParser借用

該選擇器的語法與XPath,CSS和jQuery中的選擇器的語法有很大相似之處,這使它們對於大多數用戶而言易於使用。您可以在AttoParser文檔中查看完整的語法參考。

例如,以下選擇器將在標記內的每個位置選擇

class content的每個對象(請注意,這樣做並不那么簡潔,請繼續閱讀以了解原因):

<div th:insert="mytemplate :: //div[@class='content']">...</div>

基本語法包括:

  • /x 表示名稱為x的當前節點的直接子代。
  • //x 表示任意深度的名稱為x的當前節點的子代。
  • x[@z="v"] 表示名稱為x的元素和名為z的屬性,其值為“ v”。
  • x[@z1="v1" and @z2="v2"] 表示具有名稱x的元素以及具有值“ v1”和“ v2”的屬性z1和z2。
  • x[i] 表示名稱x處於其兄弟姐妹中的第i個元素。
  • x[@z="v"][i] 表示元素名稱為x,屬性z的值為“ v”,並且在與該條件匹配的同級元素中位於第i個位置。

但是也可以使用更簡潔的語法:

  • x完全等同於//x(x在任何深度級別搜索具有名稱或引用的元素,引用是a th:ref或th:fragment屬性)。
  • 選擇器也可以不帶元素名稱/引用,只要它們包含參數說明即可。因此[@class='oneclass'],一個有效的選擇器將查找具有value的class屬性的任何元素(標簽)"oneclass"。

高級屬性選擇功能:

  • 除=(等於)外,其他比較運算符也有效:(!=不等於),=(以開頭)和$=(以結束)。例如: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>具有th:ref="oneref"或th:fragment="oneref"屬性的標簽。
  • %oneref表示任何具有th:ref="oneref"或th:fragment="oneref"屬性的標簽。請注意,這實際上等效於簡單的oneref原因,因為可以使用引用代替元素名稱。
  • 直接選擇器和屬性選擇器可以混合使用:a.external[@href^='https']。

因此,上面的標記選擇器表達式:

<div th:insert="mytemplate :: //div[@class='content']">...</div>

可以寫成:

<div th:insert="mytemplate :: div.content">...</div>

檢查另一個示例,這是:

<div th:replace="mytemplate :: myfrag">...</div>

將尋找th:fragment="myfrag"片段簽名(或th:ref引用)。但是,myfrag如果存在則還會搜索帶有名稱的標簽(在HTML中不存在)。注意與以下內容的區別:

<div th:replace="mytemplate :: .myfrag">...</div>

…實際上將查找帶有的任何元素class="myfrag",而無需關心th:fragment簽名(或th:ref引用)。

多值類匹配

標記選擇器了解要多值化的類屬性,因此即使元素具有多個class值,也可以在該屬性上應用選擇器。

例如,div.two將匹配<div class="one two three" ></div>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM