Bagian pertamaBagian ketiga5 Tampilkan Data Pemula Benih
Hal pertama yang
ditampilkan halaman
/WEB-INF/templates/seedstartermng.html kami adalah daftar dengan data awal awal yang saat ini disimpan. Untuk melakukan ini, kita memerlukan beberapa pesan eksternal, serta beberapa ekspresi bekerja untuk atribut model. Seperti ini:
<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>
Ada banyak yang bisa dilihat. Mari kita lihat masing-masing fragmen secara terpisah.
Pertama-tama, bagian ini hanya akan ditampilkan jika ada permulaan unggulan. Kami mencapainya dengan atribut
th: never atribut dan
# lists.isEmpty (...) .
<div class="seedstarterlist" th:unless="${#lists.isEmpty(allSeedStarters)}">
Perhatikan bahwa semua objek utilitas, seperti
#list , tersedia dalam ekspresi Spring EL dengan cara yang sama seperti pada ekspresi OGNL dalam dialek standar.
Hal berikutnya yang harus dilihat adalah banyak teks yang diinternasionalkan (dieksternalisasi), seperti:
<h2 th: text = "# {title.list}"> Daftar Pemula Benih
<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> ...
Ini adalah aplikasi Spring MVC, kami telah mendefinisikan
bean MessageSource dalam konfigurasi Spring kami (objek
MessageSource adalah cara standar untuk mengontrol teks eksternal di Spring MVC):
@Bean public ResourceBundleMessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("Messages"); return messageSource; }
... dan properti
basename ini menunjukkan bahwa kita akan memiliki file di classpath kita, seperti
Messages_es.properties atau
Messages_en.properties . Mari kita lihat versi bahasa Spanyol:
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
Di kolom pertama dari tabel kami menunjukkan tanggal ketika starter disiapkan. Tetapi
kami akan menunjukkan bahwa itu diformat seperti yang kami tetapkan dalam
DateFormatter kami. Untuk melakukan ini, kami akan menggunakan sintaks braket ganda (
$ {{...}} ), yang akan secara otomatis menerapkan layanan konversi Spring, termasuk DateFormatter, yang kami daftarkan saat mengatur.
<td th:text="${{sb.datePlanted}}">13/01/2011</td>
Berikut ini menunjukkan apakah wadah benih starter benih ditutup atau tidak dengan mengonversi nilai properti dari nampan tertutup boolean ke ya atau tidak yang terinternasionalisasi dengan ekspresi pencarian literal:
<td th:text="#{|bool.${sb.covered}|}">yes</td>
Sekarang kita perlu menunjukkan jenis wadah starter benih awal.
Jenisnya adalah enumerasi java dengan dua nilai (
KAYU dan
PLASTIK ), dan oleh karena itu kami mendefinisikan dua properti dalam file
Pesan kami dengan nama
seedstarter.type.WOO D dan
seedstarter.type.PLASTIC .
Tetapi untuk mendapatkan nama tipe yang
diinternasionalkan , kita perlu menambahkan
seedstarter.type. awalan ke nilai enum menggunakan ekspresi, yang hasilnya akan kita gunakan sebagai kunci pesan:
<td th:text="#{|seedstarter.type.${sb.type}|}">Wireframe</td>
Bagian tersulit dari daftar ini adalah kolom
fitur . Di dalamnya, kami ingin menampilkan semua fungsi wadah kami, yang disajikan sebagai array enumerasi
Fitur , dipisahkan oleh koma. Seperti "
Pemanas listrik, halaman ."
Perhatikan bahwa ini sangat sulit karena nilai enum ini juga harus disimpulkan, seperti yang kita lakukan dengan Type. Aliran output adalah sebagai berikut:
- Ganti awalan yang sesuai dengan semua elemen dari array fitur .
- Terima pesan eksternal yang cocok dengan semua tombol di langkah 1.
- Lampirkan semua pesan yang diterima di langkah 2, menggunakan koma sebagai pemisah.
Untuk melakukan ini, kami membuat kode berikut:
<td th:text="${#strings.arrayJoin( #messages.arrayMsg( #strings.arrayPrepend(sb.features,'seedstarter.feature.')), ', ')}">Electric Heating, Turf</td>
Kolom terakhir dari daftar kami sebenarnya cukup sederhana. Bahkan jika memiliki tabel bersarang untuk menampilkan konten setiap baris dalam wadah:
<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 Membuat Formulir
6.1 Memproses objek perintah
Objek perintah adalah nama yang Spring MVC memberikan kacang dukungan bentuk, yaitu objek yang memodelkan bidang formulir dan menyediakan metode get and set yang akan digunakan platform untuk mengatur dan mengambil nilai yang dimasukkan oleh pengguna di browser.
Thymeleaf mengharuskan Anda menentukan objek perintah menggunakan atribut
objek th: di
tag <form> Anda :
<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post"> ... </form>
Ini konsisten dengan penggunaan objek
th: lainnya , tetapi pada kenyataannya skenario khusus ini menambahkan beberapa batasan untuk integrasi yang tepat dengan kerangka Spring MVC:
- Nilai atribut objek th: dalam tag formulir harus berupa ekspresi variabel ( $ {...} ), yang hanya menentukan nama atribut model, tanpa menavigasi properti. Ini berarti bahwa ekspresi seperti $ {seedStarter} valid, tetapi $ {seedStarter.data} tidak akan.
- Di dalam tag <form>, atribut objek th: lainnya tidak dapat ditentukan. Ini konsisten dengan fakta bahwa formulir HTML tidak dapat disarangkan.
6.2 Masukan
Sekarang mari kita lihat bagaimana menambahkan input ke formulir kita:
<input type="text" th:field="*{datePlanted}" />
Seperti yang Anda lihat, kami memperkenalkan atribut baru
: bidang th:. Ini adalah fitur yang sangat penting untuk integrasi Spring MVC, karena melakukan semua kerja keras untuk mengikat
input Anda ke properti di komponen dukungan formulir. Anda bisa melihatnya sebagai ekivalen dengan atribut path dalam sebuah tag dari pustaka tag Spring MVC JSP.
Atribut th: field berperilaku berbeda tergantung pada apakah itu dilampirkan pada tag <input>, <select> atau <textarea> (dan juga tergantung pada tipe spesifik dari tag <input>). Dalam hal ini (input [ketik = teks]) baris kode di atas mirip dengan:
<input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />
... Tapi sebenarnya itu sedikit lebih, karena
bidang th: juga akan menggunakan layanan transformasi Spring terdaftar, termasuk
DateFormatter , yang kita lihat sebelumnya (bahkan jika ekspresi bidang tidak tertutup dalam tanda kurung siku). Karena ini, tanggal akan ditampilkan diformat dengan benar.
Nilai untuk atribut
th: field harus berupa ekspresi pilih (
* {...} ), yang masuk akal mengingat fakta bahwa mereka akan dievaluasi pada komponen yang mendukung formulir, dan bukan pada variabel konteks (atau atribut model dalam jargon Spring MVC). )
Tidak seperti ekspresi dalam objek
th:, ekspresi ini dapat menyertakan navigasi properti (pada kenyataannya, ekspresi apa pun yang diizinkan untuk atribut path dari tag JSP <form: input> diizinkan di sini).
Perhatikan bahwa bidang th: juga memahami tipe baru elemen <input> yang diperkenalkan dalam HTML5, seperti <input type = "datetime" ... />, <input type = "color" ... />, dll., Secara efektif menambahkan dukungan HTML5 penuh untuk Spring MVC.
6.3 Bidang kotak centang
bidang th: juga memungkinkan Anda untuk menentukan input kotak centang untuk flag. Mari kita lihat contoh dari halaman HTML kami:
<div> <label th:for="${#ids.next('covered')}" th:text="#{seedstarter.covered}">Covered</label> <input type="checkbox" th:field="*{covered}" /> </div>
Perhatikan bahwa ada hal lain selain flag itu sendiri, misalnya, label eksternal, serta menggunakan fungsi
# ids.next ('closed') untuk mendapatkan nilai yang akan diterapkan pada atribut
id dari input ke flag.
Mengapa kita perlu secara dinamis membuat atribut
id untuk bidang ini? Karena flag berpotensi bernilai multi, dan oleh karena itu, akhiran nomor urut akan selalu ditambahkan ke nilai pengenalnya (secara internal menggunakan fungsi
# ids.seq (...) ) untuk memastikan bahwa masing-masing flag input dari properti yang sama memiliki nilai pengenal yang berbeda .
Akan lebih mudah bagi kita untuk melihat ini jika kita melihat kotak centang multi-nilai:
<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>
Harap perhatikan bahwa kali ini kami menambahkan atribut
th: value , karena bidang fungsi tidak logis, seperti dijelaskan di atas, tetapi merupakan array nilai.
Mari kita lihat output HTML yang dihasilkan oleh kode ini:
<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>
Di sini kita melihat bagaimana sufiks urutan ditambahkan ke setiap atribut input
id dan bagaimana fungsi
# ids.prev (...) memungkinkan kita untuk mengekstrak nilai urutan terakhir yang dihasilkan untuk pengidentifikasi input tertentu.
Jangan khawatir tentang input tersembunyi ini dengan
name = "_ fitur" : mereka secara otomatis ditambahkan untuk menghindari masalah dengan browser yang tidak mengirim nilai bendera yang tidak dipilih ke server saat mengirimkan formulir.
Juga perhatikan bahwa jika properti
fitur kami
berisi beberapa nilai yang dipilih dalam form-backing bean kami, maka
bidang th: akan menangani hal ini dan menambahkan atribut yang
dicentang = "dicentang" ke tag input yang sesuai.
6.4 Bidang Tombol Radio
Kolom sakelar diatur mirip dengan bendera non-Boolean (multi-nilai), kecuali, tentu saja, bahwa itu bukan multi-nilai:
<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 Dropdown / Daftar pemilih
Bidang pilihan terdiri dari dua bagian: tag <select> dan tag <option> bersarangnya. Saat membuat bidang jenis ini, hanya tag <select> yang harus menyertakan atribut
bidang th :, tetapi atribut nilai
th: dalam tag <option> bersarang akan sangat penting karena mereka akan memberikan peluang untuk mengetahui apa opsi yang saat ini dipilih (mirip dengan bendera dan tombol radio non-boolean) )
Mari kita membangun kembali bidang jenis dropdown:
<select th:field="*{type}"> <option th:each="type : ${allTypes}" th:value="${type}" th:text="#{${'seedstarter.type.' + type}}">Wireframe</option> </select>
Pada titik ini, memahami potongan kode ini cukup mudah. Perhatikan bagaimana prioritas atribut memungkinkan kita untuk mengatur
th: setiap atribut di tag <option> itu sendiri.
6.6 Bidang dinamis
Berkat kemampuan canggih bidang formulir yang mengikat di Spring MVC, kami dapat menggunakan ekspresi
Spring EL yang kompleks untuk mengikat bidang formulir dinamis ke form-backing bean kami. Ini akan memungkinkan kami untuk membuat objek
Row baru di komponen
SeedStarter kami dan menambahkan bidang-bidang baris ini ke formulir kami atas permintaan pengguna.
Untuk melakukan ini, kita memerlukan beberapa metode yang dipetakan baru di controller kita yang menambah atau menghapus baris dari
SeedStarter kita tergantung pada ketersediaan parameter permintaan tertentu:
@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"; }
Dan sekarang kita bisa menambahkan tabel dinamis ke formulir kita:
<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>
Ada cukup banyak hal di sini, tetapi tidak banyak yang tidak mengerti ... kecuali satu hal
aneh :
<select th:field="*{rows[__${rowStat.index}__].variety}"> ... </select>
Jika Anda ingat dari tutorial, “
Menggunakan Thymeleaf, ” sintaksisnya
__ $ {...} __ adalah ekspresi preprocessing, yang merupakan ekspresi internal yang dievaluasi sebelum evaluasi aktual seluruh ekspresi. Tetapi mengapa cara ini menentukan indeks baris? Bukankah itu cukup dengan:
<select th:field="*{rows[rowStat.index].variety}"> ... </select>
... sebenarnya tidak. Masalahnya adalah bahwa Spring EL tidak mengevaluasi variabel dalam tanda kurung dari indeks array, jadi ketika menjalankan ekspresi di atas, kita mendapatkan kesalahan memberitahu kita bahwa
baris [rowStat.index] (bukan
baris [0] ,
baris [1] , dll. ) posisi tidak valid dalam koleksi baris. Inilah sebabnya mengapa pra-pemrosesan diperlukan di sini.
Mari kita lihat potongan kode HTML yang dihasilkan setelah kita mengklik "Tambahkan Baris" beberapa kali:
<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>