Thymeleaf3.0內容

Thymeleaf簡介

什麼是Thymeleaf

轉自:http://www.cnblogs.com/jiangchao226/p/5900222.htmljavascript

Thymeleaf是網站或者獨立應用程序的新式的服務端java模板引擎,能夠執行HTML,XML,JavaScript,CSS甚至純文本模板。css

Thymeleaf的主要目標是提供一個以優雅的高可維護型的方式建立模板,爲了達到這個目的,他創建了天然模板的概念,以一種不影響模板設計原型的方式將邏輯注入到模板文件中,能夠顯著的減小設計和開發之間的溝通成本。html

在須要的狀況下,Thymeleaf能夠建立一個各類校驗模式的模板(尤爲是基於HTML5)html5

什麼樣的模板可使用Thymeleaf處理

Thymeleaf 容許使用六種模板,每一個被稱爲一種模板模式:java

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

這其中有兩種標記語言模板模式(HTML,XML),三種文本語言模板模式(TEXT,JAVASCRIPT,CSS),和一種無操做模板模式(RAW)jquery

其中:
HTML模板模式可使用任何HTML輸入,包括HTML5,HTML4或XHTML,全驗證或無驗證都將能夠被執行,模板中的代碼和結構將被盡最大可能性的解析輸出。git

XML模板模式將被用來執行XML的輸入,在這種模式下,若是被要求嚴格驗證,則好比沒有閉合標籤,屬性沒有引號等,都將輸出異常,注意一點是,無驗證(經過DTD或XMLSchema)仍然是能夠執行的。github

文本模板模式將可使用一種非標記性的特殊語法。它多是一個電子郵件的模板或者文本模板等模板文件。注意:HTML模式或XML模式也可使用文本模板模式處理,但在這種狀況下,他們不會被解析爲標記語言,每一個標記,DOCTYPE,comment等都將視爲單純的文本。web

JavaScript模板模式可使用Thymeleaf處理Js文件。這意味着它可以在JS文件中以HTML文件一樣的方式使用數據模型。但它還整合了一些JS的專有屬性,如一些特定編碼和腳本語言。JavaScript模板是一種支持特殊語法的文本模板模式。算法

CSS模板模式和JavaScript模板模式相似,便可以使用Thymeleaf處理CSS文件,它也是用支持特殊語法的文本模板模式。

RAW模板模式是一種非執行模板,他用了處理一些保持原樣的資源(如文件,某Url的響應等),好比,在一些格式以外,可能會有一些信息須要原封不動的展示出來,這些應該明確的告知Thymeleaf不要講它們進行執行。

方言:標準方言

Thymeleaf 是一種可擴展的模板引擎(其實應該稱呼爲模板引擎框架),它容許您自由的定義製做一個能夠精確到任何程度的模板。

一個對象,運用一些邏輯(如標籤,文本,註釋,或僅僅是一個佔位符),被稱爲處理器,這些處理器加一些額外的東西,被稱爲方言。其中Thymeleaf的核心庫提供了一個一目瞭然的方言被稱爲標準方言,他應該可以知足大多數用戶的需求。

應明確一點就是,方言實際上能夠是沒有處理器,徹底有其餘的類型的東西註冊,但處理器是一種最多見的使用方式。

本教程即便用標準方言,你能夠在下面頁中瞭解這個方言的每個屬性和語法功能的定義,即便他沒有明確說起。

固然,若是用戶想要定義本身的處理邏輯,和使用庫中的各類先進功能,那麼,能夠定義本身的方言(甚至擴展標準方言),而模板引擎能夠一次配置多種方言。

官方thymeleaf-spring3和thymeleaf-spring4集成包都定義了另外一種方言被稱爲「String標準方言」,大多數至關於標準方言,但有一小部分爲了適應Spring框架的某些功能(如利用Spring的EL表達式而不是Thymeleaf標準的OGNL)因此,若是你是一個Spring mvc用戶,請你不要在浪費時間抓緊學習,由於那你在這裏學習的全部東西,都講在你的Spring項目中使用

Thymeleaf標準方言能夠在任何模式的模板中使用,尤爲適合面向web的模板模式(如HTML5和XHTML),除了HTML5,他能夠支持和驗證一下的XHTML格式:XHTML 1.0 Transitional, XHTML 1.0 Strict, XHTML 1.0, 和 XHTML 1.1.

標準方言的大多數處理器都是屬性處理器。他們在瀏覽器處理HTML模板文件以前,他會簡單處理額外的屬性,如,在jsp中使用標籤庫能夠包括代碼段,但代碼段瀏覽器並不會直接顯示同樣:

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

Thymeleaf中,能夠實現一樣的功能:

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

這個會在瀏覽器中被正確的顯示,同時,也允許咱們指定一個默認值(可選)("niufennan"),當靜態文件在瀏覽器中打開的時候,顯示的是Thymeleaf使用模板中${user.name}的值對value中的值進行了替換的結果。

這將可使設計人員和開發人員使用幾乎相同的模板文件,而且可使用極少的工做,就使一個靜態文件轉換爲開發模板文件。有這樣能力的模板一般稱之爲天然模板

古泰虛擬商店

當前及將來示例代碼下載地點

商店站點

爲了更好的說明Thymeleaf的各類概念,教程所使用的Demo能夠從項目網站下載。

這個項目是一個假想的購物網站,將提供足夠的場景來演示Thymeleaf的各類不一樣的特色。

這個購物網站須要一個很是簡單的實體模型,產品(Products)銷售給客戶(Customers),同時建立訂單(Orders),而且,還須要咱們管理用戶對產品的評論(Coomments)

模型示意圖

