I'm working with a web application with the following technologies
spring 4.3
JSF 2.2.14
PrimeFaces 6.1
Omnifaces 2.6.4
I need to validate an h:inputText, and I'm trying to use the javax.faces.validator.Validator interface.
All is working well but when the validation fail, I'm not able to retrieve the label of the field, that is stored in a p:outputLabel using the "for" attribute.
Facelets code
<p:outputLabel id="selectedAdvPriceOutputLabel" for="selectedAdvPrice" value="#{msg['prezzo']}"/>
<h:inputText id="selectedAdvPrice"
value="#{addAdvertisingController.advertisingBean.price}"
class="form-control"
converter="javax.faces.BigDecimal"
required="#{empty param[draftSave.clientId] and empty param[draftSaveXS.clientId]}"
requiredMessage="#{msg['prezzo']} #{msg['campoObbligatorio']}">
<f:validator binding="#{numberGreaterThanZeroJsfValidator}" />
</h:inputText>
Validator - validate method
public void validate(FacesContext context, UIComponent component, Object value) {
if (value != null) {
String v = value.toString();
if (StringUtils.isNotEmpty(v)) {
try {
BigDecimal bd = new BigDecimal(v);
if(bd.compareTo(BigDecimal.ZERO) <= 0){
// how to retrieve the field label???
FacesMessage msg = new FacesMessage("messageWithout label", "messageWithout label");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg, new IllegalArgumentException(exceptionMessage));
}
} catch (NumberFormatException e) {
FacesMessage msg = new FacesMessage("messageWithout label", "messageWithout label");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg, new IllegalArgumentException(exceptionMessage));
}
}
}
}
How can I retrieve the value attribute of the p:outputLabel linked to the h:inputText that is not passing the validation?
Thank you
According to Primefaces User's Guide --> 3.93 OutputLabel
Auto Label
OutputLabel sets its value as the label of the target component to be displayed in validation errors so the target component
does not need to define the label attribute again.
<h:outputLabel for="input" value="Field" />
<p:inputText id="input" value="#{bean.text}" label="Field"/>
can be rewritten as;
<p:outputLabel for="input" value="Field" />
<p:inputText id="input" value="#{bean.text}" />
It means, that OutputLabel simply sets the label attributte of a component to which the label is attached.
Just retrieve this attributte in the validator, for example in this way:
public void validate(FacesContext context, UIComponent component, Object value) {
Object labelObj = component.getAttributes().get("label");
String label = (labelObj!=null) ? labelObj.toString() : "Unknown label";
.....
.....
// how to retrieve the field label???
String message = String.format("%s : My conversion error message", label);
FacesMessage msg = new FacesMessage(message,message) ;
.....
.....
I've tested it and it works for both p:inputText and h:inputText components.
Hi, I've debugged the code and component.getAttributes().get("label")
is null
I've tested it again on JSF 2.2/Primefaces 6.1/Wildfy 10.x and it works.
Here is a simple demo project on GitHub link
index.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form>
<p:panel id="panel" header="Form" style="margin-bottom:10px;">
<p:messages id="messages" />
<h:panelGrid columns="2" cellpadding="5">
<p:outputLabel id="label1_id"
for="input1_id" value="This Is My label for h:input" />
<h:inputText id="input1_id" value="#{myBean.price1}"
required="true" >
<f:validator validatorId="MyValidator" />
</h:inputText>
<p:outputLabel id="label2_id"
for="input2_id" value="This Is My label for p:input" />
<p:inputText id="input2_id" value="#{myBean.price2}" required="true">
<f:validator validatorId="MyValidator" />
</p:inputText>
</h:panelGrid>
<p:commandButton update="panel" value="Submit" />
</p:panel>
</h:form>
</h:body>
</html>
bean
#Named
#SessionScoped
public class MyBean implements Serializable {
private static final long serialVersionUID = 5455916691447931918L;
private Integer price1;
private Integer price2;
public Integer getPrice2() {
return price2;
}
public void setPrice2(Integer price2) {
this.price2 = price2;
}
public Integer getPrice1() {
return price1;
}
public void setPrice1(Integer price1) {
this.price1 = price1;
}
}
Validator
#FacesValidator("MyValidator")
public class MyValidator implements Validator {
public void validate(FacesContext context, UIComponent component, Object value) {
Object labelObj = component.getAttributes().get("label");
String label = (labelObj!=null) ? labelObj.toString() : "Unknown label";
if (value != null) {
String v = value.toString();
if (null != v && !v.isEmpty()) {
try {
BigDecimal bd = new BigDecimal(v);
if (bd.compareTo(BigDecimal.ZERO) <= 0) {
// how to retrieve the field label???
String message = String.format("%s : Value must be greater than 0", label);
FacesMessage msg = new FacesMessage(message,message) ;
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg, new IllegalArgumentException("Validator exception:" + message));
}
} catch (NumberFormatException e) {
String message = String.format("%s : Value must be a number", label);
FacesMessage msg = new FacesMessage(message,message);
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg, new IllegalArgumentException("Validator exception:" + message));
}
}
}
}
}
And a result is:
Some things that may help or are common:
remove id from *:outputLabel as this is not needed
use validatorMessage="#{bundle.SOME_FIELD_VALIDATION_ERROR}" to have a localizable message, if your validator is throwing javax.faces.validator.ValidatorException (you can import this above your validator class, of course)
get rid of the code for the label-retrieval, see above validatorMessage field
<f:validator binding="#{someValidator}" /> seem to cause more problems, common way is: <f:validator validatorId="SomeFooValidator" /> within *:inputText (you have to make it non-self-closing)
and then annotate your validator class with #FacesValidator ("SomeFooValidator") (javax.faces.validator.FacesValidator)
Are you using JSF2.2?
Related
I have a button in a form and I want an error message to be displayed when I click on this button. It is a step I want to complete before coding something similar. My code does not work because the error message is not displayed when I click on the button.
My form is:
<h:form id="myForm">
<p:messages id="errorMessage" for="myForm" autoUpdate="true" />
<p:fieldset id="myFieldSet"
style="margin-left:auto ; margin-right:auto ; width:98% ; height:90px;">
<p:outputLabel for="numSi"
value="Value :"
style="margin-left:31px;margin-top:25px;" />
<p:inputText id="numSi"
value="#{suppSiBean.researchValue}"
maxlength="9" required="true">
</p:inputText>
<p:commandButton id="suppSignaButton" type="submit" ajax="true"
value="Launch"
style="text-align: center ; height:45px; width:150px ; margin-top:20px;"
action="#{suppSiBean.lancerRequete()}" />
</p:fieldset>
</h:form>
Excerpt of my bean :
public void lancerRequete() {}
FacesContext context = FacesContext.getCurrentInstance();
//I'm intentionally hide the values of the three following variables because it's confidential
ResourceBundle bundle = ...
String message = ...
String messageFormat = ...
FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, messageFormat.toString(), "");
FacesContext.getCurrentInstance().addMessage(getClientId("myForm"), facesMessage);
}
public String getClientId(String id) {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot root = context.getViewRoot();
UIComponent c = findComponent(root, id);
LOGGER.info("c.getClientId(context) vaut :" + c.getClientId(context));
return c.getClientId(context);
}
private UIComponent findComponent(UIComponent c, String id) {
if (id.equals(c.getId())) {
return c;
}
Iterator<UIComponent> kids = c.getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent found = findComponent(kids.next(), id);
if (found != null) {
return found;
}
}
return null;
}
Why not targeting the ID of the component directly in the addMessage method ?
public void lancerRequete() {}
...
FacesContext.getCurrentInstance().addMessage("errorMessage", facesMessage);
}
Finally, you should tell when to update this component by adding update attribute :
<p:commandButton ... update="errorMessage" />
I have the following code in my facelet page:
<hc:rangeChooser1 id="range_chooser"
from="#{testBean.from}"
to="#{testBean.to}"
listener="#{testBean.update}"
text="#{testBean.text}">
<f:ajax event="rangeSelected"
execute="#this"
listener="#{testBean.update}"
render=":form:growl range_chooser"/>
</hc:rangeChooser1>
This is my composite component:
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui">
<cc:interface componentType="rangeChooser">
<!-- Define component attributes here -->
<cc:clientBehavior name="rangeSelected" event="change" targets="hiddenValue"/>
<cc:attribute name="from" type="java.util.Calendar"/>
<cc:attribute name="to" type="java.util.Calendar"/>
<cc:attribute name="text" type="java.lang.String"/>
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
...
<p:inputText id="hiddenValue" value="#{cc.attrs.text}"/>
...
</div>
</cc:implementation>
</ui:component>
How do I pass attributes from, to and text from composite component to backing bean? I mean inject these values in backing component, and not through
<p:inputText id="hiddenValue" value="#{cc.attrs.text}"/>
Update: there's more correct definition what do I need: Be able to mutate objects which I pass from the backing bean to the composite component inside a backing component of the composite component. So when I perform process or execute my composite component I get the updated values.
This is my backing component:
#FacesComponent("rangeChooser")
public class RangeChooser extends UIInput implements NamingContainer {
private String text;
private Calendar from;
private Calendar to;
#Override
public void encodeBegin(FacesContext context) throws IOException{
super.encodeBegin(context);
}
public String getText() {
String text = (String)getStateHelper().get(PropertyKeys.text);
return text;
}
public void setText(String text) {
getStateHelper().put(PropertyKeys.text, text);
}
/*
same getters and setters for Calendar objects, from and to
*/
}
I just can't realize how do I move on? In general I need to take a value from <p:inputText id="hiddenValue" value="#{cc.attrs.text}"/> and convert it to two Calendars object from and to.
It will be great if somebody can point me toward right direction from here. I know that I need to use getAttributes().put(key,value) but don't know where to put this code. Thank you in advance.
Based on your comments this is what you expect.
Note that even if this implementation works, it is conceptually incorrect!
You are considering from and to as INPUTS (not input components, but input values) and text as OUTPUT. This is not the way JSF is intended to work!
However, here it is
<cc:interface componentType="rangeComponent">
<cc:attribute name="from" />
<cc:attribute name="to" />
<cc:clientBehavior name="rangeSelected" event="dateSelect" targets="from to"/>
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<p:calendar id="from" value="#{cc.attrs.from}"/>
<p:calendar id="to" value="#{cc.attrs.to}"/>
</div>
</cc:implementation>
used in page:
<h:form>
<e:inputRange from="#{rangeBean.from}" to="#{rangeBean.to}" text="#{rangeBean.text}">
<p:ajax event="rangeSelected" process="#namingcontainer" update="#form:output" listener="#{rangeBean.onChange}" />
</e:inputRange>
<h:panelGrid id="output" columns="1">
<h:outputText value="#{rangeBean.from}"/>
<h:outputText value="#{rangeBean.to}"/>
<h:outputText value="#{rangeBean.text}"/>
</h:panelGrid>
</h:form>
with this backing component:
#FacesComponent("rangeComponent")
public class RangeComponent extends UINamingContainer
{
#Override
public void processUpdates(FacesContext context)
{
Objects.requireNonNull(context);
if(!isRendered())
{
return;
}
super.processUpdates(context);
try
{
Date from = (Date) getValueExpression("from").getValue(context.getELContext());
Date to = (Date) getValueExpression("to").getValue(context.getELContext());
ValueExpression ve = getValueExpression("text");
if(ve != null)
{
ve.setValue(context.getELContext(), from + " - " + to);
}
}
catch(RuntimeException e)
{
context.renderResponse();
throw e;
}
}
}
with this backing bean:
#ManagedBean
#ViewScoped
public class RangeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private Date from = new Date(1000000000);
private Date to = new Date(2000000000);
private String text = "range not set";
public void onChange(SelectEvent event)
{
Messages.addGlobalInfo("[{0}] changed: [{1}]", event.getComponent().getId(), event.getObject());
}
// getters/setters
}
I rewrote the code using BalusC tecnique (and without PrimeFaces):
the form:
<h:form>
<e:inputRange value="#{rangeBean.range}">
<p:ajax event="change" process="#namingcontainer" update="#form:output"
listener="#{rangeBean.onChange}" />
</e:inputRange>
<h:panelGrid id="output" columns="1">
<h:outputText value="#{rangeBean.range}" />
</h:panelGrid>
</h:form>
the composite:
<cc:interface componentType="rangeComponent">
<cc:attribute name="value" />
<cc:clientBehavior name="change" event="change" targets="from to"/>
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<h:inputText id="from" binding="#{cc.from}">
<f:convertDateTime type="date" pattern="dd/MM/yyyy" />
</h:inputText>
<h:inputText id="to" binding="#{cc.to}">
<f:convertDateTime type="date" pattern="dd/MM/yyyy" />
</h:inputText>
</div>
</cc:implementation>
the backing component:
#FacesComponent("rangeComponent")
public class RangeComponent extends UIInput implements NamingContainer
{
private UIInput from;
private UIInput to;
#Override
public String getFamily()
{
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException
{
String value = (String) getValue();
if(value != null)
{
String fromString = StringUtils.substringBefore(value, "-");
String toString = StringUtils.substringAfter(value, "-");
try
{
from.setValue(from.getConverter().getAsObject(context, from, fromString));
}
catch(Exception e)
{
from.setValue(new Date());
}
try
{
to.setValue(to.getConverter().getAsObject(context, to, toString));
}
catch(Exception e)
{
to.setValue(new Date());
}
}
super.encodeBegin(context);
}
#Override
public Object getSubmittedValue()
{
return (from.isLocalValueSet() ? from.getValue() : from.getSubmittedValue()) + "-" + (to.isLocalValueSet() ? to.getValue() : to.getSubmittedValue());
}
#Override
protected Object getConvertedValue(FacesContext context, Object submittedValue)
{
return from.getSubmittedValue() + "-" + to.getSubmittedValue();
}
public UIInput getFrom()
{
return from;
}
public void setFrom(UIInput from)
{
this.from = from;
}
public UIInput getTo()
{
return to;
}
public void setTo(UIInput to)
{
this.to = to;
}
}
and the managed bean:
#ManagedBean
#ViewScoped
public class RangeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private String range = "01/01/2015-31/12/2015";
public void onChange(AjaxBehaviorEvent event)
{
Messages.addGlobalInfo("[{0}] changed: [{1}]", event.getComponent().getId(), event.getBehavior());
}
public String getRange()
{
return range;
}
public void setRange(String range)
{
this.range = range;
}
}
Note that managed bean only retains range property for get/set. From and to are gone, the backing component derives and rebuilds them itself.
This question already has answers here:
Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?
(2 answers)
Closed 6 years ago.
Ok, so I get this error and found no solution working for me (I tried around a lot with annotations like #FacesConverter or #Named instead #ManagedBean etc.) If anybody could point me to the right direction, that would be awesome. BalusC mentioned, that this particular error means, that the converter cannot be found, but the Converter is getting called, as I can see in log messages of JBoss (see log.info calls below in the Converter code). At least I don't get NullPointerExceptions from the Converter anymore (although I can't reproduce why). I'm getting a bit frustrated with JSF, but I'm sure it's my fault and I have overseen something obvious?
The code should be simple enough, I want to set zero, one or more Tags for a Link entity (newLink.tags) with means of a selectManyCheckbox:
This is my XHTML form:
<h:form id="create_link_form" rendered="#{authController.loggedIn}">
<h3>Enter a new Link here:</h3>`
<h:panelGrid columns="3" columnClasses="titleCell">
<h:outputLabel for="name" value="Name:"/>
<h:inputText id="name" value="#{newLink.name}"/>
<p:message for="name" errorClass="invalid"/>
<h:outputLabel for="url" value="URL:"/>
<h:inputText id="url" value="#{newLink.url}"/>
<p:message for="url" errorClass="invalid"/>
<h:outputLabel for="description" value="Description:"/>
<h:inputText id="description" value="#{newLink.description}"/>
<p:message for="description" errorClass="invalid"/>
<h:outputLabel for="shared" value="Shared?:"/>
<h:selectBooleanCheckbox id="shared" label="Shared?:" value="#{newLink.shared}"/>
<p:message for="shared" errorClass="invalid"/>
</h:panelGrid>
<h:outputLabel for="tags" value="Tags:"/>
<h:selectManyCheckbox label="Tags:" id="tags" value="#{newLink.tags}" converter="#{tagConverter}">
<f:selectItems value="#{tags}" var="tag" itemLabel="#{tag.name}" itemValue="#{tag}"/>
</h:selectManyCheckbox>
<h:inputHidden id="owner" value="#{newLink.owner}" name="{authController.loggedInUserName}"/>
<p>
<h:panelGrid columns="2">
<h:commandButton id="create" action="#{linkController.create}" value="Create"
styleClass="create">
</h:commandButton>
<p:messages styleClass="messages" autoUpdate="true" globalOnly="true"/>
</h:panelGrid>
</p>
</h:form>
And this is my Converter:
#RequestScoped
#ManagedBean
public class TagConverter implements Converter {
#Inject
private Logger log;
#Inject
private TagService tagService;
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if (value != null && value.trim().length() > 0) {
try {
log.info("TTagConverter.getAsObject() => having " + value);
Long id = Long.parseLong(value);
Tag tag = tagService.getTagWithId(id);
log.info("TagConverter.getAsObject() => Tag converted: " + tag);
return tag;
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid tag."));
}
} else {
return null;
}
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object object) {
if (object != null) {
if (object instanceof Tag) {
Long id = ((Tag) object).getId();
return id.toString();
} else {
throw new ConverterException(new FacesMessage("There was an Object type error for a "
+ object.getClass() + " in getAsString(" + object + ")"));
}
} else {
return null;
}
}
}
Your managed bean shouldnt implement it.
Create class and implement converter.
#FacesConverter("myConverter")
public class MyConverter implements Converter{
...
}
in your facelets
<h:selectManyCheckbox label="Tags:" id="tags" value="#{newLink.tags}">
<f:selectItems value="#{tags}" var="tag" itemLabel="#{tag.name}" itemValue="#{tag}"/>
<f:converter converterId="myConverter" />
</h:selectManyCheckbox>
Is it clear?
I'm still learning how to use Facelets and I am trying to send inputted values from a JSF page to a list in a ManagedBean and then display that information in a dataTable. I have JSF page to enter in the data, a backing bean to manage the list, and a regular application for the normal objects. I can't figure out how to send the input value from the JSF to the backing bean, however.
Here is my view:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Guestbook Form</title>
</h:head>
<h:body>
<h:form>
<h1>Guestbook Application Form</h1>
<p>Please fill out all entries and click the Submit button</p>
<h:panelGrid columns="3">
<h:outputText value = "Name: "></h:outputText>
<h:inputText id="nameInputText" required ="true"
requiredMessage="Please enter your name"
value = "#{guestbookBean.name}"
validatorMessage="Name must be fewer than 20
characters">
<f:validateLength maximum="20"/>
</h:inputText>
<h:message for="nameInputText" styleClass="error"/>
<h:outputText value = "Email: "></h:outputText>
<h:inputText id="emailInputText" required ="true"
requiredMessage="Please enter your email address"
value="#{guestbookBean.email}"
validatorMessage="Invalid email address format">
<f:validateRegex pattern ="\w+([-+.']\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*"/>
</h:inputText>
<h:message for="emailInputText" styleClass="error"/>
<h:outputText value = "Enter a message: "></h:outputText>
<h:inputTextarea id="messageInputText" required ="true"
requiredMessage="Please enter a message"
value="#{guestbookBean.message}"
validatorMessage="Message must be fewer than 140 characters">
<f:validateLength maximum="140"/>
</h:inputTextarea>
<h:message for="messageInputText" styleClass="error"/>
</h:panelGrid>
<h:commandButton value="Submit" type ="Submit" action ="#{guestbookBean.setList()}"/>
<h:commandButton value ="Reset Form" type ="reset"/>
<h:outputText escape="false" value ="#{guestbookBean.result}"/>
<h:dataTable value="#{guestbookBean.list}" var="guests"
styleClass="table" cellpadding="5" cellspacing="1" border="2">
<h:column>
<f:facet name ="header">Name</f:facet>
#{guests.name}
</h:column>
<h:column>
<f:facet name ="header">Email</f:facet>
#{guests.email}
</h:column>
<h:column>
<f:facet name ="header">Message</f:facet>
#{guests.message}
</h:column>
</h:dataTable>
</h:form>
</h:body>
</html>
Here is my backing bean:
package guestbook;
import java.util.List;
import java.util.ArrayList;
import java.io.Serializable;
import javax.enterprise.context.Dependent;
import javax.faces.bean.*;
#ManagedBean(name = "guestbookBean")
public class GuestbookBean implements Serializable{
private String na;
private String em;
private String m;
private List<GuestbookEntry> bookList = new ArrayList<>();
//Set name
public void setName(String first)
{
this.na = first;
}
//Set email
public void setEmail(String mail)
{
this.em = mail;
}
//Set message
public void setMessage(String msg)
{
this.m = msg;
}
//Return the name
public String getName()
{
return na;
}
//Return the email
public String getEmail()
{
return em;
}
//Return the message
public String getMessage()
{
return m;
}
public void setList()
{
GuestbookEntry bk = new GuestbookEntry();
bk.setName(na);
bk.setEmail(em);
bk.setMessage(m);
bookList.add(0, bk);
}
public List<GuestbookEntry> getList()
{
return bookList;
}
// returns result for rendering on the client
public String getResult()
{
if ( !bookList.isEmpty())
{
return "<p style=\"background-color:yellow;width:200px;" +
"padding:5px\">Data submitted successfully"+ "</p>";
}
else
{
return ""; // request has not yet been made
}
} // end method getResult
}
Here is my model:
package guestbook;
public class GuestbookEntry {
private String firstName;
private String lastName;
private String email;
private String message;
public void setFirstName(String fn)
{
firstName = fn;
}
public void setLastName(String ln)
{
lastName = ln;
}
public void setEmail(String em)
{
email = em;
}
public void setMessage(String m)
{
message = m;
}
public String getFirstName()
{
return firstName;
}
public String getLastName()
{
return lastName;
}
public String getEmail()
{
return email;
}
public String getMessage()
{
return message;
}
}
The data is not being reset because you are not clearing the fields after the submit
You can set to null na, em and m to reset the fields
Regarding the table, try adding #ViewScoped to your bean, the default scope is RequestScope, which is not keeping your table
I would like to ask some help in understanding a particular behaviour that JSF shows when Validation Phase
fails.
I'm using:
Tomcat 7.0.2
JSF 1.2_12
RichFaces 3.3.3
Problem description.
I wrote a form with 4 input fields: an inputText and 3 selectOneMenu. The inputText is required
while the selectOneMenus don't require any validation. Attached to the first selectOneMenu (row 32),
is an a4j:support tag so that, whenever the change event is fired, the list of items of the second
and the third selectOneMenu (row 44 and 58) are correctly filled. In particular, after loading
the two lists of items, the method forces the value of the second and the third selectOneMenu
to null. This mechanism seems to work fine until I submit the form without filling the input
text: as expected, JSF validation fails but when I change the value of the first selectOneMenu,
the page keeps displaying the values specified before JSF validation failed for the second and the
third selectOneMenu (note that the actionListener is still called and the values of the second and
the third selectOneMenu are still forced to null).
Since I'm using a simple PhaseListener, I noticed the following:
before JSF validation fails, every time I change the value of the first selectOneMenu, JSF life
cycle always calls the get method for the second and the third selectOneMenu during the Render
Response Phase. In this way, JSF is able to "see" that those values have been set to null during
the Invoke Application Phase.
After validation fails, JSF stops calling those getters when I change the value of the first
selectOneMenu.
Here is my view
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich">
<head>
<title>Prove Rich</title>
</head>
<body>
<h2>Prove Rich</h2>
<f:view>
<a4j:outputPanel ajaxRendered="true">
<h:messages style="color:red" />
</a4j:outputPanel>
<h:form>
<p>
Input required: <h:inputText value="#{provaProbReplyBean.inputRequired}" required="true" />
</p>
<p>
<h:outputText value="Scegli il canale:" />
<h:selectOneMenu value="#{provaProbReplyBean.canale}">
<f:selectItem itemLabel="--" itemValue="" />
<f:selectItem itemLabel="Profamily" itemValue="Profamily" />
<f:selectItem itemLabel="Captive" itemValue="Captive" />
<a4j:support event="onchange" action="#{provaProbReplyBean.caricaProcBanche}" ajaxSingle="true" reRender="procedure, banche" />
</h:selectOneMenu>
</p>
<p>
<h:outputText value="Scegli la procedura:" />
<h:selectOneMenu id="procedure" value="#{provaProbReplyBean.procedura}">
<f:selectItem itemLabel="--" itemValue="" />
<f:selectItems value="#{provaProbReplyBean.procedureList}" />
<!-- immediately save the current value -->
<a4j:support event="onchange" ajaxSingle="true" />
</h:selectOneMenu>
</p>
<p>
<h:outputText value="Scegli la banca:" />
<h:selectOneMenu id="banche" value="#{provaProbReplyBean.banca}">
<f:selectItem itemLabel="--" itemValue="" />
<f:selectItems value="#{provaProbReplyBean.bancheList}" />
<!-- immediately save the current value -->
<a4j:support event="onchange" ajaxSingle="true" />
</h:selectOneMenu>
</p>
<p><h:commandButton value="Submit" /></p>
</h:form>
</f:view>
</body>
</html>
And here is my model:
public class ProvaProbReply {
private String inputRequired;
private String canale;
private String procedura;
private String banca;
private Map<String, List<SelectItem>> canaliProc = new HashMap<String, List<SelectItem>>();
private Map<String, List<SelectItem>> canaliBanche = new HashMap<String, List<SelectItem>>();
private List<SelectItem> procedureList = new ArrayList<SelectItem>();
private List<SelectItem> bancheList = new ArrayList<SelectItem>();
public ProvaProbReply() {
List<SelectItem> l = new ArrayList<SelectItem>();
l.add(new SelectItem("Cessione del quinto"));
l.add(new SelectItem("Credito al consumo"));
l.add(new SelectItem("Mutui"));
canaliProc.put("Profamily", l);
l = new ArrayList<SelectItem>();
l.add(new SelectItem("Credito al consumo"));
canaliProc.put("Captive", l);
l = new ArrayList<SelectItem>();
canaliBanche.put("Profamily", l);
l = new ArrayList<SelectItem>();
l.add(new SelectItem("BDL"));
l.add(new SelectItem("BM"));
l.add(new SelectItem("BPM"));
l.add(new SelectItem("CRA"));
canaliBanche.put("Captive", l);
}
public String getInputRequired() {
return inputRequired;
}
public void setInputRequired(String ir) {
inputRequired = ir;
}
public String getCanale() {
return canale;
}
public void setCanale(String c) {
canale = c;
}
public String getProcedura() {
System.out.println("\ngetProcedura called\n");
return procedura;
}
public void setProcedura(String p) {
procedura = p;
}
public String getBanca() {
System.out.println("\ngetBanca called\n");
return banca;
}
public void setBanca(String b) {
banca = b;
}
public List<SelectItem> getProcedureList() {
return procedureList;
}
public List<SelectItem> getBancheList() {
return bancheList;
}
public String caricaProcBanche() {
System.out.println("\nListener called\n");
procedureList.clear();
bancheList.clear();
if(canale != null && !canale.equals("")) {
procedureList.addAll(canaliProc.get(canale));
bancheList.addAll(canaliBanche.get(canale));
}
System.out.println("BEFORE setting:\n");
System.out.println("\nProcedura: "+procedura+"\n");
System.out.println("Banca: "+banca+"\n");
procedura = null;
banca = null;
System.out.println("\n\n\nAFTER setting:\n");
System.out.println("\nProcedura: "+procedura+"\n");
System.out.println("Banca: "+banca+"\n");
return "";
}
}
It sounds like you are observing the expected behaviour of EditableValueHolder types.
If validation or conversion of types fails, the form will be rerendered in the state it was submitted. This is hinted at in the documentation for the Apply Request Values phase (JSF 2 spec):
At the end of this phase, all EditableValueHolder components in the component tree will have been updated with new submitted values included in this request (or enough data to reproduce incorrect input will have been stored, if there were conversion errors).
If conversion or validation fails, the Update Model Values phase will not be run (none of the setters will be called on the beans.) If the user just submitted a complex form, they wouldn't expect all the fields to be wiped because one was wrong. The renderers emit the value submitted by the user and do not call the getters (see isValid() and getSubmittedValue()).