JSTL trong Facelets JSF2 có ý nghĩa gì?


163

Tôi muốn xuất ra một chút mã Facelets một cách có điều kiện.

Với mục đích đó, các thẻ JSTL dường như hoạt động tốt:

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

Tuy nhiên, tôi không chắc đây có phải là cách thực hành tốt nhất không? Có cách nào khác để đạt được mục tiêu của tôi không?

Câu trả lời:


320

Giới thiệu

Các <c:xxx>thẻ JSTL là tất cả các trình tạo thẻ và chúng được thực thi trong thời gian xây dựng chế độ xem , trong khi <h:xxx>các thẻ JSF là tất cả các thành phần UI và chúng được thực thi trong thời gian hiển thị chế độ xem .

Lưu ý rằng từ JSF riêng <f:xxx><ui:xxx>thẻ chỉ những người mà ta không mở rộng từ UIComponentcũng taghandlers, ví dụ như <f:validator>, <ui:include>, <ui:define>vv Những người mà kéo dài từ UIComponentcũng JSF UI thành phần, ví dụ như <f:param>, <ui:fragment>, <ui:repeat>, vv Từ các thành phần JSF UI chỉ idbindingthuộc tính cũng được đánh giá trong thời gian xây dựng xem. Do đó, câu trả lời dưới đây về vòng đời của JSTL cũng áp dụng cho các thuộc tính idbindingthuộc tính của các thành phần JSF.

Thời gian xem build là khoảnh khắc khi tập tin XHTML / JSP là để được phân tích và chuyển đổi sang một cái cây thành phần JSF mà sau đó được lưu giữ như UIViewRootcủa FacesContext. Thời gian kết xuất khung nhìn là thời điểm đó khi cây thành phần JSF sắp tạo HTML, bắt đầu bằng UIViewRoot#encodeAll(). Vì vậy: Các thành phần UI của JSF và các thẻ JSTL không chạy đồng bộ như bạn mong đợi từ mã hóa. Bạn có thể hình dung nó như sau: JSTL chạy từ trên xuống dưới trước, tạo ra cây thành phần JSF, sau đó đến lượt JSF chạy từ trên xuống dưới một lần nữa, tạo ra đầu ra HTML.

<c:forEach> đấu với <ui:repeat>

Ví dụ: đánh dấu Facelets này lặp lại trên 3 mục bằng cách sử dụng <c:forEach>:

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

... tạo trong thời gian xây dựng chế độ xem ba <h:outputText>thành phần riêng biệt trong cây thành phần JSF, đại diện như thế này:

<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}" />

... lần lượt tạo riêng lẻ đầu ra HTML của họ trong thời gian xem kết xuất:

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

Lưu ý rằng bạn cần đảm bảo thủ công tính duy nhất của ID thành phần và những ID đó cũng được đánh giá trong thời gian xây dựng chế độ xem.

Trong khi đánh dấu Facelets này lặp lại hơn 3 mục bằng cách sử dụng <ui:repeat>, đó là thành phần UI của JSF:

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

... đã kết thúc như trong cây thành phần JSF, theo đó <h:outputText>thành phần tương tự trong thời gian hiển thị được sử dụng lại để tạo đầu ra HTML dựa trên vòng lặp hiện tại:

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

Lưu ý rằng việc <ui:repeat>là một NamingContainerthành phần đã đảm bảo tính duy nhất của ID khách hàng dựa trên chỉ số lặp; Cũng không thể sử dụng EL trong idthuộc tính của các thành phần con theo cách này vì nó cũng được đánh giá trong thời gian xây dựng chế độ xem trong khi #{item}chỉ khả dụng trong thời gian hiển thị chế độ xem. Điều tương tự cũng đúng với một h:dataTablevà các thành phần tương tự.

<c:if>/ <c:choose>vsrendered

Một ví dụ khác, đánh dấu Facelets này có điều kiện thêm các thẻ khác nhau bằng cách sử dụng <c:if>(bạn cũng có thể sử dụng <c:choose><c:when><c:otherwise>cho việc này):

<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>

... trong trường hợp type = TEXTchỉ thêm <h:inputText>thành phần vào cây thành phần JSF:

<h:inputText ... />

Trong khi đánh dấu Facelets này:

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

... sẽ kết thúc chính xác như trên trong cây thành phần JSF bất kể điều kiện. Do đó, điều này có thể kết thúc trong một cây thành phần "cồng kềnh" khi bạn có nhiều trong số chúng và chúng thực sự dựa trên mô hình "tĩnh" (tức là fieldkhông bao giờ thay đổi trong phạm vi ít nhất là phạm vi xem). Ngoài ra, bạn có thể gặp rắc rối EL khi bạn xử lý các lớp con với các thuộc tính bổ sung trong các phiên bản Mojarra trước 2.2.7.