一個簡單的服務層:

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 {
           
        //靜態資源不使用框架
        if(request.getRequestURI().startsWith("/css")||
                request.getRequestURI().startsWith("/images")||
                request.getRequestURI().startsWith("/favicon")
                )
        {
            return false;
        }
        /*
         * 根據controller/url的映射規則,獲取將要執行的控制權的request
         * 若是沒有控制權可用,返回false,讓其餘的filter或者servlet來執行
         */
        IGTVGController controller = application.getTemplateEngine().resolveControllerForRequest(request);
        if (controller == null) {
            return false;
        }
        /*
         * 獲取 TemplateEngine 實例.
         */
        ITemplateEngine templateEngine = application.getTemplateEngine();
            
        /*
         * 寫 response headers
         */
        response.setContentType("text/html;charset=UTF-8");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        /*
         * 執行控制權並處理模板
         * 將結果寫入response
         */
        controller.process(
                request, response, this.servletContext, templateEngine);

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

還有IGTVGController 接口

public interface IGTVGController {
    public void process(
            HttpServletRequest request, HttpServletResponse response,
            ServletContext servletContext, ITemplateEngine templateEngine) throws Exception;    
}

如今要作的就是IGTVGController接口的實現,主要實現了從服務中檢索數據和處理模板使用的ITemplateEngine 對象

最後,他的效果圖以下
效果圖
可是,首先讓咱們看看模板引擎是如何初始化的。

建立而且配置一個模板引擎

在過濾器process方法中加入以下的代碼:

ITemplateEngine templateEngine = application.getTemplateEngine();

這意味着GTVGApplication類中在加載的時候建立和配置了Thymeleaf啓動應用的最重要對象:模板引擎實例(ITemplateEngine接口的實現)

咱們的org.thymeleaf.TemplateEngine對象的初始化就像是這樣:

public class GTVGApplication {


    ...
    private static TemplateEngine templateEngine;
    ...
    
    public GTVGApplication(ServletContext servletContext) {
        
        ServletContextTemplateResolver templateResolver = 
            new ServletContextTemplateResolver(servletContext);
        //有String類型重載
        templateResolver.setTemplateMode(TemplateMode.HTML);
        // 這段將把 "home" 轉換爲 "/WEB-INF/templates/home.html"
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        //模板的緩存的生存時間默認爲1小時,若是沒有設置,將被緩存到LRU(緩存淘汰算法)中
        templateResolver.setCacheTTLMs(3600000L);
        
        //緩存默認爲true,若但願修改模板後自動更改(如調試時),可設置爲false
        templateResolver.setCacheable(true);
        
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);

    }
    
    ...

}

固然,配置TemplateEngine對象的方式有不少,但這幾行代碼將讓咱們學會足夠的必要步驟。

模板解釋器

從模板解釋器開始:

ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();

模板解釋器對象從以下接口實現:

org.thymeleaf.templateresolver.ITemplateResolver:

public interface ITemplateResolver {

    ...
  
    /*
     * 模板經過名字或內容來解析。星期若是他有主模板的狀況下還將嘗試將此解析爲另外一個模板的片斷
     * 若是這個模板不能經過解析,將返回null.
     */
    public TemplateResolution resolveTemplate(
        final IEngineConfiguration configuration,
        final String ownerTemplate, final String template,
        final Map<String, Object> templateResolutionAttributes);

}

有這些解釋器對象決定咱們的模板在GTVG應用中將被如何訪問,並在org.thymeleaf.templateresolver.ServletContextTemplateResolver中實現,咱們將在Servlet的上下文中檢索制定的模板文件資源。Servlet上下文指的是javax.servlet.ServletContext對象,他普遍的在每個java web應用中存在。並將使用web應用程序的根做爲資源文件的根路徑。

但這不能說全部的模板都如此解析,由於咱們能夠給他設置一些配置參數。首先,模板模式中,其中一個標準爲:

templateResolver.setTemplateMode("HTML");

HTML是ServletContextTemplateResolver的默認模板模式,
但出於代碼可讀性仍是顯示設置。

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

顧名思義,這是模板路徑的前綴和後綴,經過模板的名稱,能夠將真實的路徑傳遞到引擎來使用它。

使用這個配置:模板名稱"product/list"的路徑將爲:

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

經過配置爲cachtTTLMS屬性配置一個總的時間量,能夠配置模板解釋器在緩存中存活的時間:

templateResolver.setCacheTTLMs(3600000L);

固然,若是緩存達到了最大緩存,而且此模板是最先的緩存條目,那麼在達到TTL以前,他仍是有可能被清理出緩存的。

緩存的行爲和大小,均可以由用戶本身定義,既能夠實現ICacheManager接口本身實現規則,也能夠經過修改StandardCacheManager對象設置緩存默認管理規則。

咱們將在之後學習模板解釋器,如今讓咱們來看看模板引擎對象。

模板引擎

模板引擎對象是接口org.thymeleaf.ITemplateENgine的實現,Themeleaf的core包提供一個默認的實現:org.thymeleaf.TemplateEngine,下邊是建立引擎的一個例子:

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

很簡單,是吧,咱們所須要的僅僅是建立一個實例並設置一個模板解釋器。

一個模板解釋器是一個TemplateEngine必須的參數,固然,還須要其餘的參數,將在後邊介紹(如消息解釋器,緩存大小等),如今,這些已經知足咱們須要了。

咱們的模板引擎如今已經能夠啓動,並可使用Thymeleaf建立咱們的頁面了。

使用文本

多語言歡迎頁

咱們的第一個任務是爲咱們的商店網站建立一個首頁。

第一個版本,將是一個很是簡單的頁面:僅僅有個title和一個歡迎信息。在/WEB-INF/template/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}">歡迎您光臨本店!</p>
  
  </body>

</html>

首先你會注意到,這個文件是HTML5標準的DOCTYPE格式,他能夠在主流瀏覽器中正確的顯示(瀏覽器會忽略掉一下他不認識的屬性,如th:text)。

可是,你也可能注意到,這個模板並非一個徹底有效的HTML5文檔,由於有HTML5規格中沒有的非標準屬性,如th:*屬性,事實上,咱們添加了一個xmlns:th屬性在html標籤,用來分類非html5的標記或屬性。

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

那麼,如何使用一個徹底符合HTML5標準的模板呢?也很容易,使用Thymeleaf數據屬性語法便可,它使用data-前綴的方式來使用:

<!DOCTYPE html>

<html>

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

  <body>
  
    <p data-th-text="#{home.welcome}">歡迎您光臨本店!</p>
  
  </body>

</html>

自定義的data-前綴屬性是html5徹底支持的語法,這樣,上面這段代碼就是一個徹底符合HTML5定義的文檔

這兩種語法是徹底等價的,能夠無縫替換,但爲了使代碼更爲簡潔,本教程使用命名空間符號(th:),另外,th:語法是更經常使用的語法,在每一種Thymeleaf模板模式都是可使用,但data-這種方式只能在HTML模式中使用。

使用th:text和外部文本

外部文本一般是模板文件中須要替換的代碼,一般保存在外部的獨立文件(一般是特定的.properties文件)中。他們能夠很容易的設置其餘語言的等效文本(這一過程一般稱爲國際化)。這些外在的文本片斷一般被稱爲"messages"

Messages須要一個惟一性的Key進行標識,Thymeleaf容許你經過指定一個#{...}語法格式的Message對應一個具體的文本。如:

