JSTL в JSF2 Facelets... имеет смысл?

Я хотел бы вывести бит кода Facelets условно.

С этой целью теги JSTL работают нормально:

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

Однако я не уверен, что это лучшая практика? Есть ли другой способ достичь моей цели?

Ответ 1

Введение

Теги

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

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

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

<c:forEach> vs <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 на основе текущей итерации round:

<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} доступен только во время рендеринга представления.

<c:if>/<c:choose> vs 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> vs <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 для управления потоком генерации вывода HTML. Не связывайте var итерации JSF-компонентов с атрибутами тега JSTL. Не полагайтесь на события JSF в атрибутах тега JSTL.

В любое время, когда вы считаете, что вам нужно привязать компонент к резервной копии bean через binding или захватить один через findComponent(), а также создать/обработать его дочерние элементы с использованием кода Java в резервной копии bean с помощью new SomeComponent() а что нет, тогда вам следует немедленно остановиться и рассмотреть возможность использования JSTL. Поскольку JSTL также основан на XML, код, необходимый для динамического создания компонентов JSF, станет намного лучше читаемым и поддерживаемым.

Важно знать, что версии Mojarra старше, чем 2.1.18, имели ошибку в частичном сохранении состояния при ссылке на область видимости bean в атрибуте тега JSTL. Весь вид, охваченный bean, будет вновь воссоздан вместо того, чтобы извлекаться из дерева представлений (просто потому, что полное дерево просмотра еще не доступно в момент запуска JSTL). Если вы ожидаете или сохраняете какое-либо состояние в области видимости с тегом bean с помощью атрибута тега JSTL, то оно не вернет ожидаемое значение или оно будет "потеряно" в режиме реального охвата bean, который восстанавливается после того, как дерево просмотра построено. Если вы не можете перейти на 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 на HTML-компоненте JSF, особенно если #{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>

См. также:

Ответ 2

использовать

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

Ответ 3

Извините за отдельный ответ, но я не мог комментировать ответы выше.

Для переключающего вывода вы можете использовать переключатель primefaces-extensions.