JSTL w JSF2 Facelets… ma sens?

163

Chciałbym warunkowo wypisać trochę kodu Facelets.

W tym celu tagi JSTL wydają się działać dobrze:

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

Nie jestem jednak pewien, czy jest to najlepsza praktyka? Czy jest inny sposób na osiągnięcie celu?

Jan
źródło

Odpowiedzi:

320

Wprowadzenie

JSTL <c:xxx>tagi są wszystkie taghandlers i są one wykonywane podczas widzenia czas budowy , a JSF <h:xxx>tagi są wszystkie elementy interfejsu użytkownika i są one wykonywane podczas widzenia czas renderowania .

Należy zauważyć, że z JSF własnych <f:xxx>i <ui:xxx>tylko znaczniki które nie nie wychodzą z UIComponentrównież taghandlers, np <f:validator>, <ui:include>, <ui:define>, itd. Te, które rozciągają się od UIComponentsą również komponenty JSF UI, na przykład <f:param>, <ui:fragment>, <ui:repeat>, itd. Od komponentów JSF UI tylko idi bindingatrybuty oceniane również w czasie kompilacji widoku. Zatem poniższa odpowiedź dotycząca cyklu życia JSTL dotyczy również atrybutów idi bindingkomponentów JSF.

Czas widok kompilacji jest ten moment, kiedy plik XHTML / JSP ma być analizowany i konwertowany do drzewa komponentów JSF, która jest następnie przechowywany jako UIViewRootz FacesContext. Czas renderowania widoku to moment, w którym drzewo komponentów JSF ma wygenerować kod HTML, zaczynając od UIViewRoot#encodeAll(). Tak więc: komponenty interfejsu użytkownika JSF i tagi JSTL nie działają synchronicznie, jak można by oczekiwać po kodowaniu. Możesz to wizualizować w następujący sposób: JSTL działa najpierw od góry do dołu, tworząc drzewo komponentów JSF, a następnie kolej JSF, aby ponownie uruchomić od góry do dołu, tworząc wynik HTML.

<c:forEach> vs <ui:repeat>

Na przykład ten znacznik Facelets iterujący po 3 elementach przy użyciu <c:forEach>:

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

... tworzy podczas budowania widoku trzy oddzielne <h:outputText>komponenty w drzewie komponentów JSF, z grubsza reprezentowane w następujący sposób:

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

... które z kolei indywidualnie generują swoje dane wyjściowe HTML podczas wyświetlania czasu renderowania:

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

Zwróć uwagę, że musisz ręcznie upewnić się, że identyfikatory komponentów są niepowtarzalne i że są one również oceniane w czasie kompilacji widoku.

Podczas gdy ten znacznik Facelets iteruje po 3 elementach przy użyciu <ui:repeat>, który jest komponentem interfejsu użytkownika JSF:

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

... już kończy się tak, jak jest w drzewie komponentów JSF, dzięki czemu ten sam <h:outputText>komponent jest ponownie używany podczas renderowania widoku do generowania danych wyjściowych HTML na podstawie bieżącej rundy iteracji:

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

Zauważ, że <ui:repeat>jako NamingContainerskładnik już zapewnił niepowtarzalność identyfikatora klienta w oparciu o indeks iteracji; nie jest również możliwe użycie EL w idatrybutach komponentów potomnych w ten sposób, ponieważ jest on również oceniany podczas budowania widoku, podczas gdy #{item}jest dostępny tylko podczas renderowania widoku. To samo dotyczy h:dataTablei podobnych komponentów.

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

Jako kolejny przykład, ten znacznik Facelets warunkowo dodaje różne tagi przy użyciu <c:if>(możesz również użyć <c:choose><c:when><c:otherwise>do tego):

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

... w przypadku type = TEXTtylko dodania <h:inputText>komponentu do drzewa komponentów JSF:

<h:inputText ... />

Podczas gdy ten znacznik Facelets:

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

... zakończy się dokładnie tak, jak powyżej w drzewie komponentów JSF, niezależnie od warunku. Może to więc skończyć się w „rozdętym” drzewie komponentów, jeśli masz ich wiele i są one faktycznie oparte na modelu „statycznym” (tj. fieldNigdy się nie zmieniają przynajmniej w zakresie widoku). Możesz również napotkać problemy z EL, gdy masz do czynienia z podklasami z dodatkowymi właściwościami w wersjach Mojarra starszych niż 2.2.7.