<p th:text="#{home.welcome}">歡迎您光臨本店!</p>

咱們能夠看到Thymeleaf標準方言的兩個主要的語言點:

  • th:text屬性,他聲明設置表達式的值,並使表達式返回的值來填充標籤內容,替換或設置標籤內部的內容,當前例子中即替換「歡迎光臨本店」這些字。

  • #{home.welcome}表達式,一個標準的表達式語法,指出在模板中,th:text屬性說對應Message的key,即便用home.welcome對應的value替換現有內容。

如今,對應的外部文本在哪?

在Thymeleaf中這些文件的位置都是可配的,他經過實現org.thymeleaf.messageresolver.IMessageResolver接口進行配置,一般會實現一個基於.properties文件的實現,固然也一樣能夠根據實際狀況進行自定義實現,如將Message存儲在數據庫中。

若是咱們在模板引擎初始化的時候沒有配置任何消息解釋器的實現,那就意味着咱們使用的是默認的一個消息解析實現,實現類爲:org.thymeleaf.messageresolver.StandardMessageResolver.

這個消息解釋器會掃描/WEB-INF/template/home.html相同目錄下的相同名稱的.properties文件,好比:

  • /WEB-INF/templates/home_en.properties 英文
  • /WEB-INF/templates/home_fr.properties 法語
  • /WEB-INF/templates/home_jp.properties 日語
  • /WEB-INF/templates/home.properties 默認

其中的一個properties文件以下(en):

home.welcome=Welcome to our grocery store!

咱們已經完成了咱們所須要的Thymeleaf框架的模板,接下來建立Home的Controller。

上下文

爲了處理模板,咱們將建立一個實現了以前看到的IGTVTController接口的HomeController類:

public class HomeController implements IGTVGController {
    @Override
    public void process(HttpServletRequest request,
            HttpServletResponse response, 
            ServletContext servletContext,
            TemplateEngine templateEngine) throws Exception  {
        // TODO Auto-generated method stub
        WebContext ctx=new WebContext(request,response,servletContext,
                request.getLocale());
        templateEngine.process("home", ctx,response.getWriter());
        
    }
}

能夠看到,第一件事就是建立了一個context,一個實現了org.thymeleaf.context.IContext接口的Thymeleaf上下文。上下文應該包含了模板引擎所執行全部所需數據的變量的map,而且引用了Locale所需的外部文件。

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

而且擴展了一個專用接口,org.thymeleaf.context.IWebContext,用於基於ServletApi的應用使用,如SpringMVC:

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

Thymeleaf核心庫爲每一個接口給出了一個實現:

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

正如在controller的代碼中看到的那樣,咱們使用了WebContext,事實上咱們也必須使用它,由於這是ServletContextTemplateResolver必須使用一個IWebContext的實現類。

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

這個構造函數的四個參數中有三個是必須的,第四個參數系統能夠設置一個默認的本地區域,若是沒有設置,則使用系統默認。

從WebContext還提供了一些專用的表達式,能夠在模板中得到request的參數,request,session,application的屬性等,好比:

  • ${x}:返回一個Thymeleaf的context中存儲的變量或request的屬性。
  • ${param.x} 返回request的參數x(能夠爲複合值)
  • ${session.x} 返回session的屬性x
  • ${application.x} 返回一個servlet上下文的屬性x

就在執行前,一個特殊的變量被設置爲包含了Context和WebContext的全context對象(即全部實現了IContext的對象),被稱爲執行信息(execInfo),這個變量有兩個您能夠從模板中使用的數據。

  • 模板名:#{execInfo.templateName},一個引擎執行時的特定名稱,對應正在執行的模板。
  • 當前的日期時間:#{execInfo.now},一個Calender對象,對應引擎開始執行的時刻值。

執行模板引擎

隨着context對象已經準備好,剩下只須要指定模板名稱和context,以執行模板引擎,兵傳遞給response的writer,以寫入響應。

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

看看執行結果(圖):

更多的文本和變量

非轉義文本

一個最簡版的Home頁面彷佛已經準備好了,可是,若是有這樣的需求怎麼辦呢:

home.welcome=歡迎光臨這個<b>超級棒</b>的商店!

若是是這樣的,輸出結果爲:

home.welcome=歡迎光臨這個&lt;b&gt;超級棒 &lt;/b&gt;的商店!

很顯熱,這並非我但願看見的,咱們但願b做爲一個標籤來處理,而不是轉義後顯示在瀏覽器上。

這是瀏覽器對th:text的默認行爲。若是咱們但願Thymeleaf直接使用html標籤,而不是轉義他們,咱們可使用不一樣的屬性th:utext("unescaped text")

<p th:utext="#{home.welcome}">歡迎您光臨本店!</p>
這樣輸出信息就是所須要的了:
<p>歡迎光臨這個<b>超級棒</b>的商店!</p>

使用和顯示變量

若是我想在首頁多現實一下信息,好比顯示日期,就像下邊這樣:

歡迎這個超級棒的網店

當前日期爲:2016-9-2

那麼首先,咱們應該回到首頁,添加當前時間到context變量:

WebContext ctx=new WebContext(request,response,servletContext,
            request.getLocale());
//時間
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
ctx.setVariable("today",sdf.format(cal.getTime()));
templateEngine.process("home", ctx,response.getWriter());

咱們已經添加了一個String變量在context中,如今修改模板:

<p th:utext="#{home.welcome}">歡迎您光臨本店!</p>

<p>當前日期爲: <span th:text="${today}">2016年9月5日</span></p>

能夠看到,咱們仍然使用th:text屬性,可是語法稍有不一樣,此次使用的是${...}而不是#{...},這是一個用來顯示變量值的表達式。它使用OGNL語言來映射context中的變量。

todaytodaytoday表達式只是簡單的表示爲:獲取一個叫today的變量,可是這個表達式也能夠變得很複雜,如{user.name}的意思是獲取一個user變量,並調用他的getName()方法。

屬性值有至關多的可能性,如:消息,變量表達式等等,下一章將展現這些都是什麼.

標準表達式語法

在將這個小demo發展成一個真正的網店以前,先休息一下,熟悉一下Thymeleaf標準方言中最重要的組成部分:Thymeleaf標準表達式語法。

在以前,已經看到過兩種有效的屬性表達式:消息和變量表達式:

<p th:utext="#{home.welcome}">歡迎您光臨本店!</p>

<p>當前日期爲: <span th:text="${today}">2016年9月5日</span></p>

