修改 PhaseListener 中的 JSF 组件树
题
我有一个问题。
我实现了一个 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()
如下并收集无效输入组件的所有客户端 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 有一个 <o:highlight>
正是执行此操作的组件。