JSF2 Cross Field Validation Failure Loop - validation

Requirements in an app I'm developing say that when performing a search, the user should not be able to search for City without entering State, and vice versa, they shouldn't be able to search for State without entering City.
search.xhtml
<h:inputText id="city" binding="#{city}" value="#{search.city}" validator="#{search.validateCity}">
<f:attribute name="state" value="#{state}"/>
</h:inputText>
<h:inputText id="state" binding="#{state}" value="#{search.state}" validator="#{search.validateState}">
<f:attribute name="city" value="#{city}"/>
</h:inputText>
Search.java
public void validateCity(FacesContext context, UIComponent component, Object convertedValue) {
UIInput stateComponent = (UIInput) component.getAttributes().get("state");
String state = (String) stateComponent.getValue();
if(convertedValue.toString().length() > 0) {
if(state.length() < 1) {
throw new ValidatorException(new FacesMessage("Please enter State."));
}
}
}
public void validateState(FacesContext context, UIComponent component, Object convertedValue) {
UIInput cityComponent = (UIInput) component.getAttributes().get("city");
String city = (String) cityComponent.getValue();
if(convertedValue.toString().length() > 0) {
if(city.length() < 1) {
throw new ValidatorException(new FacesMessage("Please enter City."));
}
}
}
I've simplified down my code to show what I attempted with the standard cross field validation method. However, the problem I'm hitting is that in the validation phase, both City and State are showing Validation errors, I'm guessing because the two validators are getting in each others' way and therefore creating a loop of failure.
Is there a workaround I can use to get around this?
Thanks.

The components are validated in the order as they are declared in the component tree.
When you call UIInput#getValue() on a component which isn't validated yet, then it'll return null. Also, when you call UIInput#getValue() on a component which is already validated and been marked invalid, then it'll return null (or the old model value).
If you want to get the value of the second component during validation of the first component, then you should be using UIInput#getSubmittedValue() instead of UIInput#getValue(). You should only keep in mind that this returns the unconverted String.
Alternatively, you could take a look at OmniFaces <o:validateAllOrNone> component.
<h:inputText id="city" value="#{search.city}" />
<h:inputText id="state" value="#{search.state}" />
<o:validateAllOrNone id="cityAndState" components="city state" message="Please fill both city and state." />
<h:message for="cityAndState" />

Related

Get the validation message for a specific component

<h:inputText id="myInputText"
title="The text from validation message here"
style="#{component.valid? '' : 'border-color:red'}"
validator="#{MyBean.validate}"
required="true"
requiredMessage="required"
value="#{MyBean.value} />
<p:message for="myInputText" display="text"/>
Since I want to custom the looking for a failed validation in an inputText compoment and I know that it is possible to know whether the component was successfully validated or not, I would like to know if it is viable and how I can get the validation message, in order to display it as the tittle of my inputText component.
The problem you will have with what you're planning is that a single component can have more than one message queued. What are you going to do then? For demonstration purposes, you can use
<h:inputText id="myInputText"
title="#{facesContext.getMessageList('myInputText').get(0)}"
style="#{component.valid ? '' : 'border-color:red'}"
validator="#{MyBean.validate}"
required="true"
requiredMessage="required"
value="#{MyBean.value}" />
EDIT : You should just move the logic into your backing bean:
Implement a method that'll pull the detail from an available FacesMessage list, given a clientId
public String getComponentMessageDetail(String clientId) {
String detail = null;
FacesContext ctxt = FacesContext.getCurrentInstance();
List<FacesMessage> componentMessages = ctxt.getMessages(clientId);
if (componentMessages != null && componentMessages.isEmpty() == false) {
//returns the detail, from only the first message!
detail = componentMessages.get(0).getDetail();
}
return detail;
}
Use the utility method in your view
<h:inputText id="myInputText"
title="#{MyBean.getComponentMessageDetail('myInputText')}"
style="#{component.valid ? '' : 'border-color:red'}"
validator="#{MyBean.validate}"
required="true"
requiredMessage="required"
value="#{MyBean.value}" />
How about this java method
public String getComponentMessageDetail(String cid){
FacesContext ctxt = FacesContext.getCurrentInstance();
Iterator<FacesMessage> cm = ctxt.getMessages(cid);
List<String> msg = new ArrayList<>();
while(cm.hasNext()) {
msg.add(cm.next().getDetail());
}
return String.join(" | ", msg);
}
to show everything what's in the message cache?
Also in xhtml
<h:inputText id="myInputText" title="#{MyBean.getComponentMessageDetail('yourFormId:myInputText'}" style="#{component.valid? '' : 'border-color:red'}" validator="#{MyBean.validate}" required="true" requiredMessage="required" value="#{MyBean.value} />
it might be useful to put the name of your form-id in front of the input control's id. Otherwise the message list might have zero items, although there are some.
Here is another way to quickly show validation messages: h:messages

Validate input as required only if certain command button is pressed