但還有更多的類型和有趣的細節,咱們都還不知道,下面咱們首先來一個標準表達式的快速總結:

  • 簡單表達式
    • 變量表達式:${...}
    • 選定變量表達式:*{...}
    • 信息表達式:#{...}
    • 連接表達式:@{...}
    • 片斷表達式:~{...}
  • 字面值
    • 文本值:'one text','Another one!',...
    • 數值:1,34,3.0,12.3...
    • 布爾值:true,false
    • Null值:null
    • 標記符號值(token):one,sometext,main,...(?)
  • 操做符
    • 字符串鏈接:+
    • 文本替換:|The name is ${name}|
  • 算數運算符:
    • 基本操做符:+,-,*,/,%
    • 取負值(一元運算符):-
  • boolean運算符:
    • 基本二元操做符:and,or
    • 一元操做符:!,not
  • 比較和判斷:
    • 比較運算符:>,<,>=,<=,(gt,le,ge,le)
    • 判斷相等運算符:==,!=(eq,ne)
  • 條件運算符
    • if-then:(if)?(then)
    • if-then-else:(if)?(then):(else)
    • default:(value)?:(defaultvalue)
  • 特殊標記
    • 無操做:_

全部這些還能夠合併嵌套使用:

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

信息

咱們已經知道,消息表達式許可咱們將:

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

鏈接到資源,將內容轉換爲:

home.welcome=歡迎您光臨本店!

可是,若是文本內容不是徹底靜態的怎麼辦?例如,咱們想實如今已知某位用訪問本站的時候(登陸後),在頁面顯示他的名字:

<p>張三,您好!歡迎您光臨本店!</p>

這意味着咱們須要一個參數:

home.welcome={0},您好!歡迎您光臨本店!

參數是根據java.text.MessageFormat標準語法指定,意味着你能夠添加數字,日期等api指定的格式。

爲了給參數賦值,能夠給一個session的屬性爲用戶:

<p th:utext="#{home.welcome(${session.user.name})}">
    張三,您好!歡迎您光臨本店!
</p>

若是須要,能夠指定多個參數,用逗號分隔,事實上,消息的key自己也能夠是一個變量:

<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
   張三,您好!歡迎您光臨本店!
</p>

變量

咱們已經知道${...}表達式其實是OGNL表達式在執行context中映射的變量。

更多關於OGNL語法和功能的詳細信息,能夠閱讀http://commons.apache.org/ognl/

在SpringMVC的應用中,OGNL將被SpringEL替代,但兩種語法很是類似,經常使用的語法徹底相同。

經過OGNL定義,咱們能夠指定

<p>當前日期爲:: <span th:text="${today}">2016-8-13</span>.</p>

事實上,是這樣實現的:

ctx.getVariables("today");

OGNL也容許咱們建立更強大的表達式,好比說這樣:

<p th:utext="#{home.welcome(${session.user.name})}">
   張三,您好!歡迎您光臨本店!
</p>

實際上他是這樣執行的:

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

經過getter方法導航是OGNL語法的一大特色:下面演示更多用法:

//訪問屬性使用(.)進行訪問,至關於調用getter方法
${person.father.name}

//訪問屬性也能夠經過方括號[]進行訪問,屬性名做爲一個變量經過單引號(')或雙引號(")寫在方括號中
${person['father']['name']}

//若是對象是一個map,能夠用引號和方括號的語法將至關於直營一個調用它的key獲取對象的get方法。
${countriesByCode.ES}
${personsByName['張三'].age}

//對於數組或集合的索引也用方括號來執行
${personsArray[0].name}

//能夠調用各類方法,不管是否有參數
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}

基本對象表達式

當使用OGNL表達式的context變量的時候,可使用更方便的表達方式。這些對象也是用來#符號:

  • #ctx: context對象
  • #vars:context屬性
  • #locale:context本地化信息
  • #request:(僅限WebContext) HttpServletRequest對象
  • #response:(僅限WebContext) HttpServletResponse對象
  • #session:(僅限WebContext)HttpSession對象
  • #servletContext:(僅限WebContext)ServletContext對象

因此咱們能夠這樣使用

這裏是:<span th:text="${#locale.country}">中國</span>.

你能夠在附錄1中查看這些對象的所有參考

工具對象表達式

除了基本對象,Thymeleaf還爲咱們提供了一套實用對象,能夠幫助咱們在執行表達式中解決一下常見的任務:

  • #execInfo:正在處理的模板信息
  • #messages:獲取外部信息的內部變量的一個實用方法,同時也能夠用#{...}獲取
  • #uris:針對URL或URI進行一些轉碼的方法
  • #conversions:根據配置執行一些轉換方法
  • #dates:針對java.util.Date對象的實用方法:包括日期格式化,日期提取等。
  • #calendars:與#dates類似,但針對的是java.util.Calendar對象。
  • #numbers:針對numeric對象格式化的實用方法
  • #strings:針對String對象的實用方法,包括包含,判斷起始,前/後追加等方法
  • #objects:針對object類的通常實用方法
  • #boolean:針對boolean運算的一些實用方法
  • #arrays:針對數組的實用方法
  • #lists:針對list的實用方法
  • #sets:針對set的實用方法
  • #map:針對map的實用方法
  • #aggregates:在數組或集合中建立聚合的一些實用方法
  • #ids:用於處理可能重複的標識屬性的使用方法,例如,做爲迭代的變量。

    你能夠在附錄2中查看這些工具對象的所有參考

在首頁中使用格式化日期

如今,咱們知道了這些工具對象表達式,就能夠改變首頁中顯示日期的方式,首先,改變咱們HomeController中的代碼,將:

SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
ctx.setVariable("today",sdf.format(cal.getTime()));

這三行能夠合併爲1行

ctx.setVariable("today",Calendar.getInstance());

而後,修改模板文件的相應行爲:

<p>
  當前日期爲: <span th:text="${#calendars.format(today,'yyyy-MM-dd')}">2016年9月5日</span>
</p>

選定變量表達式(或使用{...}) ###
變量表達式不但可使用${...}表達式,同時也可使用
{...}表達式。

他們有個最重要的區別:*{...}表達式的值是在選定的對象而不是整個context的map。也就是說,若是沒有選定的對象,*{...}和${...}沒有區別

那麼問題來了:什麼是選定對象?一個th:object對象屬性,使用用戶權限頁面來演示一下:

<div th:object="${session.user}">
    <p>姓名:<span th:text="*{firstName}">張三</span></p>
    <p>年齡:<span th:text="*{age}">26</span></p>
    <p>國籍:<span th:text="*{nationlity}">中國</span></p>
