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

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

Для этого теги JSTL работают нормально:

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

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

3 ответа

Решение

Вступление

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

Обратите внимание, что из собственного JSF <f:xxx> а также <ui:xxx> теги только те, которые не выходят из UIComponent также являются обработчиками тегов, например, <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 для управления потоком генерации вывода HTML. Не связывайте var итерации компонентов JSF в атрибуты тегов JSTL. Не полагайтесь на события JSF в атрибутах тегов JSTL.

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

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

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

Смотрите также:

Использование

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

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

Для вывода в виде переключателя вы можете использовать переключатель из простых-расширений.

Другие вопросы по тегам