<c:set> đấu với <ui:param>

Chúng không thể thay thế cho nhau. Biến <c:set>đặt trong phạm vi EL, chỉ có thể truy cập được sau vị trí thẻ trong thời gian xây dựng chế độ xem, nhưng ở bất kỳ vị trí nào trong chế độ xem trong thời gian hiển thị chế độ xem. Các <ui:param>trôi qua một biến EL một mẫu Facelet bao gồm thông qua <ui:include>, <ui:decorate template>hoặc <ui:composition template>. Các phiên bản JSF cũ hơn có lỗi, theo đó <ui:param>biến cũng có sẵn bên ngoài khuôn mặt Facelet được đề cập, điều này không bao giờ nên dựa vào.

Không <c:set>scopethuộc tính sẽ hoạt động như một bí danh. Nó không lưu trữ kết quả của biểu thức EL trong bất kỳ phạm vi nào. Do đó, nó hoàn toàn có thể được sử dụng bên trong ví dụ lặp lại các thành phần JSF. Do đó, ví dụ dưới đây sẽ hoạt động tốt:

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

Nó chỉ không phù hợp với việc tính tổng trong một vòng lặp. Thay vào đó, hãy sử dụng luồng EL 3.0 :

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

Chỉ khi bạn thiết lập các scopethuộc tính với một trong các giá trị cho phép request, view, session, hoặc application, sau đó nó sẽ được đánh giá ngay trong quá trình xem thời gian xây dựng và lưu trữ trong phạm vi chỉ định.

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

Điều này sẽ chỉ được đánh giá một lần và có sẵn #{dev}trong toàn bộ ứng dụng.

Sử dụng JSTL để kiểm soát việc xây dựng cây thành phần JSF

Sử dụng JSTL chỉ có thể dẫn đến kết quả bất ngờ khi được sử dụng bên trong JSF iterating thành phần như <h:dataTable>, <ui:repeat>, vv, hoặc khi JSTL thẻ thuộc tính phụ thuộc vào kết quả của các sự kiện JSF như preRenderViewhoặc gửi giá trị hình thức trong mô hình mà không có sẵn trong quá trình xem thời gian xây dựng . Vì vậy, chỉ sử dụng các thẻ JSTL để kiểm soát luồng xây dựng cây thành phần JSF. Sử dụng các thành phần UI của JSF để kiểm soát luồng phát sinh HTML. Không liên kết các varthành phần lặp lại của JSF với các thuộc tính thẻ JSTL. Không dựa vào các sự kiện JSF trong các thuộc tính thẻ JSTL.

Bất cứ khi nào bạn nghĩ rằng bạn cần liên kết một thành phần với bean hậu thuẫn thông qua bindinghoặc lấy một thành phần thông qua findComponent()và tạo / thao tác các phần tử con của nó bằng cách sử dụng mã Java trong một bean hậu thuẫn new SomeComponent()và không, thì bạn nên dừng ngay lập tức và xem xét sử dụng JSTL. Vì JSTL cũng dựa trên XML, mã cần thiết để tự động tạo các thành phần JSF sẽ trở nên dễ đọc và dễ bảo trì hơn rất nhiều.

Điều quan trọng cần biết là các phiên bản Mojarra cũ hơn 2.1,18 có lỗi trong việc lưu trạng thái một phần khi tham chiếu một bean có phạm vi xem trong thuộc tính thẻ JSTL. Toàn bộ phạm vi khung nhìn sẽ được tạo lại mới thay vì được truy xuất từ ​​cây xem (đơn giản vì cây xem hoàn chỉnh chưa có sẵn tại thời điểm JSTL chạy). Nếu bạn đang mong đợi hoặc lưu trữ một số trạng thái trong bean có phạm vi xem theo thuộc tính thẻ JSTL, thì nó sẽ không trả về giá trị mà bạn mong đợi, hoặc nó sẽ bị "mất" trong bean có phạm vi xem thực được khôi phục sau chế độ xem cây được xây dựng. Trong trường hợp bạn không thể nâng cấp lên Mojarra 2.1.18 hoặc mới hơn, công việc xung quanh là tắt tiết kiệm một phần trạng thái web.xmlnhư sau:

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

Xem thêm:

Để xem một số ví dụ trong thế giới thực nơi thẻ JSTL hữu ích (nghĩa là khi thực sự được sử dụng đúng cách trong khi xây dựng chế độ xem), hãy xem các câu hỏi / câu trả lời sau:


Tóm lại

Đối với yêu cầu chức năng cụ thể của bạn, nếu bạn muốn hiển thị các thành phần JSF một cách có điều kiện, renderedthay vào đó, hãy sử dụng thuộc tính trên thành phần HTML của JSF, đặc biệt nếu #{lpc}đại diện cho mục được lặp hiện tại của thành phần lặp JSF như <h:dataTable>hoặc <ui:repeat>.

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

Hoặc, nếu bạn muốn xây dựng (tạo / thêm) các thành phần JSF một cách có điều kiện, thì hãy tiếp tục sử dụng JSTL. Đó là cách tốt hơn nhiều so với làm new SomeComponent()bằng lời nói trong java.

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

Xem thêm:


3
@Aklin: Không? Làm thế nào về ví dụ này ?
BalusC

1
Tôi không thể giải thích đoạn đầu tiên đúng trong một thời gian dài (các ví dụ được đưa ra rất rõ ràng). Do đó, tôi để lại bình luận này là cách duy nhất. Theo đoạn đó, tôi có ấn tượng <ui:repeat>là một trình xử lý thẻ (vì dòng này, " Lưu ý rằng chính của JSF <f:xxx><ui:xxx>... ") giống như <c:forEach>và do đó, nó được đánh giá tại thời điểm xây dựng chế độ xem (giống như giống như <c:forEach>) . Nếu đó là sau đó, không nên có bất kỳ sự khác biệt rõ ràng, chức năng giữa <ui:repeat><c:forEach>? Tôi không hiểu chính xác đoạn đó có nghĩa là gì :)
Tiny

1
Xin lỗi, tôi sẽ không làm ô nhiễm bài đăng này nữa. Tôi đã chú ý đến nhận xét trước đó của bạn nhưng không phải câu này, " Lưu ý rằng các thẻ của riêng JSF <f:xxx><ui:xxx>các thẻ không mở rộng UIComponentcũng là các trình xử lý thẻ. " Cố gắng ám chỉ đó <ui:repeat>cũng là một trình xử lý thẻ vì <ui:xxx>cũng bao gồm <ui:repeat>? Điều này có nghĩa <ui:repeat>là một trong những thành phần <ui:xxx>mở rộng UIComponent. Do đó, nó không phải là một trình xử lý thẻ. (Một số trong số chúng có thể không mở rộng UIComponent. Do đó, chúng là trình xử lý thẻ) Có phải không?
Tiny

2
@Shirgill: <c:set>không scopetạo bí danh biểu thức EL thay vì đặt giá trị được đánh giá trong phạm vi mục tiêu. scope="request"Thay vào đó, hãy thử đánh giá giá trị (trong thời gian xây dựng chế độ xem thực sự) và đặt nó làm thuộc tính yêu cầu (sẽ không bị "ghi đè" trong quá trình lặp). Dưới vỏ bọc, nó tạo và thiết lập một ValueExpressionđối tượng.
BalusC

1
@ K.Nicholas: Nó nằm dưới vỏ bọc a ClassNotFoundException. Thời gian chạy phụ thuộc của dự án của bạn bị hỏng. Rất có thể bạn đang sử dụng máy chủ không phải JavaEE như Tomcat và bạn đã quên cài đặt JSTL hoặc bạn đã vô tình bao gồm cả JSTL 1.0 và JSTL 1.1+. Bởi vì trong JSTL 1.0, gói là javax.servlet.jstl.core.*và kể từ JSTL 1.1, điều này đã trở thành javax.servlet.jsp.jstl.core.*. Có thể tìm thấy manh mối để cài đặt JSTL tại đây: stackoverflow.com/a/4928309
BalusC

13

sử dụng

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

Thx, câu trả lời tuyệt vời. Nói chung hơn: Các thẻ JSTL vẫn có ý nghĩa hay chúng ta nên coi chúng là không dùng nữa kể từ JSF 2.0?
Jan

Trong hầu hết các trường hợp, có. Nhưng đôi khi nó là thích hợp để sử dụng chúng
Bozho

3
Sử dụng h: panelgroup là một giải pháp bẩn, bởi vì nó tạo ra thẻ <span>, trong khi c: if không thêm gì vào mã html. h: panelgroup cũng có vấn đề bên trong panelGrids, vì nó nhóm các thành phần.
Rober2D2

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.