JSTL в JSF2 Facelets ... має сенс?


163

Я хотів би умовно вивести трохи коду Facelets.

З цією метою теги JSTL, здається, працюють нормально:

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

Однак я не впевнений, чи це найкраща практика? Чи є інший спосіб досягти своєї мети?

Відповіді:


320

Вступ

<c:xxx>Теги JSTL - це всі теґандлери, і вони виконуються під час збирання перегляду , тоді як <h:xxx>теги JSF - всі компоненти інтерфейсу, і вони виконуються під час візуалізації перегляду .

Зверніть увагу , що з власних JSF, <f:xxx>і <ui:xxx>тегів тільки ті , у яких НЕ простягаються від UIComponentтакож taghandlers, наприклад <f:validator>, <ui:include>, <ui:define>і т.д. Ті , які простягаються від UIComponentтакож компонент JSF призначеного для користувача інтерфейсу, наприклад <f:param>, <ui:fragment>, <ui:repeat>і т.д. З компонентів для користувача інтерфейсу JSF тільки idі bindingатрибути є також оцінюється під час збирання перегляду. Таким чином, нижченаведена відповідь щодо життєвого циклу JSTL також стосується idта bindingатрибутів компонентів JSF.

Час складання представлення - це той момент, коли файл XHTML / JSP повинен бути розібраний і перетворений у дерево компонентів JSF, яке потім зберігається UIViewRootз FacesContext. Час візуалізації перегляду - це той момент, коли дерево компонентів JSF збирається генерувати HTML, починаючи з UIViewRoot#encodeAll(). Отже: компоненти інтерфейсу JSF та теги JSTL не працюють синхронно, як ви очікували від кодування. Ви можете візуалізувати його так: JSTL спочатку працює зверху вниз, створюючи дерево компонентів JSF, потім черга JSF знову запускається зверху вниз, створюючи вихід HTML.

<c:forEach> проти <ui:repeat>

Наприклад, ця розмітка Facelets розміщує ітерацію над 3 елементами, використовуючи <c:forEach>:

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

... створює під час створення перегляду три окремі <h:outputText>компоненти в дереві компонентів JSF, приблизно представлені так:

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

... які, в свою чергу, індивідуально генерують свій вихідний код HTML під час візуалізації перегляду:

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

Зауважте, що вам потрібно вручну забезпечити унікальність ідентифікаторів компонента, і що вони також оцінюються під час збирання перегляду.

Хоча ця розмітка Facelets розміщує ітерацію над 3 елементами <ui:repeat>, що є компонентом інтерфейсу JSF:

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

... вже закінчується як є у дереві компонентів JSF, завдяки чому той самий <h:outputText>компонент під час візуалізації перегляду використовується повторно, щоб генерувати вихідний HTML на основі поточного циклу ітерації:

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

Зауважте, що <ui:repeat>як NamingContainerкомпонент вже забезпечив унікальність ідентифікатора клієнта на основі індексу ітерації; так само неможливо використовувати EL в idатрибуті дочірніх компонентів таким чином, оскільки він також оцінюється під час складання перегляду, поки #{item}він доступний лише під час візуалізації перегляду. Те саме стосується h:dataTableі подібних компонентів.

<c:if>/ <c:choose>протиrendered

Як інший приклад, ця розмітка Facelets умовно додає різні теги за допомогою <c:if>(ви також можете використовувати <c:choose><c:when><c:otherwise>для цього):

<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 = TEXTдодавання <h:inputText>компонента до дерева компонентів JSF:

<h:inputText ... />

Хоча ця розмітка Facelets:

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

... закінчиться точно так, як описано вище в дереві компонентів JSF, незалежно від умови. Таким чином, це може опинитися у «роздутому» компоненті, коли у вас їх багато, і вони фактично базуються на «статичній» моделі (тобто, fieldвона ніколи не змінюється протягом принаймні області перегляду). Також ви можете зіткнутися з проблемою EL, коли ви маєте справу з підкласами з додатковими властивостями у версіях Mojarra до 2.2.7.

<c:set> проти <ui:param>

Вони не взаємозамінні. В <c:set>встановлює змінну в рамках EL, який доступний тільки після розташування мітки під час перегляду збірки, але в будь-якій точці зору під час зору часу візуалізації. <ui:param>Проходить змінна EL в шаблон Facelet включені з допомогою <ui:include>, <ui:decorate template>або <ui:composition template>. Старіші версії JSF мали помилки, завдяки чому <ui:param>змінна також доступна поза відповідним шаблоном Facelet, на це ніколи не слід покладатися.

Атрибут <c:set>без scopeповодитиметься як псевдонім. Він не кешує результат вираження EL в будь-якій області. Таким чином, він може прекрасно використовуватися всередині, наприклад, ітераційні компоненти JSF. Таким чином, наприклад, нижче буде добре працювати:

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

Це не підходить, наприклад, для обчислення суми в циклі. Для цього замість цього використовуйте потік EL 3.0 :

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

Тільки, коли ви встановите scopeатрибут з одним з допустимих значень request, view, sessionабо application, то він буде оцінюватися безпосередньо під час складання виду і знаходиться в зазначеній галузі.

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

Це буде оцінено лише один раз і доступне як #{dev}у всій програмі.

Використовуйте JSTL для управління будівництвом компонентів JSF

