JSTL di JSF2 Facelets… masuk akal?

163

Saya ingin menampilkan sedikit kode Facelets dengan syarat.

Untuk itu, tag JSTL tampaknya berfungsi dengan baik:

<c:if test="${lpc.verbose}">
    ...
</c:if>

Namun, saya tidak yakin apakah ini praktik terbaik? Apakah ada cara lain untuk mencapai tujuan saya?

Jan
sumber

Jawaban:

320

pengantar

<c:xxx>Tag JSTL adalah semua pembuat tag dan mereka dieksekusi selama waktu pembuatan view , sedangkan <h:xxx>tag JSF adalah semua komponen UI dan mereka dieksekusi selama view render time .

Perhatikan bahwa dari JSF sendiri <f:xxx>dan <ui:xxx>tag hanya mereka yang tidak tidak memperpanjang dari UIComponentjuga taghandlers, misalnya <f:validator>, <ui:include>, <ui:define>, dll yang yang memperpanjang dari UIComponentjuga komponen JSF UI, misalnya <f:param>, <ui:fragment>, <ui:repeat>, dll Dari komponen JSF UI hanya iddan bindingatribut juga dievaluasi selama tampilan build time. Jadi jawaban di bawah ini tentang siklus hidup JSTL juga berlaku untuk iddan bindingatribut komponen JSF.

Tampilan build time adalah saat ketika file XHTML / JSP akan diurai dan dikonversi ke pohon komponen JSF yang kemudian disimpan UIViewRootpada FacesContext. Waktu render tampilan adalah saat ketika komponen pohon JSF akan menghasilkan HTML, dimulai dengan UIViewRoot#encodeAll(). Jadi: Komponen JSF UI dan tag JSTL tidak berjalan dalam sinkronisasi seperti yang Anda harapkan dari pengkodean. Anda dapat memvisualisasikannya sebagai berikut: JSTL berjalan dari atas ke bawah terlebih dahulu, menghasilkan pohon komponen JSF, lalu giliran JSF untuk menjalankan dari atas ke bawah lagi, menghasilkan output HTML.

<c:forEach> vs. <ui:repeat>

Sebagai contoh, markas Facelet ini berulang di atas 3 item menggunakan <c:forEach>:

<c:forEach items="#{bean.items}" var="item">
    <h:outputText id="item_#{item.id}" value="#{item.value}" />
</c:forEach>

... membuat selama waktu pembuatan, tiga <h:outputText>komponen terpisah di pohon komponen JSF, secara kasar direpresentasikan seperti ini:

<h:outputText id="item_1" value="#{bean.items[0].value}" />
<h:outputText id="item_2" value="#{bean.items[1].value}" />
<h:outputText id="item_3" value="#{bean.items[2].value}" />

... yang pada gilirannya secara individual menghasilkan output HTML mereka selama waktu render tampilan:

<span id="item_1">value1</span>
<span id="item_2">value2</span>
<span id="item_3">value3</span>

Perhatikan bahwa Anda perlu memastikan keunikan ID komponen dan secara manual juga dievaluasi selama waktu pembuatan tampilan.

Sementara markas Facelet ini mengulangi lebih dari 3 item menggunakan <ui:repeat>, yang merupakan komponen UI JSF:

<ui:repeat id="items" value="#{bean.items}" var="item">
    <h:outputText id="item" value="#{item.value}" />
</ui:repeat>

... sudah berakhir seperti apa adanya di komponen pohon JSF di mana <h:outputText>komponen yang sama adalah selama tampilan membuat waktu digunakan kembali untuk menghasilkan output HTML berdasarkan putaran iterasi saat ini:

<span id="items:0:item">value1</span>
<span id="items:1:item">value2</span>
<span id="items:2:item">value3</span>

Perhatikan bahwa <ui:repeat>sebagai NamingContainerkomponen sudah memastikan keunikan ID klien berdasarkan indeks iterasi; itu juga tidak mungkin untuk menggunakan EL dalam idatribut komponen anak dengan cara ini karena juga dievaluasi selama view build time sementara #{item}hanya tersedia selama view render time. Hal yang sama juga berlaku untuk h:dataTablekomponen dan sejenisnya.

<c:if>/ <c:choose>vs.rendered

Sebagai contoh lain, marka Facelets ini secara kondisional menambahkan tag yang berbeda menggunakan <c:if>(Anda juga dapat menggunakan <c:choose><c:when><c:otherwise>ini):

<c:if test="#{field.type eq 'TEXT'}">
    <h:inputText ... />
</c:if>
<c:if test="#{field.type eq 'PASSWORD'}">
    <h:inputSecret ... />
</c:if>
<c:if test="#{field.type eq 'SELECTONE'}">
    <h:selectOneMenu ... />
</c:if>

... type = TEXThanya akan menambahkan <h:inputText>komponen ke komponen pohon JSF:

<h:inputText ... />

Sementara markas Facelets ini:

