• 监察体制改革后 湘西半年72名公职人员主动交代问题 2019-05-12
  • 媒体宣传报道重庆日报 王国平:扮靓重庆两江四岸” 让城市有机更新 2019-04-26
  • 我相信“交警雨中护送高考生”是真,“交警雨中护送高考生”反被该高考生家长投诉是假。 2019-04-16
  • 14名消防员日巡逻28公里 洗冷水澡 2019-04-10
  • 靶壕有了“蓝军”,百发百中的“神枪手”练起来 2019-04-10
  • 不是秀强大了,别人就会来做朋友,这逻辑不对 2019-04-01
  • 候选企业:中国石油呼和浩特石化公司 2019-03-26
  • 航天员沙漠野外生存训练完美收官!为第一天团打call 2019-03-25
  • 请问,建立市场经济后,原计划经济哪里去?改革后,我们还在实行计划经济,为何没有提及? 2019-03-25
  • 构建年轻干部梯次培养链 2019-03-19
  • 孙实的专栏作者中国国家地理网 2019-03-15
  • 湖南师范大学举行研究阐释党的十九大精神国家社科基金重大专项学术研讨会 2019-03-15
  • [雷人]蠢货!土地处于不同的城市和地段,关联的资源不一样,价值也不一样。不然给咱俩同样面积的土地,咱的在北上广深,你的在边远山区,你干么? 2019-03-08
  • 国际社会持续热议上合青岛峰会:上合组织发展进入新阶段 彰显中国领导力 2019-03-08
  • 珍惜野生动物频现甘孜境内 生态环境质量不断提升 2019-03-06
  • 频道栏目
    神奇公式秒杀全国11选5 > 程序开发 > web前端 > HTML/CSS > 正文
    Thymeleaf教程:使用Thymeleaf
    2018-10-27 09:51:23           
    收藏   我要投稿
    Thymeleaf 教程:使用Thymeleaf

    多值类匹配

    1介绍Thymeleaf

    1.1什么是Thymeleaf?

    Thymeleaf是一个适用于Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。

    Thymeleaf的主要目标是提供一种优雅且高度可维护的模板创建方式。为实现这一目标,它以自然模板的概念为基础,将其逻辑注入模板文件,其方式不会影响模板被用作设计原型。这改善了设计沟通,缩小了设计和开发团队之间的差距。

    Thymeleaf也从一开始就设计了Web标准 - 特别是HTML5- 允许您创建完全验证的模板,如果这是您的需要。

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

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

    Welcome to our grocery store!

    标签中添加了一个属性,绝对不是HTML5-ish:

    Welcome to our grocery store!

    Bienvenido a nuestra tienda de comestibles!

    Welcome to our grocery store!

    Today is: 13 February 2011

    Product list

    NAMEPRICEIN STOCK

    Onions2.41yes

    Return to home

    Product list

    NAMEPRICEIN STOCK

    Fresh Sweet Basil4.99yes

    Italian Tomato1.25no

    Yellow Bell Pepper2.50yes

    Old Cheddar18.75yes

    Return to home

    © 2011 The Good Thymes Virtual Grocery

    ...

    ...

    ...

    ...

    ...

    © 2011 The Good Thymes Virtual Grocery

    © 2011 The Good Thymes Virtual Grocery

    © 2011 The Good Thymes Virtual Grocery

    Layout H1

    Layout content

    Layout footer

    Page content

    Included on page

    HTML XML 文本 JAVASCRIPT CSS 生的

    有两种标记模板模式(HTML和XML),三种文本模板模式(TEXT,JAVASCRIPT和CSS)和一种无操作模板模式(RAW)。

    该HTML模板模式将允许任何类型的HTML的输入,包括HTML5,HTML4和XHTML。不会执行验证或格式良好检查,并且将在输出中尽可能地尊重模板代码/结构。

    该XML模板模式将允许XML输入。在这种情况下,代码应该是格式良好的 - 没有未封闭的标签,没有不带引号的属性等 - 如果发现格式错误,解析器将抛出异常。请注意,不会执行任何验证(针对DTD或XML架构)。

    该TEXT模板模式将允许非标记性质的模板使用特殊的语法。此类模板的示例可能是文本电子邮件或模板文档。请注意,HTML或XML模板也可以被处理TEXT,在这种情况下,它们不会被解析为标记,并且每个标记,DOCTYPE,注释等都将被视为纯文本。

    该JAVASCRIPT模板模式将允许在Thymeleaf应用程序的JavaScript文件的处理。这意味着能够以与HTML文件相同的方式在JavaScript文件中使用模型数据,但是使用特定于JavaScript的集成,例如专门的转义或自然脚本。该JAVASCRIPT模板模式被认为是一种文本模式,因此使用相同的特殊语法的TEXT模板模式。

    该CSS模板模式将允许参与Thymeleaf应用CSS文件的处理。与JAVASCRIPT模式类似,CSS模板模式也是文本模式,并使用TEXT模板模式中的特殊处理语法。

    该RAW模板模式将根本不处理模板。它用于将未经处理的资源(文件,URL响应等)插入到正在处理的模板中。例如,HTML格式的外部非受控资源可以包含在应用程序模板中,安全地知道这些资源可能包含的任何Thymeleaf代码都不会被执行。

    1.3方言:标准方言

    Thymeleaf是一个极易扩展的模板引擎(实际上它可以称为模板引擎框架),允许您定义和自定义模板处理的细节级别。

    将一些逻辑应用于标记工件(标签,某些文本,注释或仅仅是占位符,如果模板不是标记)的对象称为处理器,这些处理器的集合 - 加上可能还有一些额外的工件 - 是什么一个方言通常是由??浼从?,Thymeleaf的核心库提供了一种称为标准方言的方言,对大多数用户来说应该足够了。

    请注意,方言实际上可以没有处理器,并且完全由其他类型的工件组成,但处理器绝对是最常见的用例。

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

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

    官方的thymeleaf-spring3和thymeleaf-spring4集成包都定义了一种称为“SpringStandard Dialect”的方言,它与标准方言大致相同,但是为了更好地利用Spring Framework中的某些功能(例如) ,使用Spring Expression Language或SpringEL代替OGNL)。因此,如果您是Spring MVC用户,那么您不会浪费时间,因为您在此处学习的几乎所有内容都将在Spring应用程序中使用。

    标准方言的大多数处理器都是属性处理器。这使得浏览器甚至可以在处理之前正确显示HTML模板文件,因为它们只会忽略其他属性。例如,虽然使用标记库的JSP可能包含不能由浏览器直接显示的代码片段,例如:

    ...... Thymeleaf标准方言将允许我们实现相同的功能:

    这不仅可以被浏览器正确显示,而且还允许我们(可选地)在其中指定一个值属性(在这种情况下为“James Carrot”),当在浏览器中静态打开原型时将显示该属性,并且这将由${user.name}在处理模板期间评估得到的值代替。

    这有助于您的设计人员和开发人员处理相同的模板文件,并减少将静态原型转换为工作模板文件所需的工作量。执行此操作的能力是称为自然模板的功能。

    2 The Good Thymes虚拟杂货店

    可以在Good Thymes Virtual Grocery GitHub存储库中找到本指南的本章和后续章节中显示的示例的源代码。

    2.1杂货店的网站

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

    这个应用程序是一个假想的虚拟杂货的网站,并将为我们提供许多场景来展示Thymeleaf的许多功能。

    首先,我们需要一套简单的模型实体用于我们的应用程序:通过创建Products销售。我们还将管理这些:CustomersOrdersCommentsProducts

     

    示例应用程序模型

     

    示例应用程序模型

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

    public class ProductService {

    ...

    public List findAll() {

    return ProductRepository.getInstance().findAll();

    }

    public Product findById(Integer id) {

    return ProductRepository.getInstance().findById(id);

    }

    }

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

    private boolean process(HttpServletRequest request, HttpServletResponse response)

    throws ServletException {

    try {

    // This prevents triggering engine executions for resource URLs

    if (request.getRequestURI().startsWith("/css") ||

    request.getRequestURI().startsWith("/images") ||

    request.getRequestURI().startsWith("/favicon")) {

    return false;

    }

    /*

    * Query controller/URL mapping and obtain the controller

    * that will process the request. If no controller is available,

    * return false and let other filters/servlets process the request.

    */

    IGTVGController controller = this.application.resolveControllerForRequest(request);

    if (controller == null) {

    return false;

    }

    /*

    * Obtain the TemplateEngine instance.

    */

    ITemplateEngine templateEngine = this.application.getTemplateEngine();

    /*

    * Write the response headers

    */

    response.setContentType("text/html;charset=UTF-8");

    response.setHeader("Pragma", "no-cache");

    response.setHeader("Cache-Control", "no-cache");

    response.setDateHeader("Expires", 0);

    /*

    * Execute the controller and process view template,

    * writing the results to the response writer.

    */

    controller.process(

    request, response, this.servletContext, templateEngine);

    return true;

    } catch (Exception e) {

    try {

    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

    } catch (final IOException ignored) {

    // Just ignore this

    }

    throw new ServletException(e);

    }

    }

    这是我们的IGTVGController界面:

    public interface IGTVGController {

    public void process(

    HttpServletRequest request, HttpServletResponse response,

    ServletContext servletContext, ITemplateEngine templateEngine);

    }

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

    最后,它看起来像这样:

     

    示例应用程序主页

     

    示例应用程序主页

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

    2.2创建和配置模板引擎

    我们的过滤器中的process(...)方法包含以下行:

    ITemplateEngine templateEngine = this.application.getTemplateEngine();

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

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

    public class GTVGApplication {

    ...

    private final TemplateEngine templateEngine;

    ...

    public GTVGApplication(final ServletContext servletContext) {

    super();

    ServletContextTemplateResolver templateResolver =

    new ServletContextTemplateResolver(servletContext);

    // HTML is the default mode, but we set it anyway for better understanding of code

    templateResolver.setTemplateMode(TemplateMode.HTML);

    // This will convert "home" to "/WEB-INF/templates/home.html"

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

    templateResolver.setSuffix(".html");

    // Template cache TTL=1h. If not set, entries would be cached until expelled

    templateResolver.setCacheTTLMs(Long.valueOf(3600000L));

    // Cache is set to true by default. Set to false if you want templates to

    // be automatically updated when modified.

    templateResolver.setCacheable(true);

    this.templateEngine = new TemplateEngine();

    this.templateEngine.setTemplateResolver(templateResolver);

    ...

    }

    }

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

    模板解析器

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

    ServletContextTemplateResolver templateResolver =

    new ServletContextTemplateResolver(servletContext);

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

    public interface ITemplateResolver {

    ...

    /*

    * Templates are resolved by their name (or content) and also (optionally) their

    * owner template in case we are trying to resolve a fragment for another template.

    * Will return null if template cannot be handled by this template resolver.

    */

    public TemplateResolution resolveTemplate(

    final IEngineConfiguration configuration,

    final String ownerTemplate, final String template,

    final Map templateResolutionAttributes);

    }

    这些对象负责确定我们的模板的访问方式,在这个GTVG应用程序中,org.thymeleaf.templateresolver.ServletContextTemplateResolver我们将从Servlet上下文中检索模板文件作为资源的方式:javax.servlet.ServletContext每个Java Web应用程序中都存在的应用程序范围的对象,并从Web应用程序根解析资源。

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

    templateResolver.setTemplateMode(TemplateMode.HTML);

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

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

    templateResolver.setSuffix(".html");

    该前缀和后缀修改,我们将传递到发动机获得要使用的真实资源名称的模板名称。

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

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

    (可选)通过cacheTTLMs属性在模板解析器中配置解析模板可以在缓存中存在的时间量:

    templateResolver.setCacheTTLMs(3600000L);

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

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

    关于模板解析器还有很多东西需要学习,但是现在我们来看看Template Engine对象的创建。

    模板引擎

    Template Engine对象是org.thymeleaf.ITemplateEngine接口的实现。其中一个实现是由Thymeleaf核心提供的:org.thymeleaf.TemplateEngine我们在这里创建一个实例:

    templateEngine = new TemplateEngine();

    templateEngine.setTemplateResolver(templateResolver);

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

    模板解析器是唯一需要的参数TemplateEngine,尽管稍后将介绍许多其他参数(消息解析器,缓存大小等)。现在,这就是我们所需要的。

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

    3使用文本

    3.1多语言欢迎

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

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

    您将注意到的第一件事是该文件是HTML5,任何浏览器都可以正确显示它,因为它不包含任何非HTML标记(浏览器会忽略它们不理解的所有属性,例如th:text)。

    但是您可能还注意到此模板实际上不是有效的HTML5文档,因为th:*HTML5规范不允许我们在表单中使用这些非标准属性。事实上,我们甚至xmlns:th在我们的

    ...它在模板处理中根本没有任何影响,但是作为一个咒语,阻止我们的IDE抱怨缺少所有这些th:*属性的命名空间定义。

    那么如果我们想让这个模板HTML5有效呢?简单:切换到Thymeleaf的数据属性语法,使用data-属性名称和hyphen(-)分隔符的前缀而不是分号(:):

    data-HTML5规范允许使用自定义前缀属性,因此,使用上面的代码,我们的模板将是有效的HTML5文档。

    两种表示法都是完全等效且可互换的,但为了代码示例的简单性和紧凑性,本教程将使用名称空间表示法(th:*)。此外,th:*符号更通用,并且在每个Thymeleaf模板模式(XML,TEXT...)中data-都允许使用,而符号仅允许在HTML模式中使用。

    使用th:文本和外化文本

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

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

    Welcome to our grocery store!

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

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

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

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

    但是,我们在初始化期间没有为模板引擎指定消息解析器,这意味着我们的应用程序正在使用标准消息解析器,由org.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 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是我们使用的那个。实际上我们必须这样做,因为使用aServletContextTemplateResolver要求我们使用上下文实现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());

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

    3.2有

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

    未转义的文字

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

    home.welcome=Welcome to our fantastic grocery store!

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

    Welcome to our fantastic grocery store!

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

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

    Welcome to our grocery store!

    这将输出我们的消息,就像我们想要的那样:

    Welcome to our fantastic grocery store!

    使用和显示变量

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

    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我们上下文的变量,现在我们可以在模板中显示它:

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

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

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

    4标准表达式语法

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

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

    Welcome to our grocery store!

    Today is: 13 february 2011

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

    简单表达:

    变量表达式:${...} 选择变量表达式:*{...} 消息表达式:#{...} 链接网址表达式:@{...} 片段表达式:~{...} 字面

    文本文字:'one text','Another one!',... 号码文字:0,34,3.0,12.3,... 布尔文字:true,false 空字面:null 文字标记:one,sometext,main,... 文字操作:

    字符串连接:+ 文字替换:|The name is ${name}| 算术运算:

    二元运算符:+,-,*,/,% 减号(一元运算符):- 布尔运算:

    二元运算符:and,or 布尔否定(一元运算符):!,not 比较和平等:

    比较:>,<,>=,<=(gt,lt,ge,le) 平等运营商:==,!=(eq,ne) 有条件的运营商:

    IF-THEN:(if) (then) IF-THEN-ELSE:(if) (then) : (else) 默认:(value) : (defaultvalue) 特殊代币:

    无操作:_

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

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

    4.1消息

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

    Welcome to our grocery store!

    ......对此:

    home.welcome=Bienvenido a nuestra tienda de comestibles!

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

    Bienvenido a nuestra tienda de comestibles, John Apricot!

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

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

    参数根据java.text.MessageFormat标准语法指定,这意味着您可以格式化为java.text.*包的类API文档中指定的数字和日期。

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

    Welcome to our grocery store, Sebastian Pepper!

    请注意,使用th:utext此处意味着格式化的消息不会被转义。此示例假定user.name已经转义。

    可以指定几个参数,以逗号分隔。

    消息密钥本身可以来自变量:

    Welcome to our grocery store, Sebastian Pepper!

    4.2变量

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

    有关OGNL语法和功能的详细信息,请阅读OGNL语言指南

    在Spring支持MVC的应用程序中,OGNL将被SpringEL取代,但其语法与OGNL的语法非常相似(实际上,对于大多数常见情况来说,完全相同)。

    从OGNL的语法,我们知道表达式:

    Today is: 13 february 2011.

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

    ctx.getVariable("today");

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

    Welcome to our grocery store, Sebastian Pepper!

    ...通过执行以下命令获取用户名:

    ((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 Contexts)HttpServletRequest对象。 #response:(仅限Web Contexts)HttpServletResponse对象。 #session:(仅限Web Contexts)HttpSession对象。 #servletContext:(仅限Web Contexts)ServletContext对象。

    所以我们可以这样做:

    Established locale country: US.

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

    Expression Utility对象

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

    #execInfo:有关正在处理的模板的信息。 #messages:在变量表达式中获取外化消息的方法,与使用#{...}语法获取的方法相同。 #uris:转义部分URL / URI的方法 #conversions:用于执行已配置的转换服务的方法(如果有)。 #dates:java.util.Date对象的方法:格式化,组件提取等。 #calendars:类似于#dates,但java.util.Calendar对象。 #numbers:格式化数字对象的方法。 #strings:String对象的方法:contains,startsWith,prepending / appending等。 #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());

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

    Today is: 13 May 2011

    关文本

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

    变量表达式不仅可以写成${...},也可以作为*{...}。

    但是有一个重要的区别:星号语法评估所选对象上的表达式而不是整个上下文。也就是说,只要没有选定的对象,美元和星号语法就会完全相同。

    什么是选定的对象?使用th:object属性的表达式的结果。我们在用户个人资料(userprofile.html)页面中使用一个:

    Name: Sebastian.

    Surname: Pepper.

    Nationality: Saturn.

    这完全等同于:

    Name: Sebastian.

    Surname: Pepper.

    Nationality: Saturn.

    当然,美元和星号语法可以混合使用:

    Name: Sebastian.

    Surname: Pepper.

    Nationality: Saturn.

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

    Name: Sebastian.

    Surname: Pepper.

    Nationality: Saturn.

    如上所述,如果没有执行任何对象选择,则美元和星号语法是等效的。

    Name: Sebastian.

    Surname: Pepper.

    Nationality: Saturn.

    4.4链接URL

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

    有不同类型的网址:

    绝对网址://www.thymeleaf.org 相对URL,可以是:

    页面相对:user/login.html 上下文相关:(/itemdetailsid=3服务器中的上下文名称将自动添加) 服务器相对:(~/billing/processInvoice允许在同一服务器中的另一个上下文(=应用程序)中调用URL。 协议相对URL://code.jquery.com/jquery-2.0.3.min.js

    这些表达式的实际处理及其转换为将要输出的URL是通过org.thymeleaf.linkbuilder.ILinkBuilder注册到ITemplateEngine正在使用的对象中的接口的实现来完成的。

    默认情况下,此接口的单个实现是在类中注册的org.thymeleaf.linkbuilder.StandardLinkBuilder,这对于基于Servlet API的脱机(非Web)和Web方案都是足够的。其他方案(例如与非ServletAPI Web框架的集成)可能需要链接构建器接口的特定实现。

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

    view

    view

    view

    这里要注意的一些事情:

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

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

    view

    view

    我们主页的菜单

    既然我们知道如何创建链接URL,那么在我们的主页中为网站中的其他一些页面添加一个小菜单呢?

    Please select an option

    Product List

    Order List

    Subscribe to our Newsletter

    See User Profile

    服务器根目录相对URL

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

    4.5片段

    片段表达式是表示标记片段并将其移动到模板周围的简单方法。这允许我们复制它们,将它们作为参数传递给其他模板,等等。

    最常见的用途是使用th:insert或进行片段插入th:replace(在后面的部分中将详细介绍):

    ...

    但它们可以在任何地方使用,就像任何其他变量一样:

    本教程后面有一整节专门介绍模板布局,包括对片段表达式的更深入解释。

    4.6文字

    文字文字

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

    Now you are looking at a template file.

    数字文字

    数字文字只是:数字。

    The year is 1492.

    In two years, it will be 1494.

    布尔文字

    布尔文字是true和false。例如:

    ...

    在这个例子中,它== false被写在大括号外面,因此是Thymeleaf来处理它。如果它是在大括号内写的,那么它将由OGNL / SpringEL引擎负责:

    ...

    null文字

    该null文本也可用于:

    ...

    文字代币

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

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

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

    ...

    代替:

    ...

    和变量的

    4.7附加文本

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

    4.8字面替换

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

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

    这相当于:

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

    唯一的变量/消息表达式(${...},*{...},#{...})被允许内部|...|字面取代。没有其他文字('...'),布尔/数字标记,条件表达式等。

    4.9算术运算

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

    请注意,这些运算符也可以应用于OGNL变量表达式本身(在这种情况下,将由OGNL而不是Thymeleaf标准表达式引擎执行):

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

    4.10比较器和平等

    在表达式中的值可以与进行比较>,<,>=和<=符号,以及==和!=运营商可以被用来检查是否相等(或缺乏)。请注意,XML确定不应在属性值中使用<和>符号,因此应将它们替换为<和>。

    更简单的替代方法可能是使用某些运算符存在的文本别名:gt(>),lt(<),ge(>=),le(<=),not(!)?;筫q(==),neq/ne(!=)。

    4.11条件表达式

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

    让我们来看一个例子片段(引入另一个属性修改器,th:class):

    ...

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

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

    ...

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

    ...

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

    一个默认的表情是一种特殊的条件值的没有那么一部分。它等同于某些语言(如Groovy)中存在的Elvis运算符,允许您指定两个表达式:如果它不计算为null,则使用第一个表达式,但如果确实如此,则使用第二个表达式。

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

    ...

    Age: 27.

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

    Age: 27.

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

    Name: Sebastian

    4.13无操作令牌

    No-Operation标记由下划线符号(_)表示。

    这个标记背后的想法是指定表达式的期望结果是什么都不做,即完全就像可处理属性(例如th:text)根本不存在一样。

    除了其他可能性之外,这允许开发人员将原型文本用作默认值。例如,而不是:

    ...

    ...我们可以直接使用“无用户身份验证”作为原型文本,从设计的角度来看,这会使代码更简洁,更通用:

    no user authenticated

    4.14数据转换/格式化

    Thymeleaf为variable()和selection()表达式定义了一个双括号语法,允许我们通过配置的转换服务应用数据转换。${...}*{...}

    它基本上是这样的:

    ...

    注意到那里的双支撑?:${{...}}。这指示Thymeleaf将user.lastAccessDate表达式的结果传递给转换服务,并要求它在写入结果之前执行格式化操作(转换为String)。

    假设它user.lastAccessDate是类型java.util.Calendar,如果已经注册了转换服务(实现IStandardConversionService)并且包含有效的转换Calendar -> String,则将应用它。

    IStandardConversionService(StandardConversionService类)的默认实现只是.toString()在转换为的任何对象上执行String。有关如何注册自定义转换服务实现的更多信息,请查看“更多配置”部分。

    官方thymeleaf-spring3和thymeleaf-spring4集成软件包的透明集成了Spring自己Thymeleaf的转换服务机制转换服务的基础设施,所以在Spring配置宣称,转换服务和格式化将进行自动获得${{...}}和*{{...}}表达。

    4.15预处理

    除了用于表达式处理的所有这些功能外,Thymeleaf还具有预处理表达式的功能。

    预处理是在正常表达式之前完成的表达式的执行,它允许修改最终将被执行的表达式。

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

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

    [email protected]@translateToFrench({0})

    ......和aMessages_es.properties equivalent:

    [email protected]@translateToSpanish({0})

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

    Some text here...

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

    Some text here...

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

    5设置属性值

    本章将解释我们在标记中设置(或修改)属性值的方式。

    5.1设置任何属性的值

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

    与Thymeleaf一样,此模板更像是静态原型,而不是Web应用程序的模板。首先,action我们表单中的属性静态链接到模板文件本身,因此没有地方可以进行有用的URL重写。其次,value提交按钮中的属性使其显示英文文本,但我们希望它能够国际化。

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

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

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

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

     

    \

     

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

     

    Logo de Good Thymes

     

    5.2为特定属性设置值

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

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

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

    例如,要设置value属性,请使用th:value:

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

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

    Product List

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

    th:abbrth:acceptth:accept-charset

    th:accesskeyth:actionth:align

    th:altth:archiveth:audio

    th:autocompleteth:axisth:background

    th:bgcolorth:borderth:cellpadding

    th:cellspacingth:challengeth:charset

    th:citeth:classth:classid

    th:codebaseth:codetypeth:cols

    th:colspanth:compactth:content

    th:contenteditableth:contextmenuth:data

    th:datetimeth:dirth:draggable

    th:dropzoneth:enctypeth:for

    th:formth:formactionth:formenctype

    th:formmethodth:formtargetth:fragment

    th:frameth:frameborderth:headers

    th:heightth:highth:href

    th:hreflangth:hspaceth:http-equiv

    th:iconth:idth:inline

    th:keytypeth:kindth:label

    th:langth:listth:longdesc

    th:lowth:manifestth:marginheight

    th:marginwidthth:maxth:maxlength

    th:mediath:methodth:min

    th:nameth:onabortth:onafterprint

    th:onbeforeprintth:onbeforeunloadth:onblur

    th:oncanplayth:oncanplaythroughth:onchange

    th:onclickth:oncontextmenuth:ondblclick

    th:ondragth:ondragendth:ondragenter

    th:ondragleaveth:ondragoverth:ondragstart

    th:ondropth:ondurationchangeth:onemptied

    th:onendedth:onerrorth:onfocus

    th:onformchangeth:onforminputth:onhashchange

    th:oninputth:oninvalidth:onkeydown

    th:onkeypressth:onkeyupth:onload

    th:onloadeddatath:onloadedmetadatath:onloadstart

    th:onmessageth:onmousedownth:onmousemove

    th:onmouseoutth:onmouseoverth:onmouseup

    th:onmousewheelth:onofflineth:ononline

    th:onpauseth:onplayth:onplaying

    th:onpopstateth:onprogressth:onratechange

    th:onreadystatechangeth:onredoth:onreset

    th:onresizeth:onscrollth:onseeked

    th:onseekingth:onselectth:onshow

    th:onstalledth:onstorageth:onsubmit

    th:onsuspendth:ontimeupdateth:onundo

    th:onunloadth:onvolumechangeth:onwaiting

    th:optimumth:patternth:placeholder

    th:posterth:preloadth:radiogroup

    th:relth:revth:rows

    th:rowspanth:rulesth:sandbox

    th:schemeth:scopeth:scrolling

    th:sizeth:sizesth:span

    th:spellcheckth:srcth:srclang

    th:standbyth:startth:step

    th:styleth:summaryth:tabindex

    th:targetth:titleth:type

    th:usemapth:valueth:valuetype

    th:vspaceth:widthth:wrap

    th:xmlbaseth:xmllangth:xmlspace

    5.3一次设置多个值

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

    th:alt-title将设置alt和title。 th:lang-xmllang将设置lang和xml:lang。

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

     

    \

     

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

     

    \

     

    …有了这个:

     

    \

     

    5.4附加和预先

    Thymeleaf还提供了th:attrappend和th:attrprepend属性,它们将评估结果附加(后缀)或前置(前缀)到现有属性值。

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

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

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

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

    5.5固定值布尔属性

    HTML具有布尔属性的概念,没有值的属性和一个值的前提意味着值为“true”。在XHTML中,这些属性只占用1个值,这本身就是一个值。

    例如,checked:

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

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

    th:asyncth:autofocusth:autoplay

    th:checkedth:controlsth:declare

    th:defaultth:deferth:disabled

    th:formnovalidateth:hiddenth:ismap

    th:loopth:multipleth:novalidate

    th:nowrapth:openth:pubdate

    th:readonlyth:requiredth:reversed

    th:scopedth:seamlessth:selected

    更多信息

    5.6设置任何属性的值(默认属性处理器)

    Thymeleaf提供了一个默认属性处理器,允许我们设置任何属性的值,即使th:*在标准方言中没有为它定义特定的处理器。

    所以类似于:

    ...

    将导致:

    ...

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

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

    ......

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

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

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

    6迭代

    到目前为止,我们已经创建了一个主页,一个用户个人资料页面以及一个允许用户订阅我们的新闻通讯的页面......但是我们的产品呢?为此,我们需要一种方法来迭代集合中的项目以构建我们的产品页面。

    6.1迭代基础知识

    要在我们的/WEB-INF/templates/product/list.html页面中显示产品,我们将使用表格。我们的每个产品都会连续显示(一个元素),因此对于我们的模板,我们需要创建一个模板行- 一个可以说明我们希望如何显示每个产品的模板行- 然后指示Thymeleaf重复它,每个产品一次。

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

    使用th:each

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

    public void process(

    final HttpServletRequest request, final HttpServletResponse response,

    final ServletContext servletContext, final ITemplateEngine templateEngine)

    throws Exception {

    ProductService productService = new ProductService();

    List 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在我们的模板中使用迭代产品列表:

    也就是说prod : ${prods}您在上面看到的属性值是指“用于评估的结果的每个元素${prods},重复模板的该片段,用在变量称为刺当前元素”。让我们给出一个我们看到的每个事物的名称:

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

    请注意,proditer变量的作用域是元素,这意味着它可用于内部标记。

    可重复的值

    该java.util.List班是不是可以用于Thymeleaf迭代onlyvalue。有一组非常完整的对象被属性认为是可迭代的th:each:

    任何对象实现java.util.Iterable 任何对象实现java.util.Enumeration。 任何实现的对象java.util.Iterator,其值将在迭代器返回时使用,而不需要将所有值缓存在内存中。 任何对象实现java.util.Map。迭代地图时,iter变量将属于类java.util.Map.Entry。 任何数组。 任何其他对象都将被视为包含对象本身的单值列表。

    6.2保持迭代状态

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

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

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

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

    NAMEPRICEIN STOCK

    Onions2.41yes

    状态变量(iterStat在此示例中)在th:each属性中通过在iter变量本身之后写入其名称来定义,用逗号分隔。就像iter变量一样,状态变量的范围也限定为由包含该th:each属性的标记定义的代码片段。

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

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

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

    NAMEPRICEIN STOCK

    Onions2.41yes

    6.3通过延迟检索数据进行优化

    有时我们可能希望优化数据集合的检索(例如,从数据库中),以便只有在真正使用它们时才会检索这些集合。

    实际上,这可以应用于任何数据片段,但考虑到内存中集合可能具有的大小,检索要迭代的集合是此方案的最常见情况。

    为了支持这一点,Thymeleaf提供了一种懒惰加载上下文变量的机制。实现ILazyContextVariable接口的上下文变量- 最有可能通过扩展其LazyContextVariable默认实现 - 将在执行时解决。例如:

    context.setVariable(

    "users",

    new LazyContextVariable>() {

    @Override

    protected List loadValue() {

    return databaseRepository.findAllUsers();

    }

    });

    可以在不知道其惰性的情况下使用此变量,例如:

    user name

    但与此同时,loadValue()如果在代码中condition进行求值,则永远不会被初始化(它的方法永远不会被调用)false:

    user name

    7条件评估

    7.1简单条件:“if”和“除非”

    有时,如果满足某个条件,您将需要模板的片段才会出现在结果中。

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

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

    NAMEPRICEIN STOCKCOMMENTS

    Onions2.41yes2 comment/s view

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

    view

    这将创建指向评论页面(带有URL/product/comments)的链接,其prodId参数设置为id产品的参数,但仅限于产品有任何评论。

    我们来看看生成的标记:

    NAMEPRICEIN STOCKCOMMENTS

    Fresh Sweet Basil4.99yes0 comment/s

    Italian Tomato1.25no2 comment/s view

    Yellow Bell Pepper2.50yes0 comment/s

    Old Cheddar18.75yes1 comment/s view

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

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

    如果value不为null:

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

    此外,th:if还有一个inverse属性,th:unless我们可以在前面的示例中使用它,而不是not在OGNL表达式中使用:

    view

    7.2切换语句

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

    User is an administrator

    User is a manager

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

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

    User is an administrator

    User is a manager

    User is some other thing

    8模板布局

    8.1包括模板片段

    定义和引用片段

    在我们的模板中,我们经常需要包含其他模板中的部分,页脚,标题,菜单等部分......

    为了做到这一点,Thymeleaf需要我们定义这些部分,“片段”,以便包含,这可以使用th:fragment属性来完成。

    假设我们要在所有杂货页面上添加标准版权页脚,因此我们创建一个/WEB-INF/templates/footer.html包含以下代码的文件:

    上面的代码定义了一个名为的片段copy,我们可以使用其中一个th:insert或th:replace属性轻松地在我们的主页中包含这些片段(并且th:include,尽管自Thymeleaf 3.0以来不再推荐使用它):

    请注意,th:insert需要一个片段表达式(~{...}),它是一个导致片段的表达式。在上面的例子中,这是一个非复杂的片段表达式,(~{,})封闭是完全可选的,所以上面的代码相当于:

    片段规范语法

    片段表达式的语法非常简单。有三种不同的格式:

    "~{templatename::selector}"包括在名为的模板上应用指定标记选择器而产生的片段templatename。请注意,selector可以仅仅是一个片段的名字,所以你可以指定为简单的东西~{templatename::fragmentname}就像在~{footer :: copy}上面。

    标记选择器语法由底层的AttoParser解析库定义,类似于XPath表达式或CSS选择器。有关详细信息,请参阅附录C.

    "~{templatename}"包含名为的完整模板templatename。

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

    ~{::selector}"或"~{this::selector}"插入来自同一模板的片段,进行匹配selector。如果在表达式出现的模板上找不到,则模板调用(插入)的堆栈将遍历最初处理的模板(根),直到selector在某个级别匹配。

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

    再次注意周围的~{...}包络在th:insert/中是如何可选的th:replace。

    片段可以包含任何th:*属性。一旦将片段包含在目标模板(具有th:insert/th:replaceattribute的模板)中,就会评估这些属性,并且它们将能够引用此目标模板中定义的任何上下文变量。

    这种片段方法的一大优点是,您可以在浏览器完全可显示的页面中编写片段,具有完整且有效的标记结构,同时仍保留使Thymeleaf将其包含在其他模板中的能力。

    没有引用片段th:fragment

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

    ...

    © 2011 The Good Thymes Virtual Grocery

    ...

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

    th:insert和th:replace(和th:include)之间的区别

    和之间有什么区别th:insert和th:replace(和th:include,因为3.0不推荐)?

    th:insert是最简单的:它只是插入指定的片段作为其主机标签的主体。

    th:replace实际上用指定的片段替换它的主机标签。

    th:include类似于th:insert,但不是插入片段,它只插入此片段的内容。

    所以像这样的HTML片段:

    © 2011 The Good Thymes Virtual Grocery

    ...在主机

    标签中包含三次,如下所示:

    ......将导致:

    8.2可参数化的片段签名

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

    ...

    这需要使用这两种语法之一来从th:insert或调用片段th:replace:

    ...

    ...

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

    ...

    片段局部变量没有片段参数

    即使片段定义没有这样的参数:

    ...

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

    这将相当于组合th:replace和th:with:

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

    th:断言in-template断言

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

    ...

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

    ...

    8.3灵活的布局:仅仅是片段插入

    由于片段表达式,我们可以为不是文本,数字,bean对象的片段指定参数......而是指定标记片段。

    这允许我们以一种方式创建我们的片段,使得它们可以通过来自调用模板的标记来丰富,从而产生非常灵活的模板布局机制。

    注意在下面的片段中使用title和links变量:

    我们现在可以将这个片段称为:

    ...

    ...

    ...结果将使用我们的调用模板中的实际

    标签作为title和links变量的值,从而导致我们的片段在插入过程中自定义:

    ...

    ...

    使用空片段

    一个特殊的片段表达式,即空片段(~{}),可用于指定无标记。使用前面的示例:

    ...

    注意fragment(links)的第二个参数是如何设置为空片段的,因此没有为块写入任何内容:

    ...

    ...

    使用无操作令牌

    如果我们只想让我们的片段使用其当前标记作为默认值,则no-op也可以用作片段的参数。再次,使用common_header示例:

    ...

    ...

    看看如何将title参数(common_header片段的第一个参数)设置为no-op(_),这导致片段的这一部分根本不被执行(title=无操作):

    结果是:

    ...

    ...

    高级条件插入片段

    emtpy片段和无操作令牌的可用性允许我们以非常简单和优雅的方式执行片段的条件插入。

    例如,我们可以这样做,以便仅在用户是管理员时插入我们的common :: adminhead片段,并且如果不是,则不插入任何内容(emtpy片段):

    ...

    ...

    ...

    此外,我们可以使用无操作令牌,以便仅在满足指定条件时插入片段,但如果不满足条件则保留标记而不进行修改:

    ...

    Welcome [[${user.name}]], click here for help-desk support.

    ...

    另外,如果我们已经配置了模板解析器来检查模板资源是否存在- 通过它们的checkExistence标志 - 我们可以使用片段本身的存在作为默认操作中的条件:

    ...

    Welcome [[${user.name}]], click here for help-desk support.

    ...

    8.4删除模板片段

    回到示例应用程序,让我们重新访问我们的产品列表模板的最新版本:

    NAMEPRICEIN STOCKCOMMENTS

    Onions2.41yes2 comment/s view

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

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

    所以让我们添加一些:

    NAMEPRICEIN STOCKCOMMENTS

    Onions2.41yes2 comment/s view

    Blue Lettuce9.55no0 comment/s

    Mild Cinnamon1.99yes3 comment/s view

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

    NAMEPRICEIN STOCKCOMMENTS

    Fresh Sweet Basil4.99yes0 comment/s

    Italian Tomato1.25no2 comment/s view

    Yellow Bell Pepper2.50yes0 comment/s

    Old Cheddar18.75yes1 comment/s view

    Blue Lettuce9.55no0 comment/s

    Mild Cinnamon1.99yes3 comment/s view

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

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

    NAMEPRICEIN STOCKCOMMENTS

    Onions2.41yes2 comment/s view

    Blue Lettuce9.55no0 comment/s

    Mild Cinnamon1.99yes3 comment/s view

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

    NAMEPRICEIN STOCKCOMMENTS

    Fresh Sweet Basil4.99yes0 comment/s

    Italian Tomato1.25no2 comment/s view

    Yellow Bell Pepper2.50yes0 comment/s

    Old Cheddar18.75yes1 comment/s view

    all属性中的这个值是什么意思?th:remove可以根据其价值以五种不同的方式表现:

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

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

    NAMEPRICEIN STOCKCOMMENTS

    Onions2.41yes2 comment/s view

    Blue Lettuce9.55no0 comment/s

    Mild Cinnamon1.99yes3 comment/s view

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

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

    Link text not to be removed

    另请注意,th:remove考虑null到同义词none,因此以下工作方式与上面的示例相同:

    Link text not to be removed

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

    8.5布局继承

    为了能够将单个文件作为布局,可以使用片段。具有title和content使用th:fragment和的简单布局示例th:replace:

    此示例声明了一个名为layout的片段,其中title和content作为参数。在下面的示例中,两者都将在页面上替换,并通过提供的片段表达式继承它。

    在这个文件中,该html标签将被替换的布局,但在布局title和content将已被替换title,并section分别块。

    如果需要,布局可以由多个片段组成页眉和页脚。

    9局部变量

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

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

    ...

    该prod变量仅在标记的范围内可用。特别:

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

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

    The name of the first person is Julius Caesar.

    当th:with被处理时,该firstPer变量被创建为一个局部变量,并加入到变量映射从上下文来,使得它可用于评估与在上下文中声明的任何其它变量一起,但仅在含有的边界

    标记。

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

    The name of the first person is Julius Caesar.

    But the name of the second person is Marcus Antonius.

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

    ...

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

    Today is: 13 february 2011

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

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

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

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

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

    Today is: 13 February 2011

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

    Today is: 13 February 2011

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

    10属性优先级

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

    Item description here...

    我们希望该th:each属性在之前执行,th:text以便我们得到我们想要的结果,但是考虑到HTML / XML标准没有给标签中的属性写入的顺序赋予任何意义,优先级必须在属性本身中建立机制,以确保这将按预期工作。

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

    订购特征属性

    1片段包含th:insert

    th:replace

    2片段迭代th:each

    3有条件的评估th:if

    th:unless

    th:switch

    th:case

    4局部变量定义th:object

    th:with

    五一般属性修改th:attr

    th:attrprepend

    th:attrappend

    6具体属性修改th:value

    th:href

    th:src

    ...

    7文字(标签正文修改)th:text

    th:utext

    8片段规范th:fragment

    9片段删除th:remove

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

    Item description here...

    11评论和块

    11.1。标准HTML / XML注释

    标准HTML / XML注释可以在Thymeleaf模板中的任何位置使用。Thymeleaf将不会处理这些评论中的任何内容,并将逐字复制到结果中:

    ...

    11.2。Thymeleaf解析器级注释块

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

    Thymeleaf将删除一切与,所以这些注释块也可以用于显示当模板是静态开放代码,知道当Thymeleaf处理它,它都将被删除:

    you can see me only before Thymeleaf processes me!

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

    ...

    ...

    ...

    11.3。Thymeleaf原型评论块

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

    hello!

    goodbye!

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

    hello!

    ...

    goodbye!

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

    11.4。合成th:block标签

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

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

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

    ......

    ...

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

    ......

    ...

    注意这个解决方案如何让模板成为有效的HTML(不需要在

    里面添加禁止的块),并且在浏览器中作为原型静态打开时仍然可以正常工作!

    12内联

    12.1表达内联

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

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

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

    Hello, Sebastian!

    在Thymeleaf之间表达[[...]]或被[(...)]认为是内联表达式,在其中我们可以使用任何类型的表达式,这些表达式在一个th:text或th:utext属性中也是有效的。

    请注意,虽然[[...]]对应于th:text(即结果将被HTML转义),但是[(...)]对应于th:utext并且不会执行任何HTML转义。所以对于一个变量,如msg = 'This is great!'给定这个片段:

    The message is "[(${msg})]"

    结果将使这些标签不转义,因此:

    The message is "This is great!"

    而如果像以下一样逃脱:

    The message is "[[${msg}]]"

    结果将被HTML转义:

    The message is "This is great!"

    请注意,默认情况下,文本内联在标记中的每个标记的主体中都是活动的- 而不是标记本身 - 因此我们无需执行任何操作即可启用它。

    内联vs自然模板

    如果你来自其他模板引擎,其中这种输出文本的方式是常态,你可能会问:为什么我们从一开始就不这样做?它的代码少于所有这些th:text属性!

    好吧,小心那里,因为虽然你可能会发现内联非常有趣,但你应该永远记住,当你静态打开它们时,内联表达式将逐字显示在你的HTML文件中,所以你可能无法将它们用作设计原型了!

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

    Hello, Sebastian!

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

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

    ......在设计实用性方面非常清楚。

    禁用内联

    但是,可以禁用此机制,因为实际上可能存在我们确实希望输出[[...]]或[(...)]序列而不将其内容作为表达式处理的情况。为此,我们将使用th:inline="none":

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

    这将导致:

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

    12.2文字内联

    文本内联与我们刚刚看到的表达内联功能非常相似,但它实际上增加了更多功能。它必须明确启用th:inline="text"。

    文本内联不仅允许我们使用我们刚才看到的相同内联表达式,而且实际上处理标签主体就好像它们是在TEXT模板模式下处理的模板一样,这允许我们执行基于文本的模板逻辑(不仅仅是输出表达式)。

    我们将在下一章中看到有关文本模板模式的更多信息。

    12.3 JavaScript内联

    JavaScript内联允许

    这将导致:

    上面代码中需要注意的两件重要事项:

    首先,JavaScript内联不仅会输出所需的文本,而且还会用引号和JavaScript来包含它 - 转义其内容,以便将表达式结果输出为格式良好的JavaScript文字。

    其次,发生这种情况是因为我们将${session.user.name}表达式输出为转义,即使用双括号表达式:[[${session.user.name}]]。如果相反,我们使用非转义,如:

    结果如下:

    ...这是一个格式错误的JavaScript代码。但是,如果我们通过附加内联表达式来构建脚本的一部分,那么输出未转义的内容可能就是我们所需要的,因此最好有这个工具。

    JavaScript自然模板

    所提到的JavaScript内联机制的智能远不止仅仅应用特定于JavaScript的转义并将表达式结果输出为有效文字。

    例如,我们可以在JavaScript注释中包装我们的(转义的)内联表达式,例如:

    并且Thymeleaf将忽略我们在注释之后和分号之前(在这种情况下'Gertrud Kiwifruit')编写的所有内容,因此执行此操作的结果看起来与我们不使用包装注释时的结果完全相同:

    但请仔细查看原始模板代码:

    请注意这是有效的JavaScript代码。当您以静态方式打开模板文件时(无需在服务器上执行),它将完美执行。

    所以我们这里有一个做自然模板的方法!

    高级内联评估和JavaScript序列化

    关于JavaScript内联的一个重要注意事项是,这种表达式评估是智能的,不仅限于字符串。Thymeleaf将在JavaScript语法中正确编写以下类型的对象:

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

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

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

    这种JavaScript序列化的方式是通过org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer接口的实现,可以StandardDialect在模板引擎使用的实例上配置。

    此JS序列化机制的默认实现将在类路径中查找Jackson库,如果存在,将使用它。如果没有,它将应用内置的序列化机制,涵盖大多数场景的需求并产生类似的结果(但不太灵活)。

    12.4 CSS内联

    Thymeleaf还允许在CSS

    例如,假设我们将两个变量设置为两个不同的String值:

    classname = 'main elems'

    align = 'center'

    我们可以像以下一样使用它们:

    结果将是:

    请注意CSS内联如何具有一些智能,就像JavaScript一样。具体来说,通过转义表达式输出的表达式[[${classname}]]将作为CSS标识符进行转义。这就是为什么我们classname = 'main elems'已经main\ elems在上面的代码片段中变成了原因。

    高级功能:CSS自然模板等

    与之前针对JavaScript解释的内容相同,CSS内联还允许我们的

    13文本模板模式

    13.1文本语法

    在Thymeleaf的三种模板模式被认为是文字:TEXT,JAVASCRIPT和CSS。这使它们与标记模板模式区别开来:HTML和XML。

    文本模板模式与标记模式之间的关键区别在于,在文本模板中没有标签可以以属性的形式插入逻辑,因此我们必须依赖其他机制。

    这些机制中的第一个也是最基本的是内联,我们已在前一章中详细介绍过。内联语法是在文本模板模式下输出表达式结果的最简单方法,因此这是一个完全有效的文本电子邮件模板。

    Dear [(${name})],

    Please find attached the results of the report you requested

    with name "[(${report.name})]".

    Sincerely,

    The Reporter.

    即使没有标签,上面的例子也是一个完整有效的Thymeleaf模板,可以在TEXT模板模式下执行。

    但是为了包含比仅仅输出表达式更复杂的逻辑,我们需要一种新的基于非标记的语法:

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

    - [(${item})]

    [/]

    这实际上是更详细的浓缩版本:

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

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

    [/th:block]

    请注意这个新语法是如何基于声明为[#element ...]而不是的元素(即可处理标记)。元素是开放的像[#element ...]封闭一样[/element],并且可以通过最小化open元素来声明独立标签/,其方式几乎等同于XML标签:[#element ... /]。

    标准方言只包含其中一个元素的处理器:已知的th:block,虽然我们可以在我们的方言中扩展它并以通常的方式创建新元素。此外,允许将th:blockelement([#th:block ...] ... [/th:block])缩写为空字符串([# ...] ... [/]),因此上面的块实际上等效于:

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

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

    [/]

    并且给定[# th:utext="${item}" /]等效于内联非转义表达式,我们可以使用它来获得更少的代码。因此,我们最终得到了上面看到的第一个代码片段:

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

    - [(${item})]

    [/]

    请注意,文本语法需要完整的元素平衡(没有未关闭的标记)和引用的属性- 它比HTML风格更加XML风格。

    让我们看一个更完整的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页面调用结果。请注意,这不是

    点击复制链接 与好友分享!回本站首页
    相关TAG标签 Thymeleaf 教程 模板
    上一篇:jQuery、ajax添加Json数据
    下一篇:JavaScript的原型对象
    相关文章
    图文推荐
    点击排行

    关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 神奇公式秒杀全国11选5

    版权所有: 神奇公式秒杀全国11选5--致力于做实用的IT技术学习网站

  • 监察体制改革后 湘西半年72名公职人员主动交代问题 2019-05-12
  • 媒体宣传报道重庆日报 王国平:扮靓重庆两江四岸” 让城市有机更新 2019-04-26
  • 我相信“交警雨中护送高考生”是真,“交警雨中护送高考生”反被该高考生家长投诉是假。 2019-04-16
  • 14名消防员日巡逻28公里 洗冷水澡 2019-04-10
  • 靶壕有了“蓝军”,百发百中的“神枪手”练起来 2019-04-10
  • 不是秀强大了,别人就会来做朋友,这逻辑不对 2019-04-01
  • 候选企业:中国石油呼和浩特石化公司 2019-03-26
  • 航天员沙漠野外生存训练完美收官!为第一天团打call 2019-03-25
  • 请问,建立市场经济后,原计划经济哪里去?改革后,我们还在实行计划经济,为何没有提及? 2019-03-25
  • 构建年轻干部梯次培养链 2019-03-19
  • 孙实的专栏作者中国国家地理网 2019-03-15
  • 湖南师范大学举行研究阐释党的十九大精神国家社科基金重大专项学术研讨会 2019-03-15
  • [雷人]蠢货!土地处于不同的城市和地段,关联的资源不一样,价值也不一样。不然给咱俩同样面积的土地,咱的在北上广深,你的在边远山区,你干么? 2019-03-08
  • 国际社会持续热议上合青岛峰会:上合组织发展进入新阶段 彰显中国领导力 2019-03-08
  • 珍惜野生动物频现甘孜境内 生态环境质量不断提升 2019-03-06