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 для управления потоком генерации вывода 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()в Java.

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

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

BalusC
источник
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. Следовательно, они являются обработчиками тегов) Так ли это?
Tiny
2
@Shirgill: <c:set>без scopeсоздает псевдоним выражения EL вместо установки оцененного значения в целевой области. scope="request"Вместо этого попробуйте , который сразу оценит значение (действительно во время сборки представления) и установит его как атрибут запроса (который не будет «перезаписан» во время итерации). Под одеялом он создает и устанавливает ValueExpressionобъект.
BalusC
1
@ K.Nicholas: это под одеялом 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>
Bozho
источник
Спасибо, отличный ответ. В целом: действительно ли теги JSTL все еще имеют смысл, или мы должны считать их устаревшими после JSF 2.0?
Jan
В большинстве случаев да. Но иногда их целесообразно использовать
Божо
3
Использование h: panelGroup - грязное решение, потому что оно генерирует тег <span>, а c: if ничего не добавляет в html-код. h: panelGroup также проблематичен внутри panelGrids, так как он группирует элементы.
Rober2D2
4

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

Равшан Самандаров
источник