<c:set> vs <ui:param>

Nie są wymienne. Te <c:set>zestawy zmienna w zakresie EL, która jest dostępna tylko po położenia znacznika czasu podczas kompilacji widok, ale nigdzie w widoku podczas podglądu na czas renderowania. <ui:param>Przechodzi zmienna EL do szablonu Facelet zawarte przez <ui:include>, <ui:decorate template>lub <ui:composition template>. Starsze wersje JSF zawierały błędy, w wyniku których <ui:param>zmienna jest również dostępna poza omawianym szablonem Facelet, nigdy nie należy na tym polegać.

<c:set>Bez scopeatrybutu będzie zachowywać się jak aliasu. Nie buforuje wyniku wyrażenia EL w żadnym zakresie. Dzięki temu może być doskonale używany wewnątrz, na przykład iteracji komponentów JSF. Tak więc np. Poniżej będzie działać dobrze:

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

Nie nadaje się tylko do np. Obliczania sumy w pętli. Zamiast tego użyj strumienia EL 3.0 :

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

Dopiero, gdy ustawisz scopeatrybut jednej z dopuszczalnych wartości request, view, session, lub application, po czym zostanie on natychmiast oceniano w czasie widok budować i przechowywane w określonym zakresie.

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

Zostanie to ocenione tylko raz i będzie dostępne #{dev}w całej aplikacji.

Użyj JSTL do sterowania budowaniem drzewa komponentów JSF

Korzystanie JSTL może prowadzić nie tylko nieoczekiwane wyniki, gdy są stosowane w środku elementów JSF iteracji, takie jak <h:dataTable>, <ui:repeat>itp, lub gdy znacznik JSTL cechy zależą od wyników zdarzeń JSF jak preRenderViewi złożone wartości tworzą się w modelu, które nie są dostępne w widoku czas budowy . Dlatego używaj znaczników JSTL tylko do kontrolowania przepływu budowania drzewa komponentów JSF. Użyj komponentów JSF UI, aby kontrolować przepływ generowania danych wyjściowych HTML. Nie varwiąż iteracyjnych komponentów JSF z atrybutami znaczników JSTL. Nie polegaj na zdarzeniach JSF w atrybutach tagów JSTL.

Za każdym razem, gdy myślisz, że musisz powiązać komponent z zapasowym komponentem bean za pośrednictwem bindinglub pobrać jeden za pośrednictwem findComponent()i utworzyć / manipulować jego elementami podrzędnymi za pomocą kodu Java w komponencie bean zapasowym z, new SomeComponent()a co nie, należy natychmiast przerwać i rozważyć użycie JSTL. Ponieważ JSTL jest również oparty na XML, kod potrzebny do dynamicznego tworzenia komponentów JSF stanie się o wiele bardziej czytelny i łatwiejszy w utrzymaniu.

Ważne jest, aby wiedzieć, że wersje Mojarra starsze niż 2.1.18 miały błąd w częściowym zapisywaniu stanu podczas odwoływania się do komponentu bean o zasięgu widoku w atrybucie tagu JSTL. Cały widok scoped fasola będzie nowo odtworzone zamiast pobierane z widoku drzewa (po prostu dlatego, że pełne drzewo widok nie jest jeszcze dostępna w seriach punkt JSTL). Jeśli spodziewasz się lub przechowujesz jakiś stan w komponencie bean z zakresem widoku za pomocą atrybutu tagu JSTL, to nie zwróci on oczekiwanej wartości lub zostanie „utracony” w komponencie bean o zasięgu rzeczywistym, który jest przywracany po wyświetleniu widoku drzewo jest zbudowane. W przypadku, gdy nie możesz zaktualizować Mojarra do wersji 2.1.18 lub nowszej, obejście polega na wyłączeniu częściowego zapisywania stanu w web.xmlnastępujący sposób:

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

Zobacz też:

Aby zobaczyć przykłady z rzeczywistego świata, w których tagi JSTL są pomocne (tj. Gdy są naprawdę poprawnie używane podczas tworzenia widoku), zobacz następujące pytania / odpowiedzi:


W skrócie

