Search code examples
javavalidationjsf-2primefacesrequiredfieldvalidator

Why does Input is holding value after required field validation


When a page is loaded with an input which is bound to a value in the view scopped backing bean and if the value is is pre-initialized, then after required field validation the input is again populated with the previous value.

Here is the managed bean:

@ManagedBean(name = "adminController")
@ViewScoped
public class AdminController extends BaseWebController implements Serializable {

    private static final long serialVersionUID = 1019716974557397635L;

    private transient CustomerDTO customerDTO;          

    public AdminController() {
        log.debug("Inside AdminController");
    }

    @PostConstruct
    public void init() {
        initCustomerDTO();
        customerDTO.setCustomerName("Test");
    }

    public void saveCustomer(ActionEvent event) {
        try {
            getServiceProvider().getCustomerService().addNewCustomer(customerDTO);
            getFacesContext().addMessage(null, FacesMessageUtils.getMessageForCustomerSaveSuccess(getFacesContext()));
        } catch (Throwable throwable) {
            getFacesContext().addMessage(null, FacesMessageUtils.getMessageForCustomerSaveError(getFacesContext()));
            printStackTrace(throwable);
        }       

        initCustomerDTO();              
    }   

    private void initCustomerDTO() {
        customerDTO = new CustomerDTO();
    }

    public CustomerDTO getCustomerDTO() {
        return customerDTO;
    }

    public void setCustomerDTO(CustomerDTO customerDTO) {
        this.customerDTO = customerDTO;
    }
}

CustomerDTO is a POJO.

The jsf contains:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui"
    xmlns:pe="http://primefaces.org/ui/extensions">

    <h:form id="customerForm">

        <p:inputText id="customerName" styleClass="customerName"
            autocomplete="off"
            label="#{adbBundle['admin.addCustomerPanel.addCustomerTable.customerName']}"
            value="#{adminController.customerDTO.customerName}"
            required="true" />


        <p:commandButton value="#{adbBundle['saveButton']}"
            actionListener="#{adminController.saveCustomer}"
            icon="ui-icon-check"
            update=":growl, @form, :adminTabView:customerListForm:customerListPanel" />     

    </h:form>

</ui:composition>

I also have a custom converter class for trimming blank String.

@FacesConverter(forClass = String.class)
public class StringTrimmer implements Converter {

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (StringUtils.isBlank(value)) {
            if (component instanceof EditableValueHolder) {
                ((EditableValueHolder) component).setSubmittedValue(null);
            }
            return null;
        }
        return value;
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return value.toString();
    }
}

So when the page is first loaded the input field contains "Test". I cleaned out the test field and click on the save button. I received error message for required field validation, but the input is again get populated with the default value "Test" that I have set before.

How can I solve the issue?

Update:

I am running in JBoss AS7 with Mojarra 2.1.7, which is JBoss'es implementation and Primefaces 3.4.2.


Solution

  • Your problem is caused by the converter.

    When JSF validation fails for a specific component, the submitted value is kept in the component, else it is nulled out and set as local value. When JSF is finished with validation of all components and no one has failed validation, then all local values are nulled out and set as model value (the one referenced as value attribute). When JSF needs to redisplay the form with values, it first checks if the submitted value is not null and then display it, else it checks if the local value is not null and then display it, else it displays the model value outright.

    In your case, you're converting the empty string submitted value to null regardless of validation outcome. So the submitted value is always null and thus JSF will always redisplay the model value instead of the empty string submitted value.

    I believe that you rather need the following context parameter instead of the converter, certainly if the sole goal is to prevent the model values being polluted with empty strings when empty inputs are submitted.

    <context-param>
        <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
        <param-value>true</param-value>
    </context-param>
    

    Or if your sole goal is really to trim whitespace from string, as the converter class name suggests, then you should not be touching the submitted value at all. You can find a concrete example here: Why does <h:inputText required="true"> allow blank spaces? The getAsObject() won't be set as submitted value, but instead as local value (on validation success) and ultimately as model value.