Изменение дерева компонентов JSF в PhaseListener
У меня проблема.
Я реализовал PhaseListener, который предназначен для добавления класса стиля к любым компонентам UIInput в дереве, к которым прикреплены сообщения, и удаляет класс стиля, если к нему не прикреплено никаких сообщений.
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()
, но это немного на грани, так как на самом деле не намерено манипулировать деревом компонентов, а манипулировать тем, как визуализировать представление.
Вне зависимости от конкретной проблемы, все это не является правильным решением для конкретного функционального требования, как указано в вашем вопросе:
который предназначен для добавления класса стиля к любым компонентам UIInput в дереве, к которым прикреплены сообщения, и удаляет класс стиля, если к нему не прикреплено ни одного сообщения.
Вы бы лучше отправились на решение на стороне клиента, чем манипулировали деревом компонентов (которое в конечном итоге оказалось бы в состоянии компонента 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 OmniFaces имеет <o:highlight>
компонент, который делает именно это.