<h:inputText ... rendered="#{field.type eq 'TEXT'}" />
<h:inputSecret ... rendered="#{field.type eq 'PASSWORD'}" />
<h:selectOneMenu ... rendered="#{field.type eq 'SELECTONE'}" />

... akan berakhir persis seperti di atas dalam komponen pohon JSF terlepas dari kondisinya. Ini dengan demikian dapat berakhir di pohon komponen "kembung" ketika Anda memiliki banyak dari mereka dan mereka benar-benar didasarkan pada model "statis" (yaitu fieldtidak pernah berubah selama setidaknya ruang lingkup tampilan). Juga, Anda mungkin mengalami masalah EL ketika Anda berurusan dengan subclass dengan properti tambahan di versi Mojarra sebelum 2.2.7.

<c:set> vs. <ui:param>

Mereka tidak bisa dipertukarkan. The <c:set>set variabel dalam lingkup EL, yang dapat diakses hanya setelah lokasi tag selama waktu pandangan membangun, tetapi di mana saja dalam pandangan selama tampilan waktu render. The <ui:param>melewati variabel EL untuk template facelet termasuk melalui <ui:include>, <ui:decorate template>atau <ui:composition template>. Versi JSF yang lebih lama memiliki bug di mana <ui:param>variabel juga tersedia di luar templat Facelet yang dipermasalahkan, ini seharusnya tidak pernah diandalkan.

The <c:set>tanpa scopeatribut akan berperilaku seperti sebuah alias. Itu tidak men-cache hasil ekspresi EL dalam lingkup apa pun. Dengan demikian dapat digunakan dengan baik di dalam misalnya komponen iterasi JSF. Jadi, misalnya di bawah ini akan berfungsi dengan baik:

<ui:repeat value="#{bean.products}" var="product">
    <c:set var="price" value="#{product.price}" />
    <h:outputText value="#{price}" />
</ui:repeat>

Hanya saja tidak cocok untuk misalnya menghitung jumlah dalam satu lingkaran. Untuk itu alih-alih gunakan aliran EL 3.0 :

<ui:repeat value="#{bean.products}" var="product">
    ...
</ui:repeat>
<p>Total price: #{bean.products.stream().map(product->product.price).sum()}</p>

Hanya saja, ketika Anda mengatur scopeatribut dengan satu nilai-nilai yang diijinkan request, view, session, atau application, maka akan dievaluasi segera selama waktu pandangan membangun dan disimpan dalam ruang lingkup tertentu.

<c:set var="dev" value="#{facesContext.application.projectStage eq 'Development'}" scope="application" />

Ini akan dievaluasi hanya sekali dan tersedia #{dev}di seluruh aplikasi.

Gunakan JSTL untuk mengontrol bangunan pohon komponen JSF

Menggunakan JSTL hanya dapat menyebabkan hasil yang tak terduga ketika sedang digunakan dalam komponen JSF iterasi seperti <h:dataTable>, <ui:repeat>, dll, atau ketika JSTL atribut tag tergantung pada hasil dari peristiwa JSF seperti preRenderViewatau diserahkan nilai bentuk dalam model yang tidak tersedia selama pandangan membangun waktu . Jadi, gunakan tag JSTL hanya untuk mengontrol aliran bangunan pohon komponen JSF. Gunakan komponen JSF UI untuk mengontrol aliran pembuatan output HTML. Jangan ikat varkomponen iterasi JSF ke atribut tag JSTL. Jangan mengandalkan acara JSF di atribut tag JSTL.

Kapan pun Anda merasa perlu untuk mengikat komponen ke backing bean via binding, atau ambil satu via findComponent(), dan membuat / memanipulasi anak-anaknya menggunakan kode Java dengan backing bean dengan new SomeComponent()dan apa yang tidak, maka Anda harus segera berhenti dan mempertimbangkan untuk menggunakan JSTL sebagai gantinya. Karena JSTL juga berbasis XML, kode yang diperlukan untuk secara dinamis membuat komponen JSF akan menjadi jauh lebih mudah dibaca dan dipelihara.

Penting untuk diketahui adalah bahwa versi Mojarra yang lebih tua dari 2.1.18 memiliki bug dalam penyimpanan sebagian saat mereferensikan tampilan kacang scoped di atribut tag JSTL. Kacang scoped seluruh tampilan akan baru dibuat alih-alih diambil dari pohon tampilan (hanya karena pohon tampilan lengkap belum tersedia pada titik JSTL berjalan). Jika Anda mengharapkan atau menyimpan beberapa negara dalam tampilan yang dicakup kacang oleh atribut tag JSTL, maka itu tidak akan mengembalikan nilai yang Anda harapkan, atau itu akan "hilang" dalam kacang yang dicakup tampilan nyata yang dikembalikan setelah tampilan pohon dibangun. Jika Anda tidak dapat memutakhirkan ke Mojarra 2.1.18 atau yang lebih baru, pekerjaan di sekitar adalah mematikan sebagian penghematan negara web.xmlseperti di bawah ini:

