大部分是機翻,後續持續優化javascript
原文檔地址:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
Thymeleaf官網地址:https://www.thymeleaf.org/css
Thymeleaf是一個現代的服務器端Java模板引擎的web和獨立的環境,可以處理HTML, XML, JavaScript, CSS,甚至純文本。html
Thymeleaf的主要目標是提供一種優雅的和高度可維護的方式來建立模板。爲了實現這一點,它構建在天然模板的概念上,以不影響模板做爲設計原型使用的方式將其邏輯注入模板文件。這改進了設計的交流,並在設計和開發團隊之間架起了橋樑。html5
Thymeleaf的設計從一開始就考慮了Web標準——尤爲是HTML5——容許你建立徹底驗證模板,若是你須要的話。java
開箱即用,Thymeleaf容許您處理六種模板,其中每一種被稱爲模板模式:jquery
有兩種標記模板模式(HTML和XML)、三種文本模板模式(文本、JAVASCRIPT和CSS)和一種無操做模板模式(RAW)。git
HTML模板模式將容許任何類型的HTML輸入,包括HTML五、HTML 4和XHTML。將不執行任何驗證或格式良好性檢查,而且將在輸出中儘量尊重模板代碼/結構。github
XML模板模式將容許XML輸入。在這種狀況下,代碼應該是格式良好的—沒有未關閉的標記,沒有未引用的屬性,等等—若是發現格式良好性違規,解析器將拋出異常。注意,將不執行任何驗證(針對DTD或XML模式)。web
文本模板模式將容許對非標記性質的模板使用特殊語法。此類模板的示例多是文本電子郵件或模板化文檔。注意,HTML或XML模板也能夠做爲文本處理,在這種狀況下,它們不會被解析爲標記,而每一個標記、DOCTYPE、註釋等都將被視爲純文本。spring
JAVASCRIPT模板模式將容許在Thymeleaf應用程序中處理JAVASCRIPT文件。這意味着可以像在HTML文件中同樣在JavaScript文件中使用模型數據,可是要使用特定於JavaScript的集成,好比專門的轉義或天然腳本。JAVASCRIPT模板模式被認爲是文本模式,所以使用與文本模板模式相同的特殊語法。
CSS模板模式將容許處理Thymeleaf應用程序中涉及的CSS文件。與JAVASCRIPT模式相似,CSS模板模式也是一種文本模式,並使用來自文本模板模式的特殊處理語法。
原始模板模式根本不會處理模板。它用於將未觸及的資源(文件、URL響應等)插入正在處理的模板中。例如,能夠將HTML格式的外部非控制資源包含到應用程序模板中,但要確保這些資源可能包含的任何Thymeleaf代碼都不會被執行。
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}
。
這有助於設計人員和開發人員處理相同的模板文件,並減小將靜態原型轉換爲工做模板文件所需的工做。這樣作的能力稱爲天然模板。
本文所示示例的源代碼,以及本指南的後續章節,能夠在 Good Thymes Virtual Grocery GitHub repository.
爲了更好地解釋使用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對象處理模板。
最後,它看起來是這樣的:
但首先讓咱們看看模板引擎是如何初始化的。
咱們的過濾器中的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應用程序根目錄解析資源。
但這還不是咱們對模板解析器所能說的所有,由於咱們能夠在它上面設置一些配置參數。
1、模板模式:
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建立咱們的頁面。
咱們的第一個任務是爲咱們的雜貨站點建立一個主頁。
這個頁面的第一個版本將很是簡單:只有一個標題和一條歡迎信息。
這是咱們的/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>
主頁的最簡單版本如今彷佛已經準備就緒,可是有些事情咱們尚未想到……若是咱們收到這樣的消息怎麼辦?
home.welcome=Welcome to our <b>fantastic</b> grocery store!
若是像之前同樣執行此模板,咱們將得到:
<p>Welcome to our <b>fantastic</b> 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()
方法」)。
屬性值有不少可能性:消息,變量表達式等等。下一章將向咱們展現全部這些可能性。
咱們將在雜貨店虛擬商店的開發中稍做休息,以瞭解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>
可是有更多類型的表達式,還有更多有趣的細節來了解咱們已經知道的表達式。首先,讓咱們看一下標準表達式功能的快速摘要:
#{...}
全部這些功能均可以組合和嵌套:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
衆所周知,#{...}
消息表達式使咱們能夠連接如下內容:
<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>
咱們已經提到過,${...}
表達式其實是在上下文中包含的變量映射上執行的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
:用於執行已配置的轉換服務(若是有)的方法。#dates
:java.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>
變量表達式不只能夠寫成${...}
,並且還能夠寫成*{...}
。
可是,有一個重要的區別:星號語法在選定對象而不是整個上下文上評估表達式。也就是說,只要沒有選定的對象,美圓和星號的語法就徹底同樣。
什麼是選定對象?使用該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>
因爲它們的重要性,URL是Web應用程序模板中的一等公民,而Thymeleaf Standard Dialect對它們有一種特殊的語法,該@
語法爲:@{...}
URL有不一樣類型:
http://www.thymeleaf.org
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>
標記的屬性。orderId=${o.id}
)。所需的URL參數編碼操做也將自動執行。@{/order/process(execId=${execId},execType='FAST')}
@{/order/{orderId}/details(orderId=${orderId})}
URL /order/details
將自動以應用程序上下文名稱做爲前綴。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),以便連接到同一服務器中的不一樣上下文。這些網址的指定方式以下@{~/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> 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>
布爾文本是true
和false
。例如:
<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>
文本,不管它們是文字仍是評估變量或消息表達式的結果,均可以使用+運算符輕鬆附加:
<span th:text="'The name of the user is ' + ${user.name}">
文字替換能夠輕鬆格式化包含變量值的字符串,而無需在文字後面附加 '...' + '...'
。
這些替換項必須用豎線(|
)包圍,例如:
<span th:text="|Welcome to our application, ${user.name}!|">
等效於:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
文字替換能夠與其餘類型的表達式結合使用:
<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">
惟一的變量/消息表達式(
${...},*{...},#{...}
)被容許內部|...|字面取代。沒有其餘文字('...'
),布爾/數字標記,條件表達式等。
一些算術運算也可用:+,-,*,/和%
。
<div th:with="isEven=(${prodStat.count} % 2 == 0)">
請注意,這些運算符也能夠在OGNL變量表達式內部應用(在這種狀況下,將由OGNL代替Thymeleaf標準表達式引擎執行):
<div th:with="isEven=${prodStat.count % 2 == 0}">
請注意,其中一些運算符存在文本別名:div(/)
,mod(%)
。
在表達式中的值能夠與進行比較>
,<
,>
=和<=
符號,以及==
和!=
運營商能夠被用來檢查是否相等(或缺少)。請注意,XML規定,不得在屬性值中使用 <和> 符號,所以應將其替換爲 <
和 >
。
<div th:if="${prodStat.count} > 1"> <span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
一個更簡單的替代方法多是使用如下某些運算符存在的文本別名:gt(>)
,lt(<)
,ge(>=)
,le(<=)
,not(!)
。還有eq(==)
,neq/ ne(!=)
。
條件表達式旨在僅根據兩個條件的求值結果來求值(它自己就是另外一個表達式)。
讓咱們來看一個例子片斷(引入另外一個屬性修改器,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>
一個默認的表情是一種特殊的條件值的沒有那麼一部分。它等效於某些語言(如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>
No-Operation令牌由下劃線符號(_)表示。
該標記背後的想法是指定表達式的指望結果什麼也不作,即徹底就像可處理屬性(例如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.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配置中聲明的轉換服務和格式化程序將自動提供給
${{...}}
和*{{...}}
表達式。
除了用於表達式處理的全部這些功能以外,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>
__可使用來在屬性中對預處理字符串進行轉義__。
本章將說明在標記中設置(或修改)屬性值的方法。
假設咱們的網站發佈了新聞通信,咱們但願用戶可以訂閱該新聞通信,因此咱們建立/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" />
到如今爲止,您可能會認爲相似:
<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
有兩個叫比較特殊的屬性th:alt-title
和th:lang-xmllang
可用於同時設置兩個屬性相同的值。特別:
th:alt-title
將設置alt
和title
。
th:lang-xmllang
將設置lang
和xml: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}" />
Thymeleaf還提供th:attrappend
和th: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:classappend
和th:styleappend
屬性,用於在元素上添加CSS類或樣式片斷而不覆蓋現有屬性:
<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">
(沒必要擔憂該th:each
屬性。它是一個迭代屬性,咱們將在後面討論。)
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
Thymeleaf提供了一個默認的屬性處理器,即便咱們在標準方言中沒有爲其定義任何特定的處理器,它也容許咱們設置任何屬性的值th:*
。
因此像這樣:
<span th:whatever="${user.name}">...</span>
將致使:
<span whatever="John Apricot">...</span>
也可使用徹底不一樣的語法,以更友好的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:*
,它不會替代它。徹底沒有打算未來棄用命名空間的語法。
到目前爲止,咱們已經建立了一個主頁,一個用戶我的資料頁面以及一個容許用戶訂閱咱們的新聞通信的頁面……可是咱們的產品呢?爲此,咱們將須要一種方法來遍歷集合中的項目以構建咱們的產品頁面。
爲了在咱們的/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
:
使用時th:each,Thymeleaf提供了一種用於跟蹤迭代狀態的有用機制:status變量。
狀態變量在th:each屬性中定義,而且包含如下數據:
讓咱們看看如何在上一個示例中使用它:
<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>
有時咱們可能想優化數據集合的檢索(例如從數據庫中),以便僅在真正要使用這些集合時才檢索這些集合。
實際上,這能夠應用於任何數據,可是考慮到內存中集合可能具備的大小,對於這種狀況,檢索要迭代的集合是最多見的狀況。
爲了支持這一點,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>
有時,您須要模板的一部分才能僅在知足特定條件的狀況下出如今結果中。
例如,假設咱們要在產品表中顯示一列,其中包含每一個產品的評論數量,若是有評論,則指向該產品的評論詳細信息頁面的連接。
爲了作到這一點,咱們將使用如下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如下規則評估指定的表達式:
另外,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>
還有一種方法可使用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>
在咱們的模板中,咱們常常須要包含其餘模板中的部分,例如頁腳,頁眉,菜單等部分。
爲了作到這一點,Thymeleaf須要咱們定義這些要包含的部分「片斷」,可使用該th:fragment屬性來完成。
假設咱們要在全部雜貨店頁面中添加標準的版權頁腳,所以咱們建立一個/WEB-INF/templates/footer.html
包含如下代碼的文件:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <div th:fragment="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}"包括在名爲的模板上應用指定的標記選擇器所產生的片斷templatename。請注意,該selector名稱可能僅僅是片斷名稱,所以您能夠~{templatename::fragmentname}像~{footer :: copy}上面同樣指定簡單的名稱。
標記選擇器語法由基礎的AttoParser解析庫定義,而且相似於XPath表達式或CSS選擇器。有關更多信息,請參見附錄C。
"~{templatename}"包括名爲的完整模板templatename。
請注意,您在th:insert/ th:replace標記中使用的模板名稱必須由模板引擎當前正在使用的模板解析器解析。
~{::selector}"或"~{this::selector}"從同一模板插入一個片斷,匹配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屬性的片斷。甚至多是徹底不瞭解Thymeleaf的來自不一樣應用程序的標記代碼:
... <div id="copy-section"> © 2011 The Good Thymes Virtual Grocery </div> ...
咱們可使用上面的片斷,簡單地經過其id屬性引用它,相似於CSS選擇器:
<body> ... <div th:insert="~{footer :: #copy-section}"></div> </body>
和之間有什麼區別th:insert
和th:replace
(和th:include,由於3.0不推薦)?
th:insert
最簡單:它將簡單地將指定的片斷做爲其host標籤的主體插入。
th:replace
實際上將其主機標籤替換爲指定的片斷。
th:include
與類似th:insert
,但不插入片斷,而是僅插入該片斷的內容。
所以,HTML片斷以下所示:
<footer th:fragment="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> © 2011 The Good Thymes Virtual Grocery </footer> </div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <div> © 2011 The Good Thymes Virtual Grocery </div> </body>
爲了爲模板片斷建立更相似於函數的機制,使用定義的片斷th: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>
藉助片斷表達式,咱們能夠爲片斷指定參數,這些參數不是文本,數字,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> ...
回到示例應用程序,讓咱們從新訪問產品列表模板的最新版本:
<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-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>
在這種狀況下,若是${condition}爲false,null將被返回,所以不會執行刪除。
爲了可以將單個文件做爲佈局,可使用片斷。具備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。
Thymeleaf將局部變量稱爲爲模板的特定片斷定義的變量,而且僅可用於該片斷內部的評估。
咱們已經看到的示例是prod產品列表頁面中的iter變量:
<tr th:each="prod : ${prods}"> ... </tr>
該prod變量僅在<tr>
標記範圍內可用。特別:
<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>
您可能在想:優先?咱們尚未談論這個!好吧,不用擔憂,由於這正是下一章的內容。
當您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>
<!-- ... -->
Thymeleaf模板中的任何位置均可以使用標準的HTML / XML註釋。這些註釋中的全部內容均不會被Thymeleaf處理,並將逐字複製到結果中:
<!-- User info follows --> <div th:text="${...}"> ... </div>
解析器級別的註釋塊是在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>
Thymeleaf容許定義特殊註釋塊的定義,當模板靜態打開(即做爲原型)時,標記爲註釋,但Thymeleaf在執行模板時將其視爲正常標記。
<span>hello!</span> <!--/*/ <div th:text="${...}"> ... </div> /*/--> <span>goodbye!</span>
Thymeleaf的解析系統將僅刪除<!--/*/
和/*/-->
標記,但不會刪除其內容,所以不會對其進行註釋。所以,在執行模板時,Thymeleaf實際上會看到如下內容:
<span>hello!</span> <div th:text="${...}"> ... </div> <span>goodbye!</span>
與解析器級註釋塊同樣,此功能與方言無關。
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>
),而且當在瀏覽器中做爲原型靜態打開時,仍能夠正常使用!
儘管標準方言容許咱們使用標記屬性來執行幾乎全部操做,可是在某些狀況下,咱們更喜歡直接將表達式寫到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 <b>great!</b>"</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>
文本內聯與咱們剛剛看到的表達式內聯功能很是類似,但實際上增長了更多功能。必須使用明確啓用它th:inline="text"。
文本內聯不只使咱們可以使用與剛纔看到的相同的內聯表達式,並且實際上就像在模板模式下處理標籤主體同樣處理標籤主體TEXT,這使咱們可以執行基於文本的模板邏輯(不只是輸出表達式)。
咱們將在下一章有關文本模板模式的內容中看到更多有關此內容的信息。
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註釋中,例如:
<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內聯的重要注意事項是,此表達式求值是智能的,而且不只限於字符串。Thymeleaf將使用JavaScript語法正確編寫如下類型的對象:
<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庫,若是有的話,將使用它。若是沒有,它將應用內置的序列化機制,該機制能夠知足大多數方案的需求併產生類似的結果(但靈活性較差)。
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上面的代碼片斷。
與以前解釋JavaScript的方式相同,CSS內聯還容許咱們的<style>
標籤靜態和動態地工做,即經過將內聯表達式包裝在註釋中而成爲CSS天然模板。看到:
<style th:inline="css"> .main\ elems { text-align: /*[[${align}]]*/ left; } </style>
在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-mode
模板中是徹底能夠的(請注意>
):
[# th:if="${120<user.age}"] Congratulations! [/]
固然,<
在實際的文本模板中這沒有任何意義,可是若是咱們正在使用th:inline="text"
包含上面代碼的代碼塊處理HTML模板,而且要確保咱們的瀏覽器不會將它<user.age
用做名稱的話,這是一個好主意。靜態打開文件做爲原型時的open標籤。
這種語法的優勢之一是它與標記語法同樣可擴展。開發人員仍然可使用自定義元素和屬性來定義本身的方言,爲它們應用前綴(可選),而後在文本模板模式下使用它們:
[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]
在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() { ...
在相似於僅原型的註釋塊的方式,全部三個文本模板模式(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})]! ...
如上一章所述,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樣式表,這些模板既能夠做爲原型也能夠做爲工做模板使用。
如今咱們對使用Thymeleaf有了不少了解,咱們能夠在咱們的網站上添加一些新頁面以進行訂單管理。
請注意,咱們將專一於HTML代碼,可是若是您想查看相應的控制器,則能夠查看捆綁的源代碼。
讓咱們從建立訂單列表頁面開始/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的強大功能。
如今進入訂單詳細信息頁面,在該頁面中,咱們將大量使用星號語法:
<!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>
對於咱們的Good Thymes虛擬雜貨店,咱們選擇了一個ITemplateResolver實現ServletContextTemplateResolver,該實現容許咱們從Servlet上下文中獲取模板做爲資源。
除了使咱們可以經過實現ITemplateResolver,Thymeleaf 來建立本身的模板解析器以外,還包括如下四種實現:
return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);
return new FileInputStream(new File(template));
return (new URL(template)).openStream();
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/*");
// 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模板資源–潛在的性能問題可能會經過使用模板緩存而在很大程度上獲得緩解(在這種狀況下,僅在首次訪問模板時才能解決模板問題)。
咱們沒有爲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);
爲何咱們要擁有多個消息解析器?出於與模板解析器相同的緣由:訂購了消息解析器,若是第一個沒法解析特定的消息,則將詢問第二個,而後詢問第三個,依此類推。
該轉換服務,使咱們用的手段來進行數據轉換和格式化操做雙括號語法(${{...}}
)其實是標準方言的特色,而不是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中的實現。
Thymeleaf很是重視日誌記錄,並始終嘗試經過其日誌記錄界面提供儘量多的有用信息。
slf4j,實際上,所使用的日誌記錄庫實際上充當了咱們想要在應用程序中使用的任何日誌記錄實現的橋樑(例如log4j)。
Thymeleaf班會記錄TRACE,DEBUG並INFO-level信息,這取決於咱們但願的詳細程度,而且除了通常的記錄它會使用與TemplateEngine類,咱們能夠爲不一樣的目的而單獨配置相關的三個特殊記錄器:
使用的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
Thymeleaf的工做要歸功於一組解析器(用於標記和文本),該解析器將模板解析爲事件序列(打開標籤,文本,關閉標籤,註釋等)和一系列處理器(每種須要一種行爲)應用–修改模板解析的事件序列,以便經過將原始模板與咱們的數據結合來建立咱們指望的結果。
默認狀況下,它還包括存儲已解析模板的緩存;在處理模板文件以前讀取和解析模板文件所致使的事件順序。在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");
到目前爲止,咱們已經爲食品雜貨店工做,模板以一般的方式完成,邏輯以屬性的形式插入模板中。
可是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模板將是設計團隊和開發團隊之間更好的溝通工具。
默認狀況下,不會指望每一個模板都使用去耦邏輯。取而代之的是,配置的模板解析器(的實現ITemplateResolver)將須要使用解耦邏輯將要解析的模板專門標記爲。
除了StringTemplateResolver(不容許解耦邏輯)外,的全部其餘現成實現都ITemplateResolver將提供一個稱爲的標誌useDecoupledLogic,該標誌將將該解析器解析的全部模板標記爲可能將其所有或部分邏輯存儲在單獨的資源中:
final ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); ... templateResolver.setUseDecoupledLogic(true);
啓用後,解耦模板邏輯不是必需的。啓用後,這意味着引擎將查找包含解耦邏輯的資源,若是存在,則將其解析並與原始模板合併。若是解耦的邏輯資源不存在,則不會引起任何錯誤。
一樣,在同一模板中,咱們能夠混合使用耦合邏輯和解耦邏輯,例如,經過在原始模板文件中添加一些Thymeleaf屬性,而將其餘屬性留給單獨的解耦邏輯文件。最多見的狀況是使用new(在v3.0中)th:ref屬性。
th:ref
屬性th:ref只是標記屬性。從處理的角度來看,它什麼也沒作,只是在處理模板後消失,可是它的做用在於它充當標記引用,便可以經過標記選擇器中的名稱來解析,就像標記名或片斷同樣。(th:fragment)。
所以,若是咱們有一個選擇器,例如:
<attr sel="whatever" .../>
這將匹配:
<whatever>
標籤。錨,這一事實最終可能會污染咱們的輸出。
從一樣的意義上說,它的缺點是th:ref什麼?好吧,很顯然,咱們將在模板中添加一些Thymeleaf邏輯(「邏輯」)。
請注意,該th:ref屬性的適用性不只適用於解耦的邏輯模板文件:它在其餘類型的場景中也同樣工做,例如在片斷表達式(~{...})中。
影響極小。當一個已解析的模板被標記爲使用解耦邏輯而且不被緩存時,該模板邏輯資源將首先被解析,解析並處理爲一系列內存中的指令:基本上是要注入到每一個標記選擇器的屬性列表。
但這是惟一須要執行的附加步驟,由於在此以後,將解析真實模板,而且在解析這些模板時,因爲AttoParser中節點選擇的高級功能,這些屬性將由解析器自己即時注入。。所以,已解析的節點將從解析器中出來,就像它們的注入屬性寫在原始模板文件中同樣。
這樣最大的優點?將模板配置爲要緩存時,它將緩存已包含注入屬性的模板。所以,一旦對高速緩存的模板使用解耦的模板進行緩存,其開銷將絕對爲零。
Thymeleaf解析與每一個模板相對應的解耦邏輯資源的方式可由用戶配置。它由擴展點決定org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver,爲其提供了默認實現:StandardDecoupledTemplateLogicResolver。
此標準實現有什麼做用?
IDecoupledTemplateLogicResolver能夠TemplateEngine輕鬆配置要使用的具體實現:
final StandardDecoupledTemplateLogicResolver decoupledresolver = new StandardDecoupledTemplateLogicResolver(); decoupledResolver.setPrefix("../viewlogic/"); ... templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);
某些對象和變量映射始終可被調用。讓咱們看看他們:
#ctx
:上下文對象。一種實現org.thymeleaf.context.IContext或org.thymeleaf.context.IWebContext取決於咱們的環境(獨立或網絡)。#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環境中使用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環境中,還能夠直接訪問如下對象(請注意,這些是對象,而不是映射/命名空間):
#request
:直接訪問javax.servlet.http.HttpServletRequest與當前請求關聯的對象。${#request.getAttribute('foo')} ${#request.getParameter('foo')} ${#request.getContextPath()} ${#request.getRequestName()} ...
#session
:直接訪問javax.servlet.http.HttpSession與當前請求關聯的對象。${#session.getAttribute('foo')} ${#session.id} ${#session.lastAccessedTime} ...
#servletContext
:直接訪問javax.servlet.ServletContext與當前請求關聯的對象。${#servletContext.getAttribute('foo')} ${#servletContext.contextPath} ...
#execInfo
:表達式對象,提供有關Thymeleaf標準表達式中正在處理的模板的有用信息。
#execInfo
: 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
: 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)}
#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
: 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
: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
:相似於#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
: 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
: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
: 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)}
#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
: 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
: 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
: 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
: 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
: 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
: 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')}
Thymeleaf的標記選擇器直接從Thymeleaf的解析庫AttoParser借用。
該選擇器的語法與XPath,CSS和jQuery中的選擇器的語法有很大類似之處,這使它們對於大多數用戶而言易於使用。您能夠在AttoParser文檔中查看完整的語法參考。
例如,如下選擇器將在標記內的每一個位置選擇
<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個位置。可是也可使用更簡潔的語法:
高級屬性選擇功能:
直接相似於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緣由,由於可使用引用代替元素名稱。所以,上面的標記選擇器表達式:
<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" />