マニュアル:Thymeleaf + Spring。 パート2

前編

第三部



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などのすべてのユーティリティオブジェクトは、標準の方言のOGNL式と同じ方法でSpring EL式で使用できることに注意してください。



次に表示されるのは、次のような多くの国際化された(外部化された)テキストです。



<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.propertiesMessages_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で定義したとおりに フォーマットされていることを示します。 これを行うには、二重ブラケット構文( $ {{...}} )を使用します。これは、構成中に登録したDateFormatterを含むSpring変換サービスを自動的に適用します。



 <td th:text="${{sb.datePlanted}}">13/01/2011</td>
      
      







以下は、リテラルルックアップ式を使用して、ブール型のカバー付きビンのプロパティ値を国際化されたyesまたはnoに変換することにより、シードスターターシードコンテナがカバーされるかどうかを示しています。



 <td th:text="#{|bool.${sb.covered}|}">yes</td>
      
      







次に、初期シードスターターコンテナのタイプを表示する必要があります。 タイプは2つの値( WOODおよびPLASTIC )を持つJava列挙であるため、 メッセージファイルでseedstarter.type.WOO Dおよびseedstarter.type.PLASTICという名前で2つのプロパティを定義しました



しかし、国際化された型名を取得するには、 seedstarter.typeを追加する必要があります。 式を使用してenumの値にプレフィックスを付け、その結果をメッセージキーとして使用します。



 <td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
      
      







このリストの最も難しい部分は機能列です。 その中で、 Feature列挙の配列として提示されるコンテナのすべての関数をコンマで区切って表示したいと思います。 「 電気暖房、芝生 」など。



これらは、タイプで行ったように、これらの列挙値も推論する必要があるため、特に難しいことに注意してください。 出力ストリームは次のとおりです。







これを行うには、次のコードを作成します。



 <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に与える名前です。つまり、フォームフィールドをモデル化し、ブラウザでユーザーが入力した値を確立および取得するためにプラットフォームが使用する取得およびインストールメソッドを提供するオブジェクトです。



Thymeleafでは、 <form>タグth:オブジェクト属性を使用してコマンドオブジェクトを指定する必要があります。



 <form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post"> ... </form>
      
      







これはth:objectの別の使用法と一致していますが、実際、この特定のシナリオでは、Spring MVCフレームワークとの適切な統合にいくつかの制限が追加されます。







6.2入力





フォームに入力を追加する方法を見てみましょう。



 <input type="text" th:field="*{datePlanted}" />
      
      







ご覧のとおり、新しい属性th:fieldを導入しています 。 これは、Spring MVC統合にとって非常に重要な機能です。 入力をフォームサポートコンポーネントのプロパティにバインドするという大変な作業をすべて実行するからです。 Spring MVC JSPタグライブラリのタグ内のパス属性に相当するものとして見ることができます。



th:フィールド属性は、<input>、<select>、または<textarea>タグにアタッチされているかどうかによって(また、<input>タグの特定のタイプによって)動作が異なります。 この場合(入力[type = text])、上記のコード行は次のようになります。



 <input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />
      
      







...しかし、実際にはもう少しです:th:フィールドは、以前に見たDateFormatterを含む登録済みのSpring変換サービスも使用するためです(フィールド式が角括弧で囲まれていない場合でも)。 このため、日付は正しい形式で表示されます。



th:フィールド属性の値は選択式( * {...} )である必要があります。これは、コンテキスト変数(またはSpring MVCの専門用語のモデル属性)ではなく、フォームをサポートするコンポーネントで評価されることを考えると意味があります。 )



th:objectの式とは異なり、これらの式にはプロパティナビゲーションを含めることができます(実際、<form:input> JSPタグのpath属性に許可される式はすべてここで許可されます)。



th:フィールドは、<input type = "datetime" ... />、<input type = "color" ... />など、HTML5で導入された<input>要素の新しいタイプも効果的に理解することに注意してください。 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(...)関数を使用して特定の入力識別子に対して生成された最後のシーケンス値を抽出する方法を確認します。



name = "_ 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>タグの2つの部分で構成されています。 このタイプのフィールドを作成する場合、<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>
      
      







ここには十分なものがありますが、理解できないほど多くはありません... 1つの奇妙なことを除いて:



 <select th:field="*{rows[__${rowStat.index}__].variety}"> ... </select>
      
      







チュートリアル「 Thymeleafの使用 」で覚えている場合、構文__ $ {...} __は前処理式であり、式全体の実際の評価の前に評価される内部式です。 しかし、なぜこの方法で行インデックスを指定するのでしょうか? それだけでは十分ではありません:



 <select th:field="*{rows[rowStat.index].variety}"> ... </select>
      
      







...実際は違います。 問題は、Spring ELは配列インデックスの括弧内の変数を評価しないため、上記の式を実行すると、 行[rowStat.index]rows [0]rows [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>
      
      






All Articles