Jeśli chodzi o konkretne wymaganie funkcjonalne, jeśli chcesz warunkowo renderować komponenty JSF, renderedzamiast tego użyj atrybutu komponentu JSF HTML, szczególnie jeśli #{lpc}reprezentuje aktualnie iterowany element komponentu iterującego JSF, taki jak <h:dataTable>lub <ui:repeat>.

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

Lub, jeśli chcesz warunkowo budować (tworzyć / dodawać) komponenty JSF, nadal używaj JSTL. Jest to o wiele lepsze niż werbalne robienie tego new SomeComponent()w Javie.

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

Zobacz też:

BalusC
źródło
3
@Aklin: Nie? A co z tym przykładem ?
BalusC
1
Nie potrafię poprawnie zinterpretować pierwszego akapitu przez długi czas (podane przykłady są jednak bardzo jasne). Dlatego zostawiam ten komentarz jako jedyny sposób. W tym akapicie odnoszę wrażenie, że <ui:repeat>jest to program obsługi tagów (z powodu tego wiersza „ Zwróć uwagę, że własny JSF <f:xxx>i <ui:xxx>... ”) jest tak samo, jak <c:forEach>i dlatego jest oceniany w czasie kompilacji widoku (ponownie tak samo jak <c:forEach>) . Jeśli tak, to nie powinno być żadnej widocznej, funkcjonalnej różnicy między <ui:repeat>i <c:forEach>? Nie rozumiem, co dokładnie oznacza ten akapit :)
Tiny
1
Przepraszam, nie będę dalej zanieczyszczać tego postu. Wziąłem swój poprzedni komentarz do mojej uwagi, ale nie robi to zdanie: „ Należy zauważyć, że JSF własnego <f:xxx>i <ui:xxx>znaczników, które nie rozciągają UIComponentsię również koparki tag. ” Próbuje się sugerować, że <ui:repeat>to również obsługi znacznika, ponieważ <ui:xxx>obejmuje również <ui:repeat>? Powinno to zatem oznaczać, że <ui:repeat>jest to jeden z elementów, <ui:xxx>który się rozciąga UIComponent. Dlatego nie jest to program obsługujący znaczniki. (Niektóre z nich mogą się nie rozszerzać UIComponent. W związku z tym służą do obsługi tagów).
Mały
2
@Shirgill: <c:set>without scopetworzy alias wyrażenia EL zamiast ustawiania wartości szacowanej w zakresie docelowym. Spróbuj scope="request"zamiast tego, co natychmiast oszacuje wartość (faktycznie w czasie kompilacji widoku) i ustawi ją jako atrybut żądania (który nie zostanie „nadpisany” podczas iteracji). Pod kołdrą tworzy i ustawia ValueExpressionobiekt.
BalusC
1
@ K.Nicholas: Jest pod kołdrą a ClassNotFoundException. Zależności wykonawcze twojego projektu są zepsute. Najprawdopodobniej używasz serwera innego niż JavaEE, takiego jak Tomcat i zapomniałeś zainstalować JSTL lub przypadkowo dołączyłeś zarówno JSTL 1.0, jak i JSTL 1.1+. Ponieważ w JSTL 1.0 pakiet jest, javax.servlet.jstl.core.*a od JSTL 1.1 stał się javax.servlet.jsp.jstl.core.*. Wskazówki dotyczące instalacji JSTL można znaleźć tutaj: stackoverflow.com/a/4928309
BalusC
13

posługiwać się

<h:panelGroup rendered="#{lpc.verbose}">
  ...
</h:panelGroup>
Bozho
źródło
Dzięki, świetna odpowiedź. Bardziej ogólnie: czy tagi JSTL nadal mają sens, czy też powinniśmy uznać je za przestarzałe od czasu JSF 2.0?
styczeń
W większości przypadków tak. Ale czasami warto z nich korzystać
Bozho
3
Używanie h: panelGroup jest brudnym rozwiązaniem, ponieważ generuje tag <span>, podczas gdy c: if nic nie dodaje do kodu html. h: panelGroup jest również problematyczne wewnątrz panelGrids, ponieważ grupuje elementy.
Rober2D2
4

Aby uzyskać wyjście podobne do przełącznika, możesz użyć przełącznika z rozszerzeń PrimeFaces.

Ravshan Samandarov
źródło