Search code examples
validationjsfjsf-2conditional-rendering

Render a component only when validation success


In JSF 2.X, can I render a component only when the validation success?

In my application I have many fields that must be filled. These data can be imported from a WebService through a search key.

When the user enter a valid search key the system searches the other fields and render them with the new values. But when the user enter a nonexistent key (or any other validation error) the server generates a validation error but still renders the fields, thus losing any data that there were filled.

What I need is that the user can perform the query and that if the query does not return results, this does not affect any data that he has already entered.

Below is a code example. Thus, if the user has filled in the fields inside updateThisOnSuccess and just after making an attempt to query without success, the value that is filled in is not lost.

    <h:inputText value="#{controller.searchWebService}" >
        <f:ajax execute="@this" render="updateThisOnSuccess messages" />
    </h:inputText>

    <h:panelGroup id="updateThisOnSuccess">
        <h:inputText value="#{controller.field}" />
        <!-- other fields -->
    </h:panelGroup>

Submit the field values to run the search also does not seem an option as this will cause need to validate the fields inside updateThisOnSuccess.


Note: I saw the answer given by @BalusC to a similar question, but this is different from what I'm wondering why, in that case, foo-holder is always rendered and foo is conditioning. It's not my case, since this approach would make the controls do not appear when the validation fails.


Solution

  • You can change the components that will be processed in render phase changing the Collection at getRenderIds() of PartialViewContext. According to documentation this Collection is mutable.

    FacesContext.getCurrentInstance().getPartialViewContext().getRenderIds().remove("formName:updateThisOnSuccess");
    

    To test this solution, I used this controller:

    @Named
    @ViewScoped
    public class Controller implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private final static List<String> LIST_VALID_WEB_SERVICE_SEARCHS =
            Arrays.asList(new String[] {"foo", "bar"});
    
        private String webServiceParameter;
        private Integer field01;
    
        public void searchWebService() {
    
            if (LIST_VALID_WEB_SERVICE_SEARCHS.contains(getWebServiceParameter())) {
                setField01(123);
            } else {
    
                FacesContext facesContext = FacesContext.getCurrentInstance();
    
                facesContext.getPartialViewContext().getRenderIds().remove("formFields");
    
                FacesMessage facesMessage = new FacesMessage("Search not found in WebService.");
                facesMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
                facesContext.addMessage("formName:searchWebService", facesMessage);
            }
        }
    
        public void submit() {
            System.out.println("submitted");
        }
    
        // Getters and Setters
    }
    

    And used this view:

    <h:form id="formSearch">
        <h:inputText id="webServiceParameter" value="#{controller.webServiceParameter}">
            <f:ajax execute="@this" render="formFields messages" listener="#{controller.searchWebService}" />
        </h:inputText><br />
    </h:form>
    
    <h:form id="formFields">
        <h:inputText id="field01" value="#{controller.field01}" required="true">
            <f:validateLongRange minimum="2" maximum="345" />
        </h:inputText><br />
        <!-- other fields -->
    
        <h:commandButton value="submit" action="#{controller.submit}">
            <f:ajax render="@form messages" execute="@form" />
        </h:commandButton>
    </h:form>
    
    <h:messages id="messages" />