【问题标题】:Modifying JSF Component Tree in PhaseListener在 PhaseListener 中修改 JSF 组件树
【发布时间】:2010-03-18 10:28:44
【问题描述】:

我遇到了问题。

我已经实现了一个 PhaseListener,它旨在将样式类添加到树中的任何带有消息的 UIInput 组件,如果样式类没有附加任何消息,则删除样式类。

PhaseListener 在 RENDER_RESPONSE 阶段运行,并且在调试时它在 beforePhase 和 afterPhase 方法中都起作用。在调试时,我发现 beforePhase 无法访问完整的组件树,但 afterPhase 可以。不过,在 afterPhase 中所做的任何更改都不会呈现。

我该怎么做?我希望这完全是服务器端。

谢谢,

詹姆斯

【问题讨论】:

    标签: response listener render jsf-2 phase


    【解决方案1】:

    JSF 组件树仅在视图构建时间之后可用。 RENDER_RESPONSE 阶段不一定是在呈现之前访问完整 JSF 组件树的好时机。在没有任何<f:viewAction> 的初始GET 请求期间,完整的组件树仅在afterPhase 中可用,因为它是在RENDER_RESPONSE 期间构建的。在回发期间,完整的组件树在beforePhase 中可用,但是,当导航到不同的视图时,它仍然会在RENDER_RESPONSE 阶段更改期间,所以任何修改会丢失。

    要了解视图构建时间的确切含义,请查看问题What's the view build time?

    您基本上想要挂钩“查看渲染时间”而不是RENDER_RESPONSE 阶段的beforePhase。 JSF 提供了几种方法来连接它:

    1. 在某些主模板中,将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.
          // ...
      }        
      
    2. 或者,为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>
      
    3. 或者,提供自定义 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>
      
    4. 或者,挂上ViewDeclarationLanguage#renderView(),但这有点边缘,因为它并不是真的打算操纵组件树,而是操纵如何呈现视图。

      李>

    与具体问题无关,这一切都不是您问题中所述具体功能要求的正确解决方案:

    这意味着向树中任何附加了消息的 UIInput 组件添加一个样式类,如果没有附加任何消息,则删除该样式类 p>

    您最好选择客户端解决方案,而不是操作组件树(最终会进入 JSF 组件状态!)。想象一下在迭代组件中输入的情况,例如&lt;ui:repeat&gt;&lt;h:inputText&gt;。树中实际上只有一个输入组件,而不是多个!通过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 有一个 &lt;o:highlight&gt; 组件,它正是这样做的。

    【讨论】:

    • 谢谢。实际上我已经很多年没有接触过 JSF 了 :) 但也许它对其他人有帮助。
    【解决方案2】:

    使用 ViewHandler 实现,但效率不高。渲染响应阶段的 PhaseListener 无权访问组件树。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-27
      • 1970-01-01
      • 2015-12-06
      • 1970-01-01
      • 2010-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多