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.
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.
Related
I am new to JavaBeans and I need a little help to keep my first little JSF-project going.
I am writing a little web application where a user can search with certain criteria for buildings. So the user enters in the search form 'location', 'property type', 'asking price', 'number of rooms' and 'living space'.
My managed bean accept the requiry with setter/getter and now the data is to be transmitted to a SQL class, where they are processed and matching search results are returned. It sounds simple, but I can not find a solution.
My managed bean looks like this now:
package beans
//import statements
...
#ManagedBean
#RequestScoped
public class PropertySearchBean {
private String _place
private String _propertyType
private double _askingPrice
private int _rooms
private double _livingSpace
public ArrayList<SearchResults> results = new ArrayList<SearchResults>();
// empty constructor
...
// getter and setter for these 5 user inputs
...
public void initializeSearchResults() {
// do the SQL query, recieve search results
// add it as a new object of 'SearchResults'
SQLPropertySearch search = new SQLPropertySearch(_place, _propertyType,
_askingPrice, _rooms, _livingSpace);
ArrayList<Integer> idResults = search.getPropertyIDlist();
SQLProperty property;
if(!idResults.isEmpty()) {
for(int i=0; i<idResults.size(); i++) {
property = new SQLProperty(idResults.get(i));
results.add(new SearchResults(
property.getPropertyID(),
property.getPropertyName(),
// and so on..
));
}
}
}
public static class SearchResults {
int propertyID;
String propertyName;
// and so on..
public SearchResults(int propertyID, String propertyName) {
this.propertyID = propertyID;
this.propertyName = propertyName;
// and so on..
}
// getter and setter
public int getPropertyID() {
return propertyID;
}
public void setPropertyID(int propertyID) {
this.propertyID = propertyID;
}
// and so on..
}
public ArrayList<SearchResults> getResults() {
return results;
}
}
In my XHTML-file I go through each entry of my ArrayList results.
It looks like this:
<ui:repeat var="res" value="#{PropertySearchBean.results}">
<p>#{res.propertyID}</p>
<p>#{res.propertyName}</p>
</ui:repeat>
I don't have an idea how to initialize the ArrayList, because first thing to do is the search itself, with the user input.
I am thankful for any kind of help!
You've removed the getters and setters from your example to improve readability. I'll provide one implementation here to ensure a common understanding (especially regarding the leading underscores).
public String getPlace() {
return _place;
}
public void setPlace(String place) {
this._place = place;
}
The property 'place' will be accessible within your view by using the value binding #{propertySearchBean.place}(see below).
Your code is meant to perform a search. Therefore you'll have to transfer user input from your XHTML file (view) to your managed bean. To do so you need to add a form to your view. Each search query parameter is bound to your bean using a specific value binding. Additionally the form contains a <h:commandButton> tag which finally triggers initialization of the result list.
<h:form>
<h:outputLabel for="place" value="Place:" />
<h:inputText id="place" value="#{propertySearchBean.place}" />
<!-- Additional fields -->
<h:commandButton action="#{propertySearchBean.initializeSearchResults}"
value="Search"/>
</h:form>
Note: You've used the following code in your example
<ui:repeat var="res" value="#{PropertySearchBean.results}">
Make sure that the first letter of your bean name is lower-case (propertySearchBean instead of PropertySearchBean). So this needs to be updated to
<ui:repeat var="res" value="#{propertySearchBean.results}">
As I struggled for hours I finally found where those annoying ClassCastExceptions came from, which I thought were produced by Hibernate and it's enum-mapping.
But they came from my JSF view, where I passed a List from
<h:selectManyCheckbox value="#{createUserManager.user.roles}" ... >
<f:selectItems value="#{createUserManager.roles}"/>
</h:selectManyCheckbox>
back into my backing bean.
My data simply consists of the values of an enum:
public Role[] getRoles()
{
return Role.values();
} .
I was really shocked when I tested the setter of roles in the User-class and got this:
public void setRoles(List<Role> paramRoles) {
System.out.println(paramRoles.get(0) instanceof Role); //output: false
for(Role role : paramRoles){ ...} //crashes with ClassCastException
}
Changing List<Role> paramRoles to List<String> paramRoles worked perfectly.
How is this possible? Shouldn't those generics be type safe or is type erasure in connection with JSF killing the whole type safety thing?
Also shouldn't the return value of h:selectManyCheckbox be List<Role>, like I passed in via the f:selectItems?
The behaviour you are experiencing is fully expected. Moreover, it is related to java generics in the same way as to how HTTP works.
The problem
The HTTP part
The problem is that you don't fully understand how HTTP works. When you submit data by pressing the submit button, parameters of your generated by JSF <h:selectManyCheckbox> tag, as a bunch of <input type="checkbox" name="..." value="userRoleAsString"> checkboxes, will be sent as strings and retrived ultimately as request.getParameter("checkboxName"); also as strings. Of course, JSF has no idea how to construct you model object class, Role.
The generics part
As you know due to the fact that java chose type erasure for generics to provide for backwards compatibility, information about generic types is basically a compile-type artifact and is lost at runtime. So at runtime your List<Role> erases to a plain, good old List. And as far as EL is a runtime language that uses Java Reflection API to deal with your expressions / call methods, at runtime no such information is available. Taking into account the HTTP part, JSF does its best and assigns string objects to your list, as it's all it can implicitly do. If you are willing to tell JSF to do otherwise, you need to do that explicitly, i.e. by specifying a converter to know what type of object to expect in an HTTP request.
The JSF part: aftermath
JSF has a provided javax.faces.Enum converter and in indeed works, if EL knew of the compile-time generic type of your list, that is Role. But it doesn't know of it. It would be not necessary to provide for a converter in case your multiple selection would be done on a Role[] userRoles object, or if you used the unique selection like in <h:selectOneMenu> with a value bound to Role userRole. In these examples the built-in enum converter will be called automatically.
So, to get it work as expected you need to provide for a Converter that will 'explain' JSF what type of values does this list hold, and how to do the transformations from Role to String, and vice versa.
It is worth noting that this will be the case with any bound List<...> values within the multiple choice JSF components.
Points of reference on Stack Overflow
After the problem was examined and resolved I was wondering if no one faced it in the past and searched for some previous answers here. Not surprisingly, it was asked before, and of course the problem was solved by BalusC. Below are two most valuable point of reference:
JSF 2.0 use enum in selectMany menu;
How to make a dropdown menu of a enum in JSF;
How to create and use a generic bean for enums in f:selectItems?.
The test case and two examples of working converters
Below I provide for a test case entirely for your understanding: everything works as expected apart from the third <h:selectManyCheckbox> component. It's up to you to trace it fully to eliminate the issue altogether.
The view:
<h:form>
Many with enum converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles}" converter="roleEnumConverter">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Many with plain converter
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles2}" converter="roleConverter">
<f:selectItems value="#{q16433250Bean.allRoles2}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter
<!-- will NOT be mapped correctly with Role object, but with a default String instead -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles3}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
Without any converter + array
<!-- will be mapped correctly with Role object -->
<h:selectManyCheckbox value="#{q16433250Bean.userRoles4}">
<f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
</h:selectManyCheckbox>
<br/>
<h:commandButton value="Submit" action="#{q16433250Bean.action}"/>
</h:form>
The bean:
#ManagedBean
#RequestScoped
public class Q16433250Bean {
private List<Role> userRoles = new ArrayList<Role>();//getter + setter
private List<Role> userRoles2 = new ArrayList<Role>();//getter + setter
private List<Role> userRoles3 = new ArrayList<Role>();//getter + setter
private Role[] userRoles4;//getter + setter
public enum Role {
ADMIN("Admin"),
SUPER_USER("Super user"),
USER("User");
private final String name;
private Role(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public Role[] getAllRoles() {
return Role.values();
}
public String action() {
return null;
}
}
The converters:
#FacesConverter("roleEnumConverter")
public class RoleEnumConverter extends EnumConverter {
public RoleEnumConverter() {
super(Role.class);
}
}
and
#FacesConverter("roleConverter")
public class RoleConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
Role role = Role.valueOf(value);
return role;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Role) || (value == null)) {
return null;
}
return ((Role)value).toString();
}
}
I defined the following drop down box in my page:
...
<td>
<h:selectOneMenu id="bootenvironment1" value="#{detailModel.selectedBootenvironment1}"
disabled="#{detailModel.mode == detailModel.viewMode}">
<f:selectItems value="#{detailModel.availableBootenvironments}"/>
</h:selectOneMenu>
</td>
In my model I have:
...
private Map<String, Bootenvironment> availableBootenvironments;
public DefinitionDetailModel()
{
super();
}
public String getSelectedBootenvironment1()
{
if (((Definition) getAfterObject()).getBootenvironment1() != null)
{
return ((Definition) getAfterObject()).getBootenvironment1().getEnvironmentName();
}
return "--Please select one--";
}
public void setSelectedBootenvironment1( String selectedBootenvironment )
{
((Definition) getAfterObject()).setBootenvironment1(availableBootenvironments.get(selectedBootenvironment));
}
...
And in the controller I set the availableBootenvironments map:
private void fetchBootenvironments()
{
...
#SuppressWarnings( "unchecked" )
List<Bootenvironment> bootenvironments = (List<Bootenvironment>) ...
Map<String, Bootenvironment> availableBootenvironments = new HashMap<String, Bootenvironment>();
availableBootenvironments.put("--Please select one--", null);
for(Bootenvironment bootenvironment : bootenvironments)
{
availableBootenvironments.put(bootenvironment.getEnvironmentName(), bootenvironment);
}
((DefinitionDetailModel) detailModel).setAvailableBootenvironments(availableBootenvironments);
}
The problem is that when I click a button in the page (which is bound to an action), I get the error:
detailForm:bootenvironment1: Validation error: value is not valid.
I don't understand where the error is; the value for selectItems is a map with the object's name-field(so a string) as key and the object itself as value. Then the value for the default selected (value="#{detailModel.selectedBootenvironment1}") is a string too as you can see in the getter/setter method of the model.
Another problem (maybe related to the previous one) is that when the page first loads, the default selected value should be --Please select one--- as the getBootenvironment1() returns null, but this does not happen: another one from the list is selected.
Can you please help me understanding what/where am I doing wrong?
EDIT
I implemented the Converter as you said:
#FacesConverter( forClass = Bootenvironment.class )
public class BootenvironmentConverter implements Converter
{
#Override
public String getAsString( FacesContext context, UIComponent component, Object modelValue ) throws ConverterException
{
return String.valueOf(((Bootenvironment) modelValue).getDbId());
}
#Override
public Object getAsObject( FacesContext context, UIComponent component, String submittedValue ) throws ConverterException
{
List<Bootenvironment> bootenvironments = ... (get from DB where dbid=submittedValue)
return bootenvironments.get(0);
}
}
But now I have the following error:
java.lang.ClassCastException: java.lang.String cannot be cast to
ch.ethz.id.wai.bootrobot.bo.Bootenvironment
You will get this error when the selected item value doesn't pass the equals() test on any of the available item values.
And indeed, you've there a list of Bootenvironment item values, but you've bound the property to a String which indicates that you're relying on the Bootenvironment#toString() value being passed as submitted value and that you aren't using a normal JSF Converter at all. A String can never return true on an equals() test with an Bootenvironment object.
You'd need to provide a Converter which converts between Bootenvironment and its unique String representation. Usually, the technical ID (such as the autogenerated PK from the database) is been used as the unique String representation.
#FacesConverter(forClass=Bootenvironment.class)
public class BootenvironmentConverter implements Converter {
#Override
public void getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
// Write code to convert Bootenvironment to its unique String representation. E.g.
return String.valueOf(((Bootenvironment) modelValue).getId());
}
#Override
public void getAsObject(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
// Write code to convert unique String representation of Bootenvironment to concrete Bootenvironment. E.g.
return someBootenvironmentService.find(Long.valueOf(submittedValue));
}
}
Finally, after implementing the converter accordingly, you should be able to fix the selectedBootenvironment1 property to be a normal property without any mess in getter/setter:
private Bootenvironment selectedBootenvironment1;
public Bootenvironment getSelectedBootenvironment1() {
return selectedBootenvironment1;
}
public void setSelectedBootenvironment1(Bootenvironment selectedBootenvironment1) {
this.selectedBootenvironment1 = selectedBootenvironment1;
}
I have a weird problem with a custom #FacesConverter with JBoss-7.1.0.CR1b during an AJAX call in p:selectOneMenu (Primefaces 3.0).
The simplified Converter looks like this, there are no NPE or other exceptions in this class
#FacesConverter("MyConverter")
public class MyConverter implements Converter
{
public Object getAsObject(FacesContext fc, UIComponent uic, String value)
{
logger.debug("getAsObject value: "+value);
if (submittedValue.trim().equals("")) {return null;}
else
{
MyEjb ejb = new MyEjb();
ejb.setId(Long.parseLong(value()));
return ejb; //**** alternative with return null; ****
}
}
public String getAsString(FacesContext fc, UIComponent uic, Object value)
{
if (value == null || value.equals("")) {return "";}
else
{
MyEjb ejb = (MyEjb)value;
return ""+ejb.getId();
}
}
}
The converter is used in a p:selectOneMenu:
<h:form>
<p:selectOneMenu value="#{clientBean.selected}" converter="MyConverter">
<f:selectItems value="#{clientBean.all}" var="my"
itemLabel="#{my.name}" itemValue="#{my}"/>
<p:ajax listener="#{clientBean.changed}" />
</p:selectOneMenu>
</h:form>
That's not rocket engineering, the changed method simply makes a debug:
public void changed()
{
logger.info("changed() "+selected);
}
But now the annoying part: The changed() is never called with the code like above, but I get the converter invoked three times:
12:37:51,500 DEBUG getAsObject value: 35
12:37:51,502 DEBUG getAsObject value:
12:37:51,503 DEBUG getAsObject value:
If I change the p:selectOneMenu value="#{clientBean.selectedId}" to a long selectedId and don't use the Converter the method is called once. Even if I return null in getAsObject()the changed() is called (once). I don't assume it's Primefaces related, because I have the same behavior if I use h:selectOneMenu and f:ajax.
You should have a <p:messages />, <p:growl /> or <h:messages /> which is been ajax-updated in your view. You should also pay attention to warnings in server logs about possible missing faces messages. The chance is big that you're seeing the infamous Validation error: Value not valid validation error.
After conversion, JSF will validate if the submitted object is one of the available items as part of safeguard against tampered/hacked requests. JSF will do that by submittedObject.equals(oneOfAvailableObjects) for each of the available objects as you have there in <f:selectItems>. If nothing matches, then JSF will display this validation error.
In your particular case, the MyEjb class has apparently no equals() method or its implementation is broken. See also Right way to implement equals contract.
I do not understand the behaviour of JSF2 during valdation. Hope someone can help me.
I have a form where the fields are validated after (ajax) submit - ok
If the validation failed a error message is shown - ok
For my example when I enter a valid birthday and the field name is empty an errormessage for name is shown after submit.
Now when I enter a valid name and delete the input from the birthday field an errormessage is show for birthday (that's ok) but now the old 'valid' birthday stands also in the input field!?!
How can I avoid this behaviour?
When I submit an empty field I want to see an errormessage and an empty field...
Here's my sample code:
I use a ManagedBean (TestBean) that contains an EntityBean (Contact). The Contact contains validations per annoations.
public class Contact implements Serializable {
#NotNull
#Temporal(TemporalType.DATE)
private Date birthday;
#NotNull
#Size(min=3, max=15)
private String name;
//...
}
My ManagedBean:
#ManagedBean
#ViewScoped
public class TestBean implements Serializable {
private Contact contact;
#PostConstruct
void init() {
System.out.println("init...");
contact = new Contact();
}
public void newContact(ActionEvent ae) {
System.out.println("newContact...");
contact = new Contact();
}
public void save() {
System.out.println("save...");
//TODO do something with contact...
}
public Contact getContact() { return contact; }
public void setContact(Contact contact) {this.contact = contact;}
}
An here my JSF page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core" >
<h:body>
<h:form>
<h:panelGrid columns="3">
<h:outputText value="Birthday: " />
<h:inputText id="birthday" value="#{testBean.contact.birthday}">
<f:convertDateTime/>
</h:inputText>
<h:message for="birthday" />
<h:outputText value="Name: " />
<h:inputText id="name" value="#{testBean.contact.name}"/>
<h:message for="name" />
</h:panelGrid>
<h:commandButton value="submit" action="#{testBean.save}">
<f:ajax execute="#form" render="#form"/>
</h:commandButton>
<h:commandButton value="newContact" actionListener="#{testBean.newContact}"
immediate="true">
<f:ajax render="#form"/>
</h:commandButton>
</h:form>
</h:body>
</html>
at last a snippet from web.xml
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
<param-value>true</param-value>
</context-param>
Thanks for some tips
Your particular problem is caused by
<context-param>
<param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
and a bug (at least, an oversight) in HtmlBasicRenderer#getCurrentValue() of Mojarra:
if (component instanceof UIInput) {
Object submittedValue = ((UIInput) component).getSubmittedValue();
if (submittedValue != null) {
// value may not be a String...
return submittedValue.toString();
}
}
String currentValue = null;
Object currentObj = getValue(component);
if (currentObj != null) {
currentValue = getFormattedValue(context, component, currentObj);
}
return currentValue;
Normally, the submitted value is set to null when the UIInput component is successfully converted and validated. When JSF is about to redisplay the value, it first checks if the submitted value is not null before proceeding to redisplay the model value. However, with this context parameter, it is null instead of an empty string when it is invalid and thus it will always redisplay the original model value when you remove the initial value of a required field.
To test it, set that context param value to false or remove it altogether. You'll see that it works as intended. However, it will bring back the disadvantage that your model values will be cluttered with empty strings on empty but non-required fields and you'll lose the advantage of using #NotNull annotation of JSR 303 bean validation.
To fix this, you've to alter the first part of HtmlBasicRenderer#getCurrentValue() as follows:
if (component instanceof UIInput && !((UIInput) component).isValid()) {
Object submittedValue = ((UIInput) component).getSubmittedValue();
if (submittedValue != null) {
// value may not be a String...
return submittedValue.toString();
} else {
return null;
}
}
I've already reported it to Mojarra guys as issue 2266.
I'm thinking that during normal use people won't enter a valid date, submit, and then delete the date before submitting again. I realize that you found this during testing, but probably people are just trying to successfully fill out the form and not deleting stuff they already entered, in which case perhaps keeping the last valid value is the best functionality.
If you insist... It seems like the setter method "birthday" is never called because the value is invalid, and then when the page is redisplayed the current value of "birthday" is displayed (the current value being the valid value that was previously saved). Maybe you could write a custom validator that sets the value and THEN validates the value, but this wouldn't really make much sense. You would still have to validate the value first for cases when the users enters a text string like "yesterday" instead of a valid date, and then you would have to set the date to something based on that invalid value, and then you would have to add the message to the FacesContext. So the code would have to do something like the following.
1) validate the date value
2) if it's invalid then set the field to some value which makes sense and add an error message to FacesContext.
3) if it's valid then use it.
That's do-able, but weird because you're changing the value of the field even though the passed in value is invalid...
Aaron already descripes the behaviour.
The problem I've been described exists also by clicking the 'newContact' button. If the first submit is not valid (birthday was entered, name-field is empty) an error message is shown. ok.
Afterwards the 'newContact' Button do not refresh (clear) the view. Although the model was resetted (contact = new Contact()).
I found some tipps here: http://wiki.apache.org/myfaces/ClearInputComponents
Here is my solution:
public void newContact(ActionEvent ae) {
contact = new Contact();
contact.setBirthday(new Date()); //for testing only
resetForm(ae.getComponent());
}
private void resetForm(UIComponent uiComponent) {
//get form component
UIComponent parentComponent = uiComponent.getParent();
if (uiComponent instanceof UIForm)
resetFields(uiComponent);
else if (parentComponent != null)
resetForm(parentComponent);
else
resetFields(uiComponent);
}
private void resetFields(UIComponent baseComponent) {
for (UIComponent c : baseComponent.getChildren()) {
if (c.getChildCount() > 0)
resetFields(c);
if (c instanceof UIInput)
((UIInput) c).resetValue();
}
}
Had a similar issue where a value loaded in from the backing bean would get reset when the field was blanked out and another component failed validation. I had to make a slight addition to BalusC's code to make it work.
protected String getCurrentValue(FacesContext context,
UIComponent component) {
if (component instanceof UIInput && !((UIInput) component).isValid()) {
Object submittedValue = ((UIInput) component).getSubmittedValue();
if (submittedValue != null) {
// value may not be a String...
return submittedValue.toString();
} else {
return null;
}
}
String currentValue = null;
Object currentObj;
if ( component instanceof UIInput && ((UIInput)component).isLocalValueSet() )
{
currentObj = ((UIInput)component).getLocalValue();
}
else {
currentObj = getValue(component);
}
if (currentObj != null) {
currentValue = getFormattedValue(context, component, currentObj);
}
return currentValue;
}