【问题标题】:JSF 1.1: How to display component label instead of clientId in validation messages?JSF 1.1:如何在验证消息中显示组件标签而不是 clientId?
【发布时间】:2012-12-31 18:09:32
【问题描述】:

我正在编写一个自定义组件来在弹出窗口中显示验证消息,它运行良好,但我仍然有一个问题:我想在验证消息中显示组件标签而不是 cliendIds。

我已经看过这个问题和类似问题:

Removing the component Id from validation message when using message bundle in JSF

但我使用的是 jsf 1.1,如果我尝试为属性 labelrequiredMessage 设置值,则会出现编译错误。 所以我尝试使用<outputLabel /> 为组件分配标签:

<h:outputLabel for="phone" value="Phone number" />
<h:inputText id="phone" required="true" value="#{backingBean.phoneNumber}" />

没有效果。

jsf 1.1 中是否有一种简单的方法可以让标签出现在验证消息中而不是客户端 ID 中? 如果没有,给定一个组件,我如何检索其相关的标签组件以在 Java 代码中完成工作?

谢谢。

【问题讨论】:

    标签: jsf


    【解决方案1】:

    我找到了一个不需要修改现有 JSF 代码的解决方案。

    1. 让我的组件渲染器扩展 Tomahawk 的 HtmlMessagesRenderer,它能够使用组件标签而不是其 id
    2. 使用HtmlMessagesRenderer#findInputId 检索带有消息的组件的Id
    3. 使用HtmlMessagesRenderer#findInputLabel 检索带有消息的组件的标签
    4. 将消息中的组件 ID 替换为组件标签。

    以下是我的组件 Render encodeBegin 方法的代码摘录,其中我创建了两个独立的循环,第一个用于组件消息,另一个用于全局消息。

    注意FacesContext#getClientIdsWithMessages 也返回null,这被认为是全局消息的clientId。

    请注意,因为组件本身设法检索和使用组件标签(如果存在);这个解决方案只需要在JSF页面代码中放置一个&lt;myCustomMessages /&gt;标签。

    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
    // ....
    
    // replace id with label in messages for components
            Iterator<String> clientIdsWithMessages=context.getClientIdsWithMessages();
            while(clientIdsWithMessages.hasNext()){
                String clientIdWithMessages=clientIdsWithMessages.next();
                if(clientIdWithMessages!=null){
                    Iterator<FacesMessage> componentMessages=context.getMessages(clientIdWithMessages);
                    while(componentMessages.hasNext()){
                        String stringMessage=componentMessages.next().getDetail();
                        try{
                            String inputId =findInputId(context, clientIdWithMessages);
                            String inputLabel = findInputLabel(context, clientIdWithMessages);
                            if(inputId!=null && inputLabel!=null)
                                stringMessage=stringMessage.replace(inputId, inputLabel);
                        } catch(Exception e){
                            // do nothing in this catch, just let the message be rendered with the componentId instead of a label                       }
                        msgBuilder.append(stringMessage);
                        msgBuilder.append("<br />");
                    }
                }
            }
    
    
            // process global messages
            Iterator<FacesMessage> globalMessages=context.getMessages(null);
            while(globalMessages.hasNext()){
                FacesMessage message=globalMessages.next();
                msgBuilder.append(message.getDetail());
                msgBuilder.append("<br />");
            }
    // ...
    

    这里是HtmlMessagesRenderer#findInputLabel源代码的参考,看看它是如何工作的。

    【讨论】:

      【解决方案2】:

      我想到了一种解决方案。您可以添加新的PhaseListener,它将使用PhaseId = RENDER_RESPONSE 查找PhaseEvent,并在其中的 beforePhase 方法中编写如下内容:

      public class MyPhaseListener implements PhaseListener {
      
        private void findKeyValuePairInTree(UIComponent root, 
                                            Map<String, String> map) {
          if (root instanceof HtmlOutputLabel) {
            map.put(((HtmlOutputLabel) root).getFor(), 
                             ((HtmlOutputLabel) root).getValue());
          }
          if (root.getChilder() != null && !root.getChildren().isEmpty()) {
            for (UIComponent child : root.getChilder()) {
              findKeyValuePairInTree(child, map);
            }
          }
      
        private HashMap<String, String> idToLabelMap= new HashMap<String,String>();
      
        public void beforePhase(PhaseEvent event) {
          if (event.getPhaseId().equals(PhaseId.RENDER_RESPONSE) {
      
            findKeyValuePairInTree(FacesContext.getCurrentInstance().getViewRoot(),
                                   idToLabelMap);
            // after above function finished execution in idToLabelMap will be all 
            // pairs of component ID used in standard FacesMessage as key
            // and HtmlOutputLabel value attr used as value
           
            Set<String> keySet = idToLabelMap.keySet();
            for(String key : keySet) {
              Iterator messages = FacesContext.getCurrentInstance().getMessages(key);
              while (iterator.hasNext) {
                FacesMessage msg = (FacesMessage) iterator.next();
      
                // now replace all occurences of clientId with label value from map
      
                msg.setSummary(msg.getSummary().replaceAll(key, idToLabelMap.get(key)));
                msg.setDetails(msg.getDetails().replaceAll(key, idToLabelMap.get(key)));
              }
            }
            FacesContext.getCurrentInstance().getMessages();
          } 
        }
      
      ...
      
      }
      

      现在在此方法之后的渲染阶段,FacesContext.getCurrentInstance().getMessages() 中的所有消息都将更改为带有标签值的clientId,并且因为验证阶段在渲染阶段之前,所有验证都将通过或失败 - 不再有来自验证的消息.

      唯一的问题是当您在HtmlOutputLabel 实例的for 属性中使用相同的ID 两次或更多次时,因为在不同的形式中可以使用相同的ID 用于组件。然后你可以在 for attr 里面 &lt;h:outputLabel&gt; 使用更复杂的 id,例如用表单 id 或类似的东西来提示它。

      【讨论】:

      • 谢谢,但这个解决方案需要手动构建一个 idToLabel 地图并每次都对其进行维护。我在下面发布的解决方案不需要在组件的渲染器代码之外进行代码修改。
      • 您不必这样做。还有更复杂的方法,我只发布了简单的解决方案(不是最容易维护的)。最好的办法是在组件树中找到所有 HtmlOutputLabel 组件并动态构建此映射,因为这些组件属性 for 将用作映射中的键,而 attribute 值将用作映射中此键的值。然后我会用这个解决方案更新我的答案。
      • 是的,是的,它不需要在组件的渲染器之外修改代码,但是当一个新的标签组件添加到 JSF 页面时,它仍然需要更新映射表。我的解决方案(按照您在评论中描述“最佳”解决方案的方式构建)更可取,因为将 JSF 页面与组件完全分离,因此组件更易于重用。
      • 我同意 :) 我的想法是解决问题,而不是严格的解决方案,并且要关闭 JSF 规范,它应该是渲染器而不是 PhaseListener 的工作 - 这是正确的。
      【解决方案3】:
      1. 绑定你的输出标签。
      2. 绑定你的错误信息。
      3. 现在从 java 设置输出标签的值。
      4. 如果验证失败,请检查验证获取您的输出标签值并将其设置为您的错误消息变量。

      【讨论】:

      • 嗨,这是一个非常昂贵的解决方案,因为要在弹出窗口中显示验证消息,我应该更改每个组件。我正在寻找一种在我的 PopupRender 代码中查找组件标签的 Java 方法,例如 Tomahawk 消息标签。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-28
      • 2018-09-01
      相关资源
      最近更新 更多