1
votes

I'm writing a custom component to show validation messages in popups, it works well but I have still a problem: I would like to display component labels instead of cliendIds in validation messages.

I already looked at this question and similars:

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

but I'm using jsf 1.1 and I get a compilation error if I try to set a value for attribute label or requiredMessage. So I tried assigning a label to the component using an <outputLabel />:

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

with no effect.

Is there a simple way in jsf 1.1 to make label appear in validation messages instead of client id? If not, how could I, given a component, retrieve its related label component to do the work in Java code?

Thanks.

3

3 Answers

2
votes

I found a solution which does not require to modify existing JSF code.

  1. make my component renderer extend Tomahawk's HtmlMessagesRenderer, which is capable of use a component label instead of its id
  2. use HtmlMessagesRenderer#findInputId to retrieve the Id of the component with messages
  3. use HtmlMessagesRenderer#findInputLabel to retrieve the Label of the component with messages
  4. replace component Id with component Label in the message.

Below is an excerpt from the code of my component Render encodeBegin method, in which I made two separated loops, the first for component messages the other for global messages.

Note that FacesContext#getClientIdsWithMessages returns also null, which is considered the clientId for global messages.

Note that, because the component itself manages to retrieve and use the component label, if exists; this solution it only need to place a <myCustomMessages /> tag in JSF page code.

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 />");
        }
// ...

Here is a reference to HtmlMessagesRenderer#findInputLabel source code, to take a look on how it works.

1
votes

I think of one solution. You can add new PhaseListener which will look for PhaseEvent with PhaseId = RENDER_RESPONSE and inside it's beforePhase method write something like this:

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();
    } 
  }

...

}

Now in Render Phase which comes after this method all messages in FacesContext.getCurrentInstance().getMessages() will have changed clientId with label value and because Validation Phase is before Render Phase all validation will be passed or failed - no more messages will come from validation.

The only problem is when you use in for attribute of HtmlOutputLabel instance the same id twice or more because in different forms can be same ids used for components. Then You can in for attr inside <h:outputLabel> use more sophisticated id, for example clue it with form id or something like this.


I hope this will help.

0
votes
  1. bind your output label .
  2. bind your error message.
  3. now set value of your output label from java.
  4. check validation if validation failed get your output label value and set it to in your error message variable.