第一部分第三部分5显示种子启动器数据
我们的
/WEB-INF/templates/seedstartermng.html页面
显示的第一件事是一个列表,其中包含当前保存的初始起始数据。 为此,我们需要一些外部消息以及模型属性的某些表达式。 像这样:
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}"> <h2 th:text="#{title.list}">List of Seed Starters</h2> <table> <thead> <tr> <th th:text="#{seedstarter.datePlanted}">Date Planted</th> <th th:text="#{seedstarter.covered}">Covered</th> <th th:text="#{seedstarter.type}">Type</th> <th th:text="#{seedstarter.features}">Features</th> <th th:text="#{seedstarter.rows}">Rows</th> </tr> </thead> <tbody> <tr th:each="sb : ${allSeedStarters}"> <td th:text="${{sb.datePlanted}}">13/01/2011</td> <td th:text="#{|bool.${sb.covered}|}">yes</td> <td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td> <td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td> <td> <table> <tbody> <tr th:each="row,rowStat : ${sb.rows}"> <td th:text="${rowStat.count}">1</td> <td th:text="${row.variety.name}">Thymus Thymi</td> <td th:text="${row.seedsPerCell}">12</td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div>
有很多值得一看的地方。 让我们分别看每个片段。
首先,只有有种子启动器时,才会显示此部分。 我们通过
th:never属性和
#lists.isEmpty(...)函数来实现这一点。
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}">
请注意,所有实用程序对象(例如
#lists )都可以在Spring EL表达式中以与标准方言中OGNL表达式相同的方式使用。
接下来要看的是很多国际化(外部化)的文本,例如:
<h2 th:text =“#{title.list}”>种子启动器列表
<table> <thead> <tr> <th th:text="#{seedstarter.datePlanted}">Date Planted</th> <th th:text="#{seedstarter.covered}">Covered</th> <th th:text="#{seedstarter.type}">Type</th> <th th:text="#{seedstarter.features}">Features</th> <th th:text="#{seedstarter.rows}">Rows</th> ...
这是一个Spring MVC应用程序,我们已经在Spring配置中定义了
MessageSource bean (
MessageSource对象是控制Spring MVC中外部文本的标准方法):
@Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("Messages"); return messageSource; }
...,并且此
basename属性指示我们将在类路径中包含文件,例如
Messages_es.properties或
Messages_en.properties 。 让我们看一下西班牙语版本:
title.list=Lista de semilleros date.format=dd/MM/yyyy bool.true=sí bool.false=no seedstarter.datePlanted=Fecha de plantación seedstarter.covered=Cubierto seedstarter.type=Tipo seedstarter.features=Características seedstarter.rows=Filas seedstarter.type.WOOD=Madera seedstarter.type.PLASTIC=Plástico seedstarter.feature.SEEDSTARTER_SPECIFIC_SUBSTRATE=Sustrato específico para semilleros seedstarter.feature.FERTILIZER=Fertilizante seedstarter.feature.PH_CORRECTOR=Corrector de PH
在表格的第一列中,我们显示了启动器的准备日期。 但是
我们将显示它的格式与我们在
DateFormatter中定义的相同。 为此,我们将使用双括号语法(
$ {{...}} ),该语法将自动应用Spring转换服务,包括我们在设置时注册的DateFormatter。
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
下面通过使用文字查找表达式将布尔覆盖的bin的属性值转换为国际化的yes或no,来显示是否覆盖种子启动器种子容器:
<td th:text="#{|bool.${sb.covered}|}">yes</td>
现在我们需要显示初始种子启动器容器的类型。 该类型是具有两个值(
WOOD和
PLASTIC )的java枚举,因此我们在
Messages文件中定义了两个属性,名称分别为
seedstarter.type.WOO D和
seedstarter.type.PLASTIC 。
但是为了获得国际化的类型名称,我们需要添加
seedstarter.type。 使用表达式为enum值添加前缀,然后将其结果用作消息键:
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
此列表中最难的部分是
功能列。 在其中,我们要显示容器的所有功能,这些
功能以
功能枚举的形式表示,以逗号分隔。 就像“
电暖气,草坪 。”
请注意,这特别困难,因为像我们对Types所做的那样,还必须推断这些枚举值。 输出流如下:
- 用features数组的所有元素替换适当的前缀。
- 接收与步骤1中的所有键匹配的外部消息。
- 附加在步骤2中接收到的所有消息,使用逗号作为分隔符。
为此,我们创建以下代码:
<td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td>
我们列表的最后一列实际上将非常简单。 即使它具有嵌套表以显示容器中每一行的内容:
<td> <table> <tbody> <tr th:each="row,rowStat : ${sb.rows}"> <td th:text="${rowStat.count}">1</td> <td th:text="${row.variety.name}">Thymus Thymi</td> <td th:text="${row.seedsPerCell}">12</td> </tr> </tbody> </table> </td>
6创建表格
6.1处理命令对象
命令对象是Spring MVC为表单支持bean提供的名称,即,对象是对表单字段进行建模并提供get和set方法的对象,平台将使用它们来设置和检索用户在浏览器中输入的值。
Thymeleaf要求您使用
<form>标记中的
th:object属性指定命令对象:
<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post"> ... </form>
这与
th:object的另一种用法是一致的,但实际上,此特定方案为与Spring MVC框架正确集成增加了一些限制:
- 表单标记中的th:对象属性值必须是变量表达式( $ {...} ),仅指定模型属性名称,而不能浏览属性。 这意味着像$ {seedStarter}这样的表达式有效,但$ {seedStarter.data}则无效。
- 在<form>标记内,不能指定另一个th:object属性。 这与HTML表单不能嵌套的事实是一致的。
6.2输入
现在让我们看看如何将输入添加到表单中:
<input type="text" th:field="*{datePlanted}" />
如您所见,我们正在引入一个新属性:
th:field 。 这是Spring MVC集成的一个非常重要的功能,因为它完成了将
输入绑定到表单支持组件中的属性的所有艰苦工作。 您可以在Spring MVC JSP标记库中将其视为等效于标记中的path属性。
th:字段属性的行为取决于它是否附加到<input>,<select>或<textarea>标记(也取决于<input>标记的特定类型)。 在这种情况下(输入[type = text]),上面的代码行类似于:
<input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />
...但实际上要多一点,因为
th:字段还将使用注册的Spring转换服务,包括我们之前看到的
DateFormatter (即使字段表达式未括在方括号中)。 因此,日期将以正确的格式显示。
th:字段属性的值应该是选择表达式(
* {...... ),考虑到它们将在支持该格式的组件而不是上下文变量(或Spring MVC行话中的模型属性)上进行求值的事实,这是有道理的。 )
与
th:object中的表达式不同,这些表达式可以包括属性导航(实际上,此处允许<form:input> JSP标记的path属性允许的任何表达式)。
请注意,th:字段还可以有效地理解HTML5中引入的<input>元素的新类型,例如<input type =“ datetime” ... />,<input type =“ color” ... />等。为Spring MVC添加了完整的HTML5支持。
6.3复选框字段
th:字段还允许您定义标志的复选框输入。 让我们从HTML页面中查看一个示例:
<div> <label th:for="${#ids.next('covered')}" th:text="#{seedstarter.covered}">Covered</label> <input type="checkbox" th:field="*{covered}" /> </div>
请注意,除了标志本身之外,还有其他东西,例如,外部标签,以及使用
#ids.next('closed')函数来获取将应用于标志输入的
id属性的值。
为什么我们需要为此字段动态创建一个
id属性? 由于这些标志可能是多值的,因此,序列号的后缀将始终添加到其标识符值中(内部使用
#ids.seq(...)函数),以确保相同属性的每个输入标志都具有不同的标识符值。
如果我们查看这样一个多值复选框,我们将更容易看到这一点:
<ul> <li th:each="feat : ${allFeatures}"> <input type="checkbox" th:field="*{features}" th:value="${feat}" /> <label th:for="${#ids.prev('features')}" th:text="#{${'seedstarter.feature.' + feat}}">Heating</label> </li> </ul>
请注意,这一次我们添加了
th:value属性,因为如上所述函数字段不是逻辑的,而是一个值数组。
让我们看一下这段代码生成的HTML输出:
<ul> <li> <input id="features1" name="features" type="checkbox" value="SEEDSTARTER_SPECIFIC_SUBSTRATE" /> <input name="_features" type="hidden" value="on" /> <label for="features1">Seed starter-specific substrate</label> </li> <li> <input id="features2" name="features" type="checkbox" value="FERTILIZER" /> <input name="_features" type="hidden" value="on" /> <label for="features2">Fertilizer used</label> </li> <li> <input id="features3" name="features" type="checkbox" value="PH_CORRECTOR" /> <input name="_features" type="hidden" value="on" /> <label for="features3">PH Corrector used</label> </li> </ul>
在这里,我们看到如何将序列后缀添加到每个
id输入属性,以及
#ids.prev(...)函数如何允许我们提取为特定输入标识符生成的最后一个序列值。
不用担心这些
名称=“ _ features”的隐藏输入:它们会自动添加,以避免浏览器在提交表单时不会将未选择的标志值发送到服务器的问题。
还要注意,如果我们的
features属性在表单支持bean中包含一些选定的值,则
th:字段将处理此问题,并将
checked =“ checked”属性添加到相应的输入标签中。
6.4单选按钮字段
开关字段的设置类似于非布尔(多值)标志,当然,它们不是多值的:
<ul> <li th:each="ty : ${allTypes}"> <input type="radio" th:field="*{type}" th:value="${ty}" /> <label th:for="${#ids.prev('type')}" th:text="#{${'seedstarter.type.' + ty}}">Wireframe</label> </li> </ul>
6.5下拉/列表选择器
选择字段由两部分组成:<select>标记及其嵌套的<option>标记。 创建此类型的字段时,只有<select>标记应包括
th:字段属性,但是嵌套的<option>标记中的
th:值属性将非常重要,因为它们将提供一个机会来找出当前选定的选项(类似于非布尔标志和单选按钮) )
让我们重建一个下拉类型字段:
<select th:field="*{type}"> <option th:each="type : ${allTypes}" th:value="${type}" th:text="#{${'seedstarter.type.' + type}}">Wireframe</option> </select>
在这一点上,理解这段代码非常容易。 只需注意属性优先级如何允许我们设置
th: <option>标记本身中的
每个属性。
6.6动态字段
借助于Spring MVC中绑定表单域的高级功能,我们可以使用复杂的
Spring EL表达式将动态表单域绑定到表单支持bean。 这将使我们能够在
SeedStarter组件中创建新的
Row对象,并应用户的要求将这些行的字段添加到表单中。
为此,我们需要在控制器中使用几个新的映射方法,这些方法根据某些请求参数的可用性在
SeedStarter中添加或删除行:
@RequestMapping(value="/seedstartermng", params={"addRow"}) public String addRow(final SeedStarter seedStarter, final BindingResult bindingResult) { seedStarter.getRows().add(new Row()); return "seedstartermng"; } @RequestMapping(value="/seedstartermng", params={"removeRow"}) public String removeRow( final SeedStarter seedStarter, final BindingResult bindingResult, final HttpServletRequest req) { final Integer rowId = Integer.valueOf(req.getParameter("removeRow")); seedStarter.getRows().remove(rowId.intValue()); return "seedstartermng"; }
现在,我们可以向表单添加动态表:
<table> <thead> <tr> <th th:text="#{seedstarter.rows.head.rownum}">Row</th> <th th:text="#{seedstarter.rows.head.variety}">Variety</th> <th th:text="#{seedstarter.rows.head.seedsPerCell}">Seeds per cell</th> <th> <button type="submit" name="addRow" th:text="#{seedstarter.row.add}">Add row</button> </th> </tr> </thead> <tbody> <tr th:each="row,rowStat : *{rows}"> <td th:text="${rowStat.count}">1</td> <td> <select th:field="*{rows[__${rowStat.index}__].variety}"> <option th:each="var : ${allVarieties}" th:value="${var.id}" th:text="${var.name}">Thymus Thymi</option> </select> </td> <td> <input type="text" th:field="*{rows[__${rowStat.index}__].seedsPerCell}" /> </td> <td> <button type="submit" name="removeRow" th:value="${rowStat.index}" th:text="#{seedstarter.row.remove}">Remove row</button> </td> </tr> </tbody> </table>
这里有足够的东西,但除了不明白的一件事之外,还有很多东西让您不明白……
<select th:field="*{rows[__${rowStat.index}__].variety}"> ... </select>
如果您还记得“
Using Thymeleaf ”
教程中的内容 ,则语法
__ $ {...} __是一个预处理表达式,它是一个内部表达式,在对整个表达式进行实际求值之前先对其求值。 但是为什么以这种方式指定行索引呢? 用以下方法还不够吗?
<select th:field="*{rows[rowStat.index].variety}"> ... </select>
...实际上不是。 问题是Spring EL不会对数组索引括号中的变量求值,因此在执行上述表达式时,我们得到一个错误,告诉我们
行[rowStat.index] (而不是
行[0] ,
行[1]等)。 )在行集合中的无效位置。 这就是为什么这里需要预处理的原因。
在多次单击“添加行”后,让我们看一下结果HTML代码的片段:
<tbody> <tr> <td>1</td> <td> <select id="rows0.variety" name="rows[0].variety"> <option selected="selected" value="1">Thymus vulgaris</option> <option value="2">Thymus x citriodorus</option> <option value="3">Thymus herba-barona</option> <option value="4">Thymus pseudolaginosus</option> <option value="5">Thymus serpyllum</option> </select> </td> <td> <input id="rows0.seedsPerCell" name="rows[0].seedsPerCell" type="text" value="" /> </td> <td> <button name="removeRow" type="submit" value="0">Remove row</button> </td> </tr> <tr> <td>2</td> <td> <select id="rows1.variety" name="rows[1].variety"> <option selected="selected" value="1">Thymus vulgaris</option> <option value="2">Thymus x citriodorus</option> <option value="3">Thymus herba-barona</option> <option value="4">Thymus pseudolaginosus</option> <option value="5">Thymus serpyllum</option> </select> </td> <td> <input id="rows1.seedsPerCell" name="rows[1].seedsPerCell" type="text" value="" /> </td> <td> <button name="removeRow" type="submit" value="1">Remove row</button> </td> </tr> </tbody>