迭代


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

6.1迭代基础知识

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

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

Using 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},重复模板的该片段,用在变量称为刺当前元素”。让我们给出一个名称:

  • We will call ${prods} the iterated expression or iterated variable.
  • We will call prod the iteration variable or simply iter variable.

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

Iterable values

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

  • Any object implementing java.util.Iterable
  • Any object implementing java.util.Enumeration.
  • Any object implementing java.util.Iterator, whose values will be used as they are returned by the iterator, without the need to cache all values in memory.
  • Any object implementing java.util.Map. When iterating maps, iter variables will be of class java.util.Map.Entry.
  • Any array.
  • Any other object will be treated as if it were a single-valued list containing the object itself.

6.2Keeping iteration status

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

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

  • The current iteration index, starting with 0. This is the index property.
  • The current iteration index, starting with 1. This is the count property.
  • The total amount of elements in the iterated variable. This is the size property.
  • The iter variable for each iteration. This is the current property.
  • Whether the current iteration is even or odd. These are the even/odd boolean properties.
  • Whether the current iteration is the first one. This is the first boolean property.
  • Whether the current iteration is the last one. This is the last boolean property.

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

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

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

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

<!DOCTYPE html>

<html>

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

  <body>

    <h1>Product list</h1>

    <table>
      <tr>
        <th>NAME</th>
        <th>PRICE</th>
        <th>IN STOCK</th>
      </tr>
      <tr class="odd">
        <td>Fresh Sweet Basil</td>
        <td>4.99</td>
        <td>yes</td>
      </tr>
      <tr>
        <td>Italian Tomato</td>
        <td>1.25</td>
        <td>no</td>
      </tr>
      <tr class="odd">
        <td>Yellow Bell Pepper</td>
        <td>2.50</td>
        <td>yes</td>
      </tr>
      <tr>
        <td>Old Cheddar</td>
        <td>18.75</td>
        <td>yes</td>
      </tr>
    </table>

    <p>
      <a href="/gtvg/" shape="rect">Return to home</a>
    </p>

  </body>

</html>

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

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

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

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

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

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

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

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

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

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

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

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