<context-param>
    <param-name>javax.faces.PARTIAL_STATE_SAVING</param-name>
    <param-value>false</param-value>
</context-param>

Lihat juga:

Untuk melihat beberapa contoh dunia nyata di mana tag JSTL sangat membantu (yaitu ketika benar-benar digunakan selama membangun tampilan), lihat pertanyaan / jawaban berikut:


Pendeknya

Mengenai persyaratan fungsional konkret Anda, jika Anda ingin merender komponen JSF secara kondisional, gunakan renderedatribut pada komponen HTML JSF sebagai gantinya, terutama jika #{lpc}mewakili item iterasi yang saat ini di-iterasi dari komponen iterasi JSF seperti <h:dataTable>atau <ui:repeat>.

<h:someComponent rendered="#{lpc.verbose}">
    ...
</h:someComponent>

Atau, jika Anda ingin membangun (membuat / menambahkan) komponen JSF secara kondisional, maka tetap gunakan JSTL. Ini jauh lebih baik daripada melakukan new SomeComponent()di Jawa secara lisan.

<c:if test="#{lpc.verbose}">
    <h:someComponent>
        ...
    </h:someComponent>
</c:if>

Lihat juga:

BalusC
sumber
3
@ Arklin: Tidak? Bagaimana dengan contoh ini ?
BalusC
1
Saya tidak bisa menafsirkan paragraf pertama dengan benar untuk waktu yang lama (contoh-contoh yang diberikan sangat jelas). Karenanya, saya meninggalkan komentar ini sebagai satu-satunya cara. Dengan paragraf itu, saya dalam kesan itu <ui:repeat>adalah penangan tag (karena baris ini, " Perhatikan bahwa JSF sendiri <f:xxx>dan <ui:xxx>... ") sama seperti <c:forEach>dan karenanya, dievaluasi pada tampilan waktu pembuatan (sekali lagi sama seperti saja <c:forEach>) . Jika memang demikian, seharusnya tidak ada perbedaan fungsional yang terlihat antara <ui:repeat>dan <c:forEach>? Saya tidak mengerti arti sebenarnya dari paragraf itu :)
Tiny
1
Maaf, saya tidak akan mencemari posting ini lebih jauh. Saya memperhatikan komentar Anda sebelumnya, tetapi bukankah kalimat ini, " Perhatikan bahwa tag JSF sendiri <f:xxx>dan <ui:xxx>tag yang tidak diperpanjang UIComponentjuga merupakan penangan tag. " Berusaha menyiratkan bahwa <ui:repeat>itu juga penangan tag karena <ui:xxx>juga termasuk <ui:repeat>? Ini kemudian harus berarti bahwa itu <ui:repeat>adalah salah satu komponen <ui:xxx>yang meluas UIComponent. Karenanya, ini bukan penangan tag. (Beberapa dari mereka mungkin tidak memperpanjang UIComponent. Oleh karena itu, mereka penangan tag) Apakah itu?
Tiny
2
@Shirgill: <c:set>tanpa scopemembuat alias dari ekspresi EL alih-alih menetapkan nilai yang dievaluasi dalam cakupan target. Coba scope="request"sebagai gantinya, yang akan segera mengevaluasi nilai (selama waktu pembuatan tampilan memang) dan menetapkannya sebagai atribut permintaan (yang tidak akan "ditimpa" selama iterasi). Di bawah selimut, itu membuat dan menetapkan ValueExpressionobjek.
BalusC
1
@ K.Nicholas: Ada di bawah selimut a ClassNotFoundException. Ketergantungan runtime proyek Anda rusak. Kemungkinan besar Anda menggunakan server non-JavaEE seperti Tomcat dan Anda lupa menginstal JSTL, atau Anda tidak sengaja memasukkan JSTL 1.0 dan JSTL 1.1+. Karena dalam JSTL 1.0 paketnya adalah javax.servlet.jstl.core.*dan sejak JSTL 1.1 ini telah menjadi javax.servlet.jsp.jstl.core.*. Petunjuk untuk menginstal JSTL dapat ditemukan di sini: stackoverflow.com/a/4928309
BalusC
13

menggunakan

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>
Bozho
sumber
Terima kasih, jawaban bagus. Lebih umum: Apakah tag JSTL masih masuk akal atau haruskah kita menganggapnya sudah usang sejak JSF 2.0?
Jan
Dalam kebanyakan kasus, ya. Tetapi kadang-kadang tepat untuk menggunakannya
Bozho
3
Menggunakan h: panelGroup adalah solusi kotor, karena menghasilkan tag <span>, sementara c: jika tidak menambahkan apa pun ke kode html. h: panelGroup juga bermasalah di dalam panelGrids, karena mengelompokkan elemen.
Rober2D2
4

Untuk keluaran seperti sakelar, Anda bisa menggunakan sakelar tampilan dari PrimeFaces Extensions.

Ravshan Samandarov
sumber