1 Reply Latest reply on Feb 17, 2008 2:19 PM by pmuir

    Proposed solution for lost faces messages after navigation r

      Hi all,

      I found that org.jboss.seam.core.FacesMessages.addToControl() does not work with redirects, if the target control is not contained in the current page.

      Example:


      • user enters input data into page1 and hits submit
      • validation finds that some input field on page2 is missing to complete the workflow, adds a message with FacesMessage.addToControl,
      • the action handler returns the target view id
      • and a navigation rule in pages.xml redirects to page2


        JSF doesn't seem to provide an appropriate hook to work around the issue. A PhaseListener didn't work, because the target component tree is only fully constructed during rendering. Querying the FacesContext viewRoot in before[RENDER_RESPONSE] returns a non-null value, however the component tree is not yet constructed at this stage (childrenList is null).

        Note that the described situation only applies to first-renders of a page. During a post-back, the tree would be available in before[RENDER_RESPONSE].

        Because of the not-yet-constructed component tree, getClientId() in FacesMessages.addToControl(String, FacesMessage) returns null, and the message is lost.

        The only solution that worked for me was saving unresolved client ids in FacesMessages.addToControl(String, FacesMessage), and implementing a custom ViewHandler that restored the faces messages for unresolved client ids. I extended org.jboss.seam.ui.facelet.SeamFaceletViewHandler, and restored unresolved client ids in getActionURL() - at this stage, the component tree is fully constructed.

        Relevant parts of modified org.jboss.seam.ui.facelet.SeamFaceletViewHandler:
        public class SeamFaceletViewHandler extends FaceletViewHandler {
         ...
        
         @Override
         public String getActionURL(FacesContext context, String viewId) {
         if (Contexts.isConversationContextActive()) {
         FacesMessages.instance().restoreUnresolvedMessages();
         }
         return super.getActionURL(context, viewId);
         }
        }
        


        Relevant parts of modified org.jboss.seam.core.FacesMessages:
        @Scope(ScopeType.CONVERSATION)
        @Name("org.jboss.seam.core.facesMessages")
        @Install(precedence = BUILT_IN)
        @Intercept(NEVER)
        public class FacesMessages implements Serializable {
        
         ...
         private Map<String, List<Message>> unresolvedFacesMessages = new HashMap<String, List<Message>>();
        
         ...
        
         @SuppressWarnings("unchecked")
         public void restoreUnresolvedMessages() {
         if (unresolvedFacesMessages == null || unresolvedFacesMessages.isEmpty())
         return;
         for (Map.Entry<String, List<Message>> entry : unresolvedFacesMessages.entrySet()) {
         for (Message msg : entry.getValue()) {
         String clientId = getClientId(entry.getKey());
         FacesContext.getCurrentInstance().addMessage(clientId, msg.toFacesMessage());
         }
         }
         unresolvedFacesMessages.clear();
         }
        
         public void addToControl(String id, FacesMessage facesMessage) {
         if (facesMessage != null) {
         String clientId = getClientId(id);
         List<Message> list = clientId != null ? getKeyedMessages(clientId)
         : getUnresolvedMessages(id);
         list.add(new Message(facesMessage));
         }
         }
        
         private List<Message> getKeyedMessages(String clientId) {
         List<Message> list = keyedFacesMessages.get(clientId);
         if (list == null) {
         list = new ArrayList<Message>();
         keyedFacesMessages.put(clientId, list);
         }
         return list;
         }
        
         private List<Message> getUnresolvedMessages(String clientId) {
         List<Message> list = unresolvedFacesMessages.get(clientId);
         if (list == null) {
         list = new ArrayList<Message>();
         unresolvedFacesMessages.put(clientId, list);
         }
         return list;
         }
        
         ...
        }
        


        Does this seem like a plausible solution? The code seems to work, and validation messages get displayed correctly after redirects, which I think is quite nice from a usability point of view.

        regards,
        Karl