JSF 组件树仅在视图构建时间之后可用。 RENDER_RESPONSE 阶段不一定是在呈现之前访问完整 JSF 组件树的好时机。在没有任何<f:viewAction> 的初始GET 请求期间,完整的组件树仅在afterPhase 中可用,因为它是在RENDER_RESPONSE 期间构建的。在回发期间,完整的组件树在beforePhase 中可用,但是,当导航到不同的视图时,它仍然会在RENDER_RESPONSE 阶段更改期间,所以任何修改会丢失。
要了解视图构建时间的确切含义,请查看问题What's the view build time?
您基本上想要挂钩“查看渲染时间”而不是RENDER_RESPONSE 阶段的beforePhase。 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.
// ...
}
-
或者,为PreRenderViewEvent 实现一个全局SystemEventListener。
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 组件添加一个样式类,如果没有附加任何消息,则删除该样式类 p>
您最好选择客户端解决方案,而不是操作组件树(最终会进入 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 以 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> 组件,它正是这样做的。