Використання JSTL може призвести до несподіваних результатів лише при використанні всередині ітераційних компонентів JSF, таких як <h:dataTable>, <ui:repeat>тощо, або коли атрибути тегів JSTL залежать від результатів подій JSF, таких як preRenderViewабо подані значення форми в моделі, які недоступні під час збирання перегляду . Отже, використовуйте теги JSTL лише для управління потоком побудови компонентів JSF. Використовуйте компоненти JSF UI для управління потоком генерації вихідного HTML. Не прив'язуйте varітераційних компонентів JSF до атрибутів тегів JSTL. Не покладайтеся на події JSF в атрибутах тегів JSTL.

Коли ви думаєте, що вам потрібно прив’язати компонент до резервного файлу через bindingабо захопити його через findComponent(), а також створити / маніпулювати його дітьми, використовуючи код Java в резервному бобі, new SomeComponent()а що ні, тоді слід негайно зупинитись і розглянути можливість використання JSTL. Оскільки JSTL також базується на XML, код, необхідний для динамічного створення компонентів JSF, стане набагато кращим для читання та ремонтування.

Важливо знати, що версії Mojarra, старші 2.1.18, мали помилку в частковому збереженні стану при посиланні на переглянуту квасолю в атрибуті тегу JSTL. Весь об'єм обширного виду буде знову відтворений замість вилученого з дерева перегляду (просто тому, що повне дерево перегляду ще не доступне в точці запуску JSTL). Якщо ви очікуєте або зберігаєте деякий стан у переглянутому діапазоні за атрибутом тегу JSTL, він не поверне значення, яке ви очікуєте, або воно буде «втрачено» в реальному діапазоні масштабування, який буде відновлено після перегляду будується дерево. Якщо ви не можете оновити до Mojarra 2.1.18 або новішої версії, вирішення проблеми полягає в тому, щоб вимкнути часткове економію стану, web.xmlяк показано нижче:

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

Дивитися також:

Щоб побачити приклади реального світу, де теги JSTL є корисними (тобто, коли вони справді правильно використовуються під час створення представлення даних), див. Наступні питання / відповіді:


Коротко

Що стосується вашого конкретного функціонального вимоги, якщо ви хочете винести компоненти JSF умовно, використовуйте renderedатрибут компонента JSF HTML замість цього, особливо , якщо #{lpc}є в даний час ітерованих елемента в JSF ітерації компонента , таких як <h:dataTable>або <ui:repeat>.

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

Або, якщо ви хочете умовно створити (створити / додати) компоненти JSF, тоді продовжуйте використовувати JSTL. Це набагато краще, ніж багатослівно робити new SomeComponent()в Java.

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

Дивитися також:


3
@Aklin: Ні? Як щодо цього прикладу ?
BalusC

1
Я не можу довго інтерпретувати перший абзац належним чином (хоча наведені приклади дуже зрозумілі). Отже, я залишаю цей коментар як єдиний спосіб. За цим абзацом я маю враження, що <ui:repeat>це обробник тегів (через цей рядок " Зверніть увагу, що JSF є власним <f:xxx>і <ui:xxx>... ") так само, як <c:forEach>і, отже, він оцінюється при створенні часу перегляду (знову просто так, як просто <c:forEach>) . Якщо це тоді, не повинно бути видимих, функціональних відмінностей між <ui:repeat>і <c:forEach>? Я не розумію, що саме означає цей абзац :)
Tiny

1
На жаль, я більше не забруднюю цю публікацію. Я взяв до уваги ваш попередній коментар, але чи не це речення: " Зауважте, що власні JSF <f:xxx>і <ui:xxx>теги, які не поширюються UIComponent, також є обробниками тегів. " Намагається зрозуміти, що <ui:repeat>це також обробник тегів, оскільки він <ui:xxx>також включає <ui:repeat>? Тоді це повинно означати, що <ui:repeat>це один із компонентів, <ui:xxx>що розширюються UIComponent. Отже, це не обробник тегів. (Деякі з них можуть не поширюватися UIComponent. Отже, вони обробляють теги) Чи це?
Крихітні

2
@Shirgill: <c:set>без scopeстворює псевдонім виразу EL замість встановлення оціненого значення в цільовій області. Спробуйте scope="request"замість цього, який негайно оцінить значення (дійсно під час збирання перегляду) і встановить його як атрибут запиту (який не буде "переписано" під час ітерації). Під обкладинками він створює та встановлює ValueExpressionоб’єкт.
BalusC

1
@ K.Nicholas: Це під кришками a ClassNotFoundException. Залежності від вашого проекту порушені. Швидше за все, ви використовуєте не-JavaEE-сервер, наприклад Tomcat, і ви забули встановити JSTL, або ви випадково включили як JSTL 1.0, так і JSTL 1.1+. Тому що в JSTL 1.0 пакет є, javax.servlet.jstl.core.*а з JSTL 1.1 таким став javax.servlet.jsp.jstl.core.*. Ключ для установки JSTL можна знайти тут: stackoverflow.com/a/4928309
BalusC

13

використання

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

Thx, чудова відповідь. Більш загально: чи все ще мають сенс теги JSTL чи слід вважати їх застарілими з часу JSF 2.0?
січня

У більшості випадків так. Але іноді доречно їх використовувати
Божо

3
Використання h: panelGroup - брудне рішення, оскільки воно генерує тег <span>, а c: якщо нічого не додає до html-коду. h: panelGroup також проблематичний всередині panelGrids, оскільки він групує елементи.
Rober2D2

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.