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
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>
或者, hook on ViewDeclarationLanguage#renderView()
,但这有点边缘,因为它并不是真的打算操纵组件树,而是操纵如何呈现视图。
与具体问题无关,这一切都不是您问题中所述具体功能要求的正确解决方案:
这意味着将样式类添加到树中附加了消息的任何 UIInput 组件,如果没有附加任何消息,则删除样式类
您最好选择客户端解决方案,而不是操纵组件树(最终会进入 JSF 组件状态!)。想象一下在迭代组件中输入的情况,例如<ui:repeat><h:inputText>
. 树中实际上只有一个输入组件,而不是多个!操作样式类 viaUIInput#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
JSON 数组的风格传递给 JavaScript,然后 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>
组件可以做到这一点。