</div>

這至關於:

<div>
    <p>姓名:<span th:text="${session.user.firstName}">張三</span></p>
    <p>年齡:<span th:text="${session.user.age}">26</span></p>
    <p>國籍:<span th:text="${session.user.nationlity}">中國</span></p>
</div>

固然,也能夠混合使用

<div th:object="${session.user}">
    <p>姓名:<span th:text="*{firstName}">張三</span></p>
    <p>年齡:<span th:text="${session.user.age}">26</span></p>
    <p>國籍:<span th:text="*{nationlity}">中國</span></p>
</div>

當一個使用選定對象的地方,選定的對象其實就是使用了#object表達式的${...}表達式

<div th:object="${session.user}">
    <p>姓名:<span th:text="${#object.firstName}">張三</span></p>
    <p>年齡:<span th:text="${session.user.age}">26</span></p>
    <p>國籍:<span th:text="${#object.nationlity}">中國</span></p>
</div>

也就是說,若是沒有已經完成的選定對象,那麼,*{...}和${...}兩種表達式是徹底等價的。

<div>
    <p>姓名:<span th:text="*{session.user.firstName}">張三</span></p>
    <p>年齡:<span th:text="*{session.user.age}">26</span></p>
    <p>國籍:<span th:text="*{session.user.nationlity}">中國</span></p>
</div>

Url連接

因爲其重要性,URL是web應用程序模板的一等公民,Thymeleaf的標準方言都爲它定義了特殊語法:@{...}

他有不一樣類型的網址:

  • 絕對地址,好比:https://niufennan.github.io/
  • 相對地址,能夠有以下方式:
    • 相對於頁的,好比:user/login.html
    • 相對於上下午的,好比:/itemdetails?d=3(將自動添加服務器的上下文名稱)
    • 相對於服務器的,好比:~/billing/processInvoice(能夠在同一服務器中的不一樣上下文(即application)中使用)
    • 相對於協議的,好比://cdn.bootcss.com/jquery/2.2.3/jquery.min.js

真正將這些表達式轉換並輸出爲URL的工具,是一個註冊在ITemplateEngine中的一個org.thymeleaf.linkbuilder.ILinkBuilder接口的實現類。

Thymeleaf能夠在任何狀況下處理絕對地址,但相應的,也會要求你給予一個實現了IWebContext接口的context對象,他包含了一些建立連接須要的來自Http請求的相關信息。

在默認狀況下,註冊的實現是類org.thymeleaf.linkbuilder.StandardLinkBuilder,它對於基於Servlet API的網絡或離線應用已經足夠了,而其餘狀況(如非ServletAPI的web項目)則可能須要本身建立接口的實現。

舉例說明一下,需使用th:href屬性:

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

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

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

注意:

  • th:href是一個修飾屬性,一旦設置這個屬性,它會計算連接,並設置<a\>標籤內的href屬性的url值。
  • 能夠對url的參數使用表達式(好比orderId=${o.id}),url上所需的編碼工做,也會自動執行。
  • 若是須要多個參數,用逗號(,)分開便可如:(@{/order/process(execId=${execId},execType='FAST')})
  • 網絡路徑中也可使用變量模板,如:@{/order/{orderId}/details(orderId=${orderId})}
  • 相對URL使用/開始,如/order/details,將自動適應上下文的名詞前綴。
  • 若是不清楚cookie是否被啓用,一個jsessionid=...的後綴可能被加入到url中,用於回話保存,這就是所謂的URL重寫。Thymeleaf容許利用Servlet的api爲每個url,使用response.encoding來擴展重寫過濾器。
  • th:href容許有一個靜態工做的url配置在模板中,這樣,咱們的模板即便在不工做時,仍然能夠經過瀏覽器互相鏈接。

就像#{...}同樣,URL表達式中的值也能夠是另外一個表達式的結果:

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

爲首頁製做一個簡單菜單

如今已經知道了如何建立一個鏈接,是時候給首頁增長一個小菜單了,以告訴你們這個站點還有些什麼內容。

<p>請選擇:</p>
<ol>
  <li><a href="product/list.html" th:href="@{/product/list}">產品列表</a></li>
  <li><a href="order/list.html" th:href="@{/order/list}">訂單列表</a></li>
  <li><a href="subscribe.html" th:href="@{/subscribe}">訂閱新聞</a></li>
  <li><a href="userprofile.html" th:href="@{/userprofile}">用戶權限</a></li>
</ol>

相對於服務器根的URL

還有一個附加的語法,可用於指向一個服務器的根地址(而不是上下午的根地址),以便於指向同一服務器中的不一樣上下文。他的語法爲:@{~/path/to/something}

片斷

片斷表達式是用一種簡單的方式來標識一個片斷的標記,並能夠將他移動的其他模板,它容許咱們執行重寫模板,傳遞給其餘模板參數等操做。
作經常使用的片斷使用方式是插入使用,屬性值爲th:insert或th:replace(更多內容見後邊部分)

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

它能夠在任何地方使用,就像其餘變量同樣:

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

本教程稍後的部分有一篇完整的關於模板佈局的介紹,其中包含變量表達式更深層次的介紹。

字面值

文本值

字面值指的是包含在單引號之間字符,它可使任何字符,但應該儘可能避免使用'


如今你看到的是模板文件.

 

數值

顧名思義,數值顯示的就是一個數字:

<p>今年是<span th:text="2016">1942</span>.</p>
<p>兩年後,將是<span th:text="2013 + 2">1944</span>.</p>

布爾值

布爾值只有true和false兩個值,舉例:

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

注意,在這個例子中,==false是寫在了...使Thymeleaf...的外邊,因此使Thymeleaf自己在支持它,若是寫在了{...}的裏邊,則變爲由OGNL或SpringEL庫來支持它。

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

null值

null值能夠這樣使用

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

標記符號值(token)

實際上,數值,布爾值,null值都是一種特殊的token值。

這些token值容許爲標準表達式進行一點點的簡化,他們的工做方式和文本值徹底同樣,可是隻能使用字符(a-z,A-Z),數值(0-9),括號即[ ]和( ),
因此不能有空格,括號等。

而且,他能夠不被包含在單引號中:

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

而不是這樣(這樣爲文本值):

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

追加文本

一段文本,不管是一段字面值,變量的返回值,仍是信息表達式,均可以使用+來追加一段文本

<div th:text="'這我的的名字爲: ' + ${user.name}">

字面值替換