I have specific use case for JSF validation. For example I have an inputText field:
<p:inputText id="input" required="#{myBean.required}" value="#{myBean.value}" maxlength="20" disabled="#{myBean.disabled}">
<p:ajax event="blur" process="#this" update="name" listener="#{myBean.listener}"/>
</p:inputText>
Value of input is number (in some cases it can also be a string, because this is part of composite component, but problem is better described if we assume this is a number). This input is part of the form, at the end of form I have submit button:
<p:commandButton value="Save" actionListener="#{myBean.save}"/>
What are my requests:
When submit button is pressed all validation should be processed and this is OK, this works fine.
When blur event is fired on input field if field is not empty a number validation should be processed, and this is also OK. At the end I update field with id name with some value.
Now I have a problem. My third request is when input is empty validation on input should not be processed. This is special case in which I will clear field with id name. This is also case when i remove text which is already entered in input, remove focus from component (press TAB for example) and in that case AJAX request should also be processed and name input will also be cleared.
How I can disable validation of this input field in case when it is empty, and just for this ajax event?
Let the input's required attribute check if the save button is pressed or not (which can be identified by the presence of its client ID in the request parameter map).
<h:form>
<p:inputText ... required="#{not empty param[save.clientId] and myBean.required}" />
<p:commandButton binding="#{save}" ... />
</h:form>
(note: do not bind it to a bean property! the code is as-is)
This way it would only evaluate true when the save button is actually pressed.
Or, if you have problems with binding and/or don't have a problem hardcoding the button's client ID:
<h:form id="formId">
<p:inputText ... required="#{not empty param['formId:buttonId'] and myBean.required}" />
<p:commandButton id="buttonId" ... />
</h:form>
Just remove the required attribute as you accept the input if the input is empty. Then write a custom validator which accepts only empty input or numerical input.
<p:inputText id="input" value="#{myBean.value}" maxlength="20" disabled="#{myBean.disabled}" validator="customerNumericInputValidator"> <p:ajax event="blur" process="#this" update="name" listener="#{myBean.listener}"/> </p:inputText>
public class customerNumericInputValidator implements Validator {
#Override
public void validate(FacesContext facesContext, UIComponent uIComponent,
Object object) throws ValidatorException {
String number = (String) object;
number = Strings.nullToEmpty(number).trim();
//if the request is a full request then number can not be empty
if(!FacesContext.getCurrentInstance().isPostback() && Strings.isNullOrEmpty(number))
{
FacesMessage message = new FacesMessage();
message.setSummary(Messages.getMessage("error empty value"));
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
if(!Strings.isNullOrEmpty(number))
{
if(!isNumber(number))
{
FacesMessage message = new FacesMessage();
message.setSummary(Messages.getMessage("error not numerical value"));
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}
}
}

How can I display ValidatorException and required="true" of same input field in different messages elements

I took the following BalusC kickoff example and modified it a bit by adding a submit button and additional h:messages and removing the f:ajax from the h:inputSecret's (removed the f:ajax cause for some reason when I leave the first h:inputSecret it immediately displays "value is required" error for the second h:inputSecret - but the user haven't got the chance to type it in... ??? <- another future question ?:) )
OK, to make long story short:
I'm trying to figure out how can display the validation errors regarding the both password fields(that the passwords are not equal) in the global h:messages and not in the individual h:message of the password fields
I do want that the required="true" will be displayed in the <h:message of each field...
But right now the validation message (thrown by my exception) and the required="true" are being displayed in the same place
Here is the code:
<h:outputLabel for="password" value="Password:" />
<h:inputSecret id="password" value="#{bean.password}" required="true">
<f:validator validatorId="confirmPasswordValidator" />
<f:attribute name="confirm" value="#{confirmPassword.submittedValue}" />
</h:inputSecret>
<h:message id="m_password" for="password" />
<h:outputLabel for="confirm" value="Password (again):" />
<h:inputSecret id="confirm" binding="#{confirmPassword}" required="true">
</h:inputSecret>
<h:message id="m_confirm" for="confirm" />
And additional h:commandButton with h:messages below that code :
<h:commandButton value="doSomething" action="#{myBean.myAction}">
<f:ajax execute="password confirm" render="m_password m_confirm"></f:ajax>
</h:commandButton>
<h:messages globalOnly="true" styleClass="validation_value_required"/>
#FacesValidator("confirmPasswordValidator")
public class ConfirmPasswordValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
String password = (String) value;
String confirm = (String) component.getAttributes().get("confirm");
if (password == null || confirm == null) {
return; // Just ignore and let required="true" do its job.
}
if (!password.equals(confirm)) {
throw new ValidatorException(new FacesMessage("Passwords are not equal."));
}
}
}
Also
Thanks ahead,
Solution (Thanks to BalusC)
changed
<f:attribute name="confirm" value="#{confirmPassword.submittedValue}" />
to
<f:attribute name="confirm" value="#{confirmPassword}" />
and
String confirm = (String) component.getAttributes().get("confirm");
into
UIInput confirmPasswordComponent = (UIInput) component.getAttributes().get("confirm");
String confirm = (String) confirmPasswordComponent.getSubmittedValue();
and
throw new ValidatorException(new FacesMessage("Passwords are not equal."));
into
context.addMessage(null, new FacesMessage("Passwords are not equal."));
context.validationFailed();
((UIInput) component).setValid(false);
confirmPasswordComponent.setValid(false);
return;
If a Validator on a particular component throws a ValidatorException, then its FacesMessage will automatically be associated with the component on which the Validator is invoked.
You need to manually add the FacesMessage on a null client ID so that it end up in <h:messages globalOnly="true">. You also need to manually set validationFailed() on FacesContext so that JSF won't update the model values nor invoke the action. If necessary (though recommended), you also need to manually mark the components as invalid so that any appropriate listeners/tree-visitors (e.g. for highlighting) will take this into account.
if (!password.equals(confirm)) {
context.addMessage(null, new FacesMessage("Passwords are not equal."));
context.validationFailed();
((UIInput) component).setValid(false);
confirmPasswordComponent.setValid(false); // You'd need to pass it as component instead of as its submitted value in f:attribute.
}
By the way, the OmniFaces project has an <o:validateEqual> component which should make this less tedious. See also showcase example.

