JSF 组件树仅在视图构建时间之后才可用。这RENDER_RESPONSE
阶段不一定是在渲染之前访问完整的 JSF 组件树的好时机。在初始 GET 请求期间没有任何<f:viewAction>
,完整的组件树仅在afterPhase
因为它是在RENDER_RESPONSE
。在回发期间,完整的组件树在beforePhase
但是,当导航到不同的视图时,它仍然会改变during the RENDER_RESPONSE
阶段,因此任何修改都会丢失。
要了解视图构建时间到底是多少,请转到问题视图构建时间是多少?
您基本上想要挂钩“查看渲染时间”而不是beforePhase
of 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 for 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()
如下并收集无效输入组件的所有客户端 ID(此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
类似于 JavaScript 的 JSON 数组,然后通过 JavaScript 获取它们document.getElementById()
并改变className
属性。
for (var i = 0; i < invalidInputClientIds.length; i++) {
var invalidInput = document.getElementById(invalidInputClientIds[i]);
invalidInput.className += ' error';
}
JSF 实用程序库OmniFaces has a <o:highlight>正是执行此操作的组件。