還能夠直接替換一段字面值中包含的字符串信息,而無需經過操做符+
這些替換必須被豎線(|)包圍,如:

<span th:text="|歡迎光臨這個應用, ${user.name}!|">

即至關於:

<span th:text="'歡迎光臨這個應用, ' + ${user.name} + '!'">

格式替換也能夠和其餘表達式相結合使用:

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

注意:只有變量表達式${...}能夠出如今|...|中,其餘的如布爾,數值或文字的token,條件表達式等都不可使用。

算數運算

在表達式中還可使用一些算數運算,如:+,-,*,/,%

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

注意:同布爾值的例子同樣,這個一樣能夠寫成使用OGNL或SpringEL支持的方式:

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

注意:有兩個運算符存在別名:div(/),mod(%)

比較和判斷

表達式中的值能夠經過>,<,>=,<=進行比較,也能夠經過==和!=操做符進行相等的判斷。注意在xml中使用的時候,不該該直接使用<,>,應當使用&lt
和&gt來代替

<div th:if="${prodStat.count} &gt; 1">
<div th:text="'執行模式爲 ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">

注意,這些都存在着別名:gt(>),lt(<),ge(>=),le(<=),not(!),eq(==),neq/ne(!=)

條件表達式

條件表達式爲根據條件(另外一個表達式)來選擇兩個表達式中的一個。

看一個示例:

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

條件表達式的全部的三個部分均在一個自表達式中,意味着他能夠用在變量${...},*{...},信息#{...},URL@{...}或字面量('...')中.

他還可使用()來進行嵌套

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

else部分能夠省略,若是條件爲否,則爲一個null值

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

默認表達式(Elvis運算符)

默認表達式是一種特殊的沒有then部分的條件表達式。在一些語言,如Groovy中,它至關於Elvis運算符,它容許有兩個表達式,第二個只有在第一個返回null的時候才執行。

修改一下用戶權限頁,如:

<div th:object="${session.user}">
  ...
  <p>年齡: <span th:text="*{age}?: '(沒有輸入)'">27</span>.</p>
</div>

正如看到的那樣,使用?:操做符,咱們在指定當年齡無效的時候,給定一個默認值(使用字面值),這條代碼至關於:

<p>年齡: <span th:text="*{age != null}? *{age} : '(沒有輸入)'">27</span>.</p>

和條件值同樣,他也可使用括號來實現嵌套:

<p>
  姓名: 
  <span th:text="*{name}?: (*{admin}? 'Admin' : #{default.username})">張三</span>
</p>

無操做標記

無操做標記是使用下劃線表示(_)

這背後是想指定一個標記來表達指望的結果是什麼也不作,也就是說,若是處理的屬性(如th:text)沒有值。

這將容許開發人員使用原型文本做爲默認值,如:

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

將能夠改成:

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

直接使用原型,可使使代碼更加靈活

日期的轉換與格式化

Thymeleaf可使用雙大括號的方式,爲變量${...}和選擇*{...}表達式經過配置轉換服務進行數據轉換。

它基本上是這樣的:

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

這個雙大括號${{}}通知Thymeleaf對user.createTime表達式的結果進行轉換服務,並在輸出結果以前進行格式化操做。

轉換服務器默認使用IStandardConversionService的實現類(StandardConversionService類),它只可以簡單的執行一個對象的toString(),即將一個對象轉爲字符串服務,有關注冊一個轉換服務的更多信息,能夠查看稍後更多配置部分。

在thymeleaf-spring3和thymeleaf-spring4有一整套基於Spring轉換服務的Thymeelaf轉換服務配置,所以他能夠自動實現${{}}和*{{}}的服務。

預處理

Thymeleaf表達式除了這些特徵,還爲咱們提供了預處理的功能。

預處理是指在正常的完成一個表達式以前執行的工做。他容許對最終執行的實際表達式進行修改.

一個預處理表達式和一個正常表達式幾乎同樣,但他在雙下劃線之間(__$(表達式)__)

好比,國際化的配置文件message_jp.properties的一個條目包含了一個OGNL表達式用於調用一個靜態方法:

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

以及另外一個等效的Message_en.properties:

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

對此,能夠先建立一個用於標記的表達式,這個表達式的值取決於區域設置,爲此,使用預處理,而後讓Thymeleaf去執行它:

<p th:text="${__#{article.text('textVar')}__}">一些文字...</p>

例如日本,上邊預處理與以下內容等效:

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

預處理中若有字符串__可使用\_\_來進行轉義。

設置屬性值

本章將介紹如何設置或修改標籤屬性的值,下一章將講解如何設置內容的值。

屬性值通用設置方式

咱們的網站會不按期發佈一些新聞,咱們但願咱們的用戶可以訂閱他,因此咱們建立一個/WEB-INF/templates/subscribe.html的表單模板:

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

這個看起來不錯,可是,這個更像是一個普通的html頁面,首先,這個表單的action是指向了模板文件自己,並無重寫URL,第二,提交按鈕的值,他是一個普通的文本,而咱們但願他是一個國際化的。

使用th:attr屬性,它具備設置或修改一個標籤屬性值的能力。

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

用法很簡單:th:attr將是一個值對應一個屬性的表達式,在轉換處理後,將會返回以下結果:

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

除了更新了屬性值,還能夠看到,應用的已經自動將url更新爲context前綴的url。

若是,咱們想在同時更新多個屬性呢?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" alt="這裏是logo" />

特定屬性值設定的方式

到了如今,你可能會發現,像:

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

這種,看起來是一個很是難看的模板。這種指定屬性值的賦值方式很明顯不是最優雅的一種建立方式模板。

事實也的確如此,這也就是爲何th:attr不多出如今實際應用的模板中,實際中常用的是th:*來實現標籤中特定的屬性。

在Thymeleaf的標準方言中,一般都是很直觀的方式,如設置一個按鈕的value值,使用th:value表達式,如:

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

看起來感受好多了,而後在看一下表單中的action屬性:

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

在以前home.html模板中的th:href,其實就在使用的這個語法:

<li><a href="product/list.html" th:href="@{/product/list}">產品列表</a></li>

Thymeleaf針對每個屬性都有一個特定的設置語法:



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

 

同時設置多個值

還有兩個比較特殊的屬性即th:alt-title和
th:lang-xmllang,他們能同時設置兩個屬性:

  • th:alt-title同時設置alt和title
  • th:lang-xmllang同時設置lang和xml:lang

否則修改首頁中剛剛的logo圖標籤:

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

追加和重寫

Thymeleaf還提供了th:attrappend和th:attrprepend屬性,用於爲屬性以前或以後增長值

