Изменение дерева компонентов JSF в PhaseListener
Вопрос
У меня возникла проблема.
Я реализовал PhaseListener, который предназначен для добавления класса style к любым компонентам UIInput в дереве, к которым прикреплены сообщения, и удаляет класс style, если к ним не прикреплено никаких сообщений.
PhaseListener запускается на этапе RENDER_RESPONSE и выполняет свою работу как в методах beforePhase, так и в методах afterPhase во время отладки.Во время отладки я обнаружил, что у beforePhase нет доступа к полному дереву компонентов, но у afterPhase есть.Однако любые изменения, внесенные в afterPhase, не отображаются.
Как мне это сделать?Я хочу, чтобы это было полностью на стороне сервера.
Спасибо,
Джеймс
Решение 2
Реализовано с использованием ViewHandler, однако это неэффективно.PhaseListener на этапе ответа рендеринга не имеет доступа к дереву компонентов.
Другие советы
Дерево компонентов JSF доступно только после завершения просмотра времени сборки.В RENDER_RESPONSE
фаза не обязательно является подходящим моментом для получения доступа к полному дереву компонентов JSF до его рендеринга.Во время первоначального запроса GET без каких-либо <f:viewAction>
, полное дерево компонентов доступно только в afterPhase
поскольку он строится во время RENDER_RESPONSE
.Во время обратной передачи полное дерево компонентов доступно в beforePhase
, однако, когда произойдет переход к другому представлению, оно все равно будет изменено во время в RENDER_RESPONSE
фаза, так что любые изменения будут потеряны.
Чтобы узнать, каково именно время сборки представления, перейдите к вопросу Каково время сборки представления?
По сути, вы хотите зацепиться за "просмотр времени рендеринга", а не beforePhase
из RENDER_RESPONSE
фаза.JSF предлагает несколько способов зацепиться за него:
В каком-нибудь основном шаблоне прикрепите
preRenderView
слушатель для<f:view>
.<f:view ...> <f:event type="preRenderView" listener="#{bean.onPreRenderView}" /> ... </f:view>
public void onPreRenderView(ComponentSystemEvent event) { UIViewRoot view = (UIViewRoot) event.getSource(); // The view is the component tree. Just modify it here accordingly. // ... }
Или внедрить глобальную
SystemEventListener
дляPreRenderViewEvent
.public class YourPreRenderViewListener implements SystemEventListener { @Override public boolean isListenerForSource(Object source) { return source instanceof UIViewRoot; } @Override public void processEvent(SystemEvent event) throws AbortProcessingException { UIViewRoot view = (UIViewRoot) event.getSource(); // The view is the component tree. Just modify it here accordingly. // ... } }
Чтобы запустить его, зарегистрируйте его, как показано ниже, в
faces-config.xml
:<application> <system-event-listener> <system-event-listener-class>com.example.YourPreRenderViewListener</system-event-listener-class> <system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class> </system-event-listener> </application>
Или, предоставьте пользовательский
ViewHandler
в котором вы выполняете работу вrenderView()
.public class YourViewHandler extends ViewHandlerWrapper { private ViewHandler wrapped; public YourViewHandler(ViewHandler wrapped) { this.wrapped = wrapped; } @Override public void renderView(FacesContext context, UIViewRoot view) { // The view is the component tree. Just modify it here accordingly. // ... // Finally call super so JSF can do the rendering job. super.renderView(context, view); } @Override public ViewHandler getWrapped() { return wrapped; } }
Чтобы запустить его, зарегистрируйтесь, как показано ниже, в
faces-config.xml
:<application> <view-handler>com.example.YourViewHandler</view-handler> </application>
Или, зацепись
ViewDeclarationLanguage#renderView()
, но это немного на грани, поскольку на самом деле предназначено не для манипулирования деревом компонентов, а для того, чтобы манипулировать тем, как отображать представление.
Не связанный что касается конкретной проблемы, то все это не является правильным решением для конкретного функционального требования, как указано в вашем вопросе:
который предназначен для добавления класса style к любым компонентам UIInput в дереве, к которым прикреплены сообщения, и удаляет класс style, если к ним не прикреплено никаких сообщений
Вам действительно лучше выбрать решение на стороне клиента, а не манипулировать деревом компонентов (которое в конечном итоге окажется в состоянии компонента JSF!).Представьте себе случай входных данных в повторяющихся компонентах, таких как <ui:repeat><h:inputText>
.Физически в дереве есть только один входной компонент, а не несколько!Манипулирование классом стиля с помощью UIInput#setStyleClass()
будет представлен в каждом цикле итерации.
Вам лучше всего посетить дерево компонентов, используя UIViewRoot#visitTree()
как показано ниже, и соберите все идентификаторы клиентов недопустимых входных компонентов (это visitTree()
подход будет прозрачно учитывать повторяющиеся компоненты):
Set<String> invalidInputClientIds = new HashSet<>();
view.visitTree(VisitContext.createVisitContext(context, null, EnumSet.of(VisitHint.SKIP_UNRENDERED)), new VisitCallback() {
@Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
if (!input.isValid()) {
invalidInputClientIds.add(input.getClientId(context.getFacesContext()));
}
}
return VisitResult.ACCEPT;
}
});
А затем после этого пройти invalidInputClientIds
в виде массива JSON в JavaScript, который затем будет захватывать их через document.getElementById()
и изменить className
атрибут.
for (var i = 0; i < invalidInputClientIds.length; i++) {
var invalidInput = document.getElementById(invalidInputClientIds[i]);
invalidInput.className += ' error';
}
Библиотека утилит JSF Вселикий имеет <o:highlight>
компонент, который делает именно это.