Input text validation based on drop-down list selection

How can I validate an input text box based on a selection from the drop-down list?
You could pass the selected value of the dropdown as an attribute of the input component so that the validator can grab it.
E.g.
<h:selectOneMenu binding="#{menu}" value="#{bean.item}">
<f:selectItems value="#{bean.items}" />
</h:selectOneMenu>
<h:inputText value="#{bean.input}">
<f:attribute name="item" value="#{menu.value}" />
<f:validator validatorId="inputValidator" />
</h:inputText>
with
#FacesValidator("inputValidator")
public class InputValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) {
Object item = component.getAttributes().get("item");
// ...
}
}
Note that the ordering of the components matters. JSF processes UIInput components in the order they appear in the view. If the dropdown component is placed after the input text component, then you need to pass #{menu.submittedValue} as attribute, but at that point the value is not converted yet. You could if necessary workaround with a <h:inputHidden> which is placed after the both components and put the validator in there.

<f:validator disabled flag not working

I've created a custom validator for my project, it simply checks the select ones value and 'validates' the value is not '0'. We have a standard (I'm sure not uncommon) of manually setting the first value of our selectOneMenu compents to:
<f:selectItem itemValue="0"
itemLabel="-- Select One --"/>
Which works fine, but then makes the component always pass the required check. So this validator simply treats this value as if there was no selection made.
SelectOneMenu example:
<h:selectOneMenu id="eligibility"
value="#{reg.eligibility}"
required="#{reg.fieldsRequired}">
<f:selectItem itemValue="0"
itemLabel="-- Select One --"/>
<f:selectItems value="#{reg.eligibilityList}" />
<f:validator validatorId="selectOneValidator"
disabled="#{!reg.fieldsRequired}"/>
Custom Validator:
#FacesValidator("selectOneValidator")
public class SelectOneValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent uiComponent, Object o) throws ValidatorException {
String val = null;
if (uiComponent instanceof HtmlSelectOneMenu) {
HtmlSelectOneMenu oneMenu = (HtmlSelectOneMenu) uiComponent;
if (oneMenu.isRequired() && !oneMenu.isDisabled()) {
if (o instanceof String) {
val = (String) o;
} else if (o instanceof Number) {
val = String.valueOf(o);
}
if ("0".equals(val)) {
FacesMessage msg = new FacesMessage();
msg.setSummary("Please select a value from the list.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
}
}
}
}
This has worked just fine thus far, however, the current use-case I'm running into trouble.
My page has multiple SelectOneMenus one of which toggles the required and disabled attributes via an ajax call for the page. I'm not having any issues with the required="#{reg.fieldsRequired}", however; the disabled="#{!reg.fieldsRequired}" attribute on my custom validator does not seem to make a difference. I'm just thinking out loud, but when the page first loads the #{reg.fieldsRequired} expression is false. If I then change the SelectOneMenu to set this boolean value to true, then press the submit button, the disabled attribute doesn't seem to have been set. I wondered if this is simply a ajax issue and that all of components simply needed to be re-renderd so I added the #form in my ajax call: <a4j:ajax render="#form" listener="#{reg.saveActionChanged}"/> in hopes that would fix the problem, but no difference in the behavior.
Environment:
JSF 2.0.3
Tomcat 6.0.14
ajax call being made with RichFaces 4.0
Any help is most appreciated!
Sorry, I can't reproduce your problem with JSF 2.1.3. The <f:validator disabled> works as expected.
However, as a completely different alternative, you can in this particular case just make use of the standard required validator. You only need to set the item value to #{null} instead of 0.
<f:selectItem itemValue="#{null}" itemLabel="-- Select One --"/>
This way you don't need the custom validator. The message can be set as requiredMessage attribute of the input component.
<h:selectOneMenu requiredMessage="Please select a value from the list.">

Resources