好比,你可能想在一個按鈕的現有css類的基礎上在新增一個css類,將會很是容易:

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

若是你對cssStyle變量的值爲warning,那麼輸出將爲:

<input type="button" value="點擊" class="btn warning" />

由於樣式表使用的如此頻繁,因此標準方言中還有兩個附加屬性,th:classappend和th:styleappend屬性,用於追加一個class類或者一段樣式表而不改變現有內容:

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

each爲迭代屬性,稍後介紹。

擁有固定值的布爾屬性

在XHTML/HTML5中有些屬性是特殊的,它們要麼是一個固定值,要麼根本就不存在,好比:

<input type="checkbox" name="option1" checked="checked" />
<input type="checkbox" name="option2" />

在嚴格模式下,checked不能有其餘的值,相似的還有disabled,muliple,readonly,selected等。

在標準方言中,容許你經過一個條件值來設置這些屬性,若是值爲真,這些屬性將設置爲它的固定值,若是爲假,則不會設置此屬性。如:

<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

設置自定義屬性

除了剛剛看到的對於特定屬性的處理外,Thymeleaf還在標準方言中提供了一個默認屬性處理器,能夠設置任意自定義屬性的值,甚至能夠沒有具體的th:*處理器。
好比:

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

將返回

<span whatever="張三">...</span>

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-{前綴}-{名字}這種語法是一個html5的自定義屬性的標準方式。這種方式下,開發者無需導入命名空間,如th:,Thymeleaf將自動提供執行(全部方言模式,不只是標準方言)。

還有一種語法來指定自定義標籤:{前綴}-{名字},這裏遵循的是W3C標準的自定義標籤。這也是可使用,舉個例子,好比th:block就可使用th-block,這個將在稍後解釋。

注意:這個語法是th:*命名空間的一個補充,並非他的替換,在爲了跟本沒有棄用命名空間語法的想法。

迭代

到目前爲止,這個網絡商店已經有了一個主頁,有了一個用戶配置的頁面,還有了一個讓用戶訂閱咱們的通信頁,可是,咱們的產品呢?咱們是否是首先應該有一個產品列表,讓客戶知道咱們在賣什麼東東麼?嗯,顯然是的,讓咱們如今就走吧!

迭代基礎

在模板/WEB-INF/templates/product/list.html中要顯示產品列表,須要一個table。而後每一個產品在顯示上是table的一行,因此在咱們的模板中,就須要Thymeleaf來迭代每個產品。

在標準方言中,提供了一個迭代屬性,爲th:each

使用 th:each

對於產品列表頁,首先須要更改產品列表的控制器,讓其從服務層中獲取產品數據,然添加到模板的context中。

public void process(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext,
        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來迭代產品:

<h1>產品列表</h1>
<table>
    <tr>
        <th>產品名稱</th>
        <th>產品價格</th>
        <th>有現貨</th>
    </tr>
    <tr th:each="prod:${prods}">
        <td th:text="${prod.name}">土豆</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="@{/}">返回首頁</a>
</p>

**prod:prodsprods∗∗屬性值的意思是,迭代{prods}的每一個元素並重復這個模板的這個片斷。而後解釋一下這兩部分分別的意思:

  • ${prods}被稱爲迭表明達式或迭代變量
  • prod被稱爲重複變量或迭代值

注意:迭代值止只能夠用在tr節點上面(包括迭代裏邊包含的td標籤)。

可迭代的值

不是隻有java.util.List對象可使用Thymeleaf進行迭代。事實上,任何一個完整的對象集均可以使用th:each屬性:

  • 全部實現了java.util.Iterable接口的對象
  • 全部實現了java.util.Map接口的對象(此時的迭代值是java.util.Map.Entry類)
  • 全部數組
  • 任何對象都視爲一個只包含了它自己的單值的列表。

保持迭代狀態

當使用th:each的時候,Thymeleaf會提供一個跟着迭代狀態的機制:狀態變量。

狀態定義被封裝在th:each的屬性中。幷包含如下數據:

  • 獲取當前迭代的從0開始的下標,使用index屬性
  • 獲取當前迭代的從1開始的下標,使用count屬性
  • 獲取當前迭代元素的總量,使用size屬性
  • 獲取迭代變量中的迭代值,使用current屬性
  • 當前迭代值是奇數仍是偶數,使用even/odd的布爾值屬性
  • 當前的迭代值是否是第一個元素,使用first布爾值屬性
  • 當前迭代值是否是最後一個元素,使用last布爾值屬性。

如今修改一下前一個例子:

<h1>產品列表</h1>
<table>
    <tr>
        <th>產品名稱</th>
        <th>產品價格</th>
        <th>有現貨</th>
    </tr>
    <tr th:each="prod,iterStat:${prods}" th:class="${iterStat.odd}?'odd'">
        <td th:text="${prod.name}">土豆</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="@{/}">返回首頁</a>
</p>

能夠看到,狀態變量(即iterStat)的定義:將這個變量的名字做爲屬性寫在迭代值以後,用逗號於迭代值隔開。產生了迭代值以後,他的狀態值就能夠也僅僅能夠在th:each包含的代碼段中使用。

這段代碼的執行結果爲:

能夠看到,這端代碼執行的很是完美。而且只在奇數行添加了odd的css(行數從0開始)

若是沒有顯示的設置一個狀態變量,Thymeleaf會默認的建立一個使用迭代值+Stat後綴的迭代變量,如:

<table>
  <tr>
    <th>產品名稱</th>
    <th>產品價格</th>
    <th>有現貨</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>

經過懶檢索進行優化

有時咱們須要對只有檢索操做的數據集的檢索(如從數據庫查詢)進行優化。

事實上,他能夠應用於任何的數據塊,而檢索內存中可能要重複使用的集合多是最多見的狀況。

爲了可以支持這一點,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>

但若是爲下邊的代碼,條件爲false的時候也將不會被初始化(它的loadValue()方法將不會被調用):

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

條件判斷

if和unless

有時你可能想要,模板的某個片斷只有在條件被知足的時候纔出如今結果中。

好比,假設咱們要在產品表中顯示一個列,該列針對每一個產品的評論,若是有評論,則連接到評論頁面。

要實現這個功能,就要用到前面說到過的th:if屬性:

<table>
    <tr>
        <th>產品名稱</th>
        <th>產品價格</th>
        <th>有現貨</th>
        <th>用戶評價</th>
    </tr>
    <tr th:each="prod:${prods}">
        <td th:text="${prod.name}">土豆</td>
        <td th:text="${#numbers.formatDecimal(prod.price,0,2)}">2.41</td>
        <td th:text="${prod.isStock}?#{true}:#{false}">yes</td>
        <td>
            <span th:text=${#lists.size(prod.comments)}>2</span>個評價
            <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}"
            th:if="${not #lists.isEmpty(prod.comments)}">查看</a>
        </td>
    </tr>
</table>

重點注意下面這一行:

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

事實上,這段代碼沒什麼好解釋的,若是產品有評論,那麼咱們就建立一個跳轉到評論頁面的超連接,而且使用產品ID做爲參數。

查看生成結果:

Perfect!這正是咱們想要的!

th:if不光可使用布爾值,一下規則均可以:

  • 若是值不爲空:
    • 若是值爲布爾型而且爲true
    • 若是值爲數值型而且不爲0
    • 若是值爲character而且不爲0
    • 若是值爲String,而且不爲"false","off"和"no"
    • 若是值不爲布爾型,數值型,character或String的任意類型
  • 若是值爲null,th:if將爲false

th:if還有一個互逆的表達式爲th:unless,還繼續用以前的例子做一個演示:

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

switch

條件選擇還能夠像java同樣,使用switch來聲明:在Thymeleaf中使用th:switch和th:case來實現。
他的工做方式會和你想的同樣:

<div th:switch="${user.role}">
  <p th:case="'admin'">超級管理員用戶</p>
  <p th:case="#{roles.manager}">管理員用戶</p>
</div>

注意:一旦一個th:case被判斷爲真,那麼其餘的同等級的th:case都將被判斷爲假

default的寫法爲th:case="*"

<div th:switch="${user.role}">
  <p th:case="'admin'">超級管理員用戶</p>
  <p th:case="#{roles.manager}">管理員用戶</p>
  <p th:case="*">其餘用戶</p>
</div>

模板的佈局

導入模板片斷

定義和引用片斷

咱們常常會想讓咱們的模板包含一些其餘模板,比較常見的用途如頁眉,頁腳,菜單等。

爲了作到這一點,Thymeleaf須要咱們定義一些可用片斷,咱們能經過th:fragment屬性來實現這一點。

如今,咱們要添加一個帶標準版權聲明的頁腳文件(/WEB-INF/templates/footer.html):

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <body>
    <div th:fragment="copy">
      &copy; 網絡商店
    </div>
  </body>
</html>

上面定義了一個叫copy的代碼片斷,咱們能經過th:insert和th:replace屬性(還有th:include,但Thymeleaf3.*版本不推薦使用),很容易的將他導入到首頁中:

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

注意一點,th:insert使用的是一個片斷表達式(~{...}),或片斷中一個更具體的表達式。但在前者的狀況下(簡單片斷表達式),好比就像上邊的代碼,~{...}是可選的,因此以下班的代碼是等價的:

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

片斷語法說明

判斷表達式語法很是簡單,有三種不一樣的格式:

  • 模板名::dom選擇將導入模板名所指定的代碼片斷到dom選擇器中。
    • 注意:dom選擇器能夠僅僅是一個片斷的名字,因此能夠指定一下很是簡單的名字,如:footer:copy或更簡單的。

dom選擇器語法相似於XPath或css選擇器,更多內容見附錄C。

  • 直接使用模板名將此模板對應的完整的代碼導入。

注意:此時由th:insert和th:replace標籤導入的模板必須在當前的模板引擎下的模板解釋器能夠分辨。

  • 使用::dom選擇器this:dom選擇器導入與之相同的模板。

在上面的格式中,模板名dom選擇器均可以使用任何表達式的結果來表示,好比:

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

片斷中能夠包含任何th:*屬性。一點判斷被包含到目標模板(即便用th:insert/th:replace的文件)中,這些屬性將被執行。而後他們將能使用目標模板中的任何context變量。

這種方法有個很大的優點:你的任何片斷的代碼,完整的代碼都可以顯示在瀏覽器中,同時,仍保留了使用Thymeleaf把他導入到其餘模板中的能力。

引用不包含th:fragment的片斷

此外,因爲強大的dom選擇器,使咱們能夠導入不含有th:fragment屬性的代碼片斷,他甚至能夠標記全部來自Thymeleaf所不知道的應用,如:

<div id="copy-section">
  &copy; 2011 網絡商店
</div>

這裏能夠就像css同樣的使用它的id屬性:

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

th:insert和th:replace的不一樣點(以及th:include)

好了,如今讓咱們看看這幾個屬性有什麼區別(th:insert,th:replace和th:include(3.*版本不推薦使用)):

  • th:insert是將th:fragment標籤的內容歸入宿主標籤
  • th:replace是使用th:fragment標籤替換宿主標籤
  • th:include與th:insert相似,可是他插入的是片斷的內容,而不是片斷

不夠直觀?舉個例子:

<div th:fragment="copy">
  &copy; 網絡商店
</div>

導入到兩個div標籤中:

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

執行結果:

<body>
  ...
  <div>
    <footer>
        &copy; 網絡商店
    </footer>
  </div>
  <footer>
    &copy; 網絡商店
  </footer>
  <div>
      &copy; 網絡商店
  </div>
</body>

片斷的參數

爲了像一個函數同樣的使用一個模板片斷,在片斷定義的時候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}">

注意,在這裏,不管參數是否有簽名,都定義的是一個片斷的局部變量,因此不會致使它覆蓋或清空以前的context變量,片斷仍然能夠正常訪問調用它的模板的每一個context變量。

模板斷言

th:assert屬性能夠定義一個用逗號分隔的表達式,用來爲每個條件作出評估,以判斷是否產生異常。

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

這是一個在片斷簽名時就驗證參數的方便方式:

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

更靈活的模板:超越單純的插入

基於片斷表達式,咱們能夠爲片斷指定一個非文本,數字,javabean的參數,以代替標記。

這樣咱們就能使用這樣一種方式,它可使用豐富的標記來調用模板,以實現一個很是靈活的模板佈局機制。

注意titlelinks變量在片斷中的使用:

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

  <title th:replace="${title}">這個超帥應用</title>

  <!-- 經常使用樣式腳本 -->
  <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/myapp.css}">
  <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
  <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>

  <!--/* 每頁佔位符連接 */-->
  <th:block th:replace="${links}" />

</head>

咱們使用的片斷

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

  <title>超帥應用 - Main</title>

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

</head>
...

結果將咱們的調用的模板的實際的

相關文章
相關標籤/搜索