Model value of first input component is null in validator of second component - validation

I need to get the value of a <p: selectOneMenu /> for use in a validator via <f:attribute>:
<p:selectOneMenu id="tDocument" value="#{usuarioController.persona.tipoDocumento}">
<f:selectItem itemLabel="#{msg.selectOne}" itemValue=""/>
<f:selectItems value="#{tipeListController.tipoIdentificacion}" var="_tDocument" itemValue="#{_tDocument}"/>
</p:selectOneMenu>
<p:inputText id="doc" value="#{usuarioController.persona.num_documento}" required="true" validator="ciRucValidator">
<f:attribute id="idenType" name="identificationType" value="#{usuarioController.persona.tipoDocumento}" />
</p:inputText>
But when trying to get it in the validator as below I get null:
TipoIdentificacion identificationType = (TipoIdentificacion) component.getAttributes().get("identificationType");
How is this caused and how can I solve it?

The model value is set during 4th phase "Update Model Values". However, validators run during 3rd phase "Process Validators". That is thus one phase earlier. It should be obvious that the updated model value of the other component is not available at that moment.
The canonical approach is to just pass the component and then extract the value directly from it via UIInput#getValue() or UIInput#getSubmittedValue() depending on the order of the components.
<p:selectOneMenu binding="#{tDocument}" ...>
...
</p:selectOneMenu>
<p:inputText ... validator="ciRucValidator">
<f:attribute name="tDocument" value="#{tDocument}" />
</p:inputText>
Note that I removed <f:attribute id>, this doesn't exist, and also note that binding example is as-is; very importantingly without a bean property.
You can grab it in the validator as below:
UIInput tDocument = (UIInput) component.getAttributes().get("tDocument");
TipoIdentificacion identificationType = (TipoIdentificacion) tDocument.getValue();
// ...
This also gives you the opportunity to invalidate the other component via setValid(false) if necessary.
See also:
How to get the value of another component in a custom validator?

Related

JSF form validation skip specific validator for second submit when user wants to override

I am validating a user entered account number using two validators, one for basic standard format, and the other that validates the account number against values stored in a database. The database of valid account numbers may not always be up to date so I want to allow the user to override and submit their entered account number but only after the database validation has failed. I always want to validate its standard format 8 characters with no spaces.
<h:form id="formId">
<p:panelGrid>
<p:row>
<p:column>
<p:outputLabel value="Account : " for="acct" />
</p:column>
<p:column>
<p:selectOneMenu id="acct" value="#{bean.acct.acctNum}" effect="fold" editable="true" validator="acctLengthAndSpaceValidator" required="true" requiredMessage="Required">
<f:selectItems value="#{bean.mySavedAccounts}" var="acct"
itemLabel="#{acct.acctNumber} itemValue="#{acct.acctNumber}" />
<o:validator validatorId="accountDatabaseValidator" disabled="#{bean.skipDbValidation}" />
</p:selectOneMenu>
</p:column>
<p:column>
<p:messages for="acct" showDetail="true" skipDetailIfEqualsSummary="true" />
</p:column>
</p:row>
</p:panelGrid>
<br />
<p:selectBooleanCheckbox rendered="#{facesContext.validationFailed}" value="#{bean.skipDbValidation}" itemLabel="I know this account is really valid, please skip validation and let me submit!">
<p:ajax update="#this" listener="#{bean.testListener()}" />
</p:selectBooleanCheckbox>
<p:commandButton value="Submit" action="#{bean.submit()}" update="formId"/>
</h:form>
The checkbox does appear after the form is initially submitted and has any validation failure (I will figure out how to isolate to just the failed accountDatabaseValidator). But then when I select the checkbox, and submit again, both validators are still fired. I added the ajax listener to debug, and it isn't firing and the boolean value skipDbValidation is still false.
Perhaps my approach is not correct in achieving my concrete goal of validating against the database but then giving the user the option of skipping the db validation after initial failure.
EDIT
if i remove rendered="#{facesContext.validationFailed}" from the checkbox and have it visible all the time, the boolean skipDbValidation will get set to true if the checkbox is checked and then on subsequent submit, the skipDbValidation is ignored as expected. But I do not want the checkbox allowing the user to bypass visible at first. Only after validation fails.
The technical explanation that this doesn't work is that the rendered attribute is re-evaluated during processing the form submit. At this point the faces context is not validationFailed anymore (it was only validationFailed during the previous request) and thus the component is not rendered anymore and then the component's submitted value won't be applied. This matches #6 of commandButton/commandLink/ajax action/listener method not invoked or input value not set/updated.
Your work around by rendering it client-side rather than server-side is acceptable. But I gather that you wanted to show it only when the specific validator has been invoked. There are at least 2 ways to achieve this:
Check UIInput#isValid() of the input component of interest. You can achieve that by binding the component to the view (not to the bean!) via component's binding attribute so you can reference it elsewhere in the same view.
<p:selectOneMenu binding="#{acct}" ...>
...
</p:selectOneMenu>
...
<p:selectBooleanCheckbox styleClass="#{acct.valid ? 'ui-helper-hidden' : ''}" ...>
...
</p:selectBooleanCheckbox>
Note that I took the opportunity to reuse the PrimeFaces-provided style class.
Or, make the validator a view scoped bean and reference it via <o:validator binding> instead.
#Named
#ViewScoped
public class AccountDatabaseValidator implements Validator, Serializable {
private boolean validationFailed;
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
// ...
validationFailed = !isValid(value);
if (validationFailed) {
throw new ValidatorException(createError("Invalid value"));
}
}
public boolean isValidationFailed() {
return validationFailed;
}
}
<p:selectOneMenu ...>
<o:validator binding="#{accountDatabaseValidator}" ... />
</p:selectOneMenu>
...
<p:selectBooleanCheckbox rendered="#{accountDatabaseValidator.validationFailed}" ...>
...
</p:selectBooleanCheckbox>
My work around to get the checkbox to programmatically display and so the checkbox would function was to hide and display using CSS instead of the render attribute.
style="#{facesContext.validationFailed ? 'Display: inline' : 'Display: none;'}"
<p:selectBooleanCheckbox style="#{facesContext.validationFailed ? 'Display: inline' : 'Display: none;'}" value="#{bean.skipDbValidation}" itemLabel="I know this account is really valid, please skip validation and let me submit!">
<p:ajax update="#this" />
</p:selectBooleanCheckbox>
But I still can't figure out how to display the checkbox for a specific validation failure.
I will post another question for that
EDIT
Here is how I ended up displaying the checkbox only after the Invalid Account validation failure.
<p:selectBooleanCheckbox style="#{facesContext.messageList.stream()
.anyMatch(v -> v.summary == 'Invalid Account') or
bean.skipDbValidation ? 'Display: inline' : 'Display: none;'}"
value="#{bean.skipDbValidation}" itemLabel="I know this account is really valid, please skip validation and let me submit!">
<p:ajax update="#this" />
</p:selectBooleanCheckbox>

Skip validation conditionally, when empty f:selectItem in p/h:selectOneMenu is selected

Parents :
<p:selectOneMenu id="parentList"
value="#{bean.selectedParent}"
converter="#{parentConverter}"
required="true">
<f:selectItem itemLabel="Select" itemValue="#{null}"/>
<f:selectItems var="parent"
value="#{bean.parentList}"
itemLabel="#{parent.parentName}"
itemValue="#{parent}"/>
<p:ajax update="childrenList" listener="#{bean.setchildren}"/>
</p:selectOneMenu>
Children :
<p:selectOneMenu id="childrenList"
value="#{bean.selectedchild}"
converter="#{childConverter}"
required="true">
<f:selectItem itemLabel="Select" itemValue="#{null}"/>
<f:selectItems var="child"
value="#{bean.childrenList}"
itemLabel="#{child.childName}"
itemValue="#{child}"/>
</p:selectOneMenu>
The managed bean :
#Named
#ViewScoped
public class Bean implements Serializable {
#Inject
private Service service;
private Parent selectedParent;
private Child selectedChild;
private List<Parent> parentList;
private List<Child> childrenList;
private static final long serialVersionUID = 1L;
public Bean() {}
#PostConstruct
private void init() {
parentList = service.getParentList();
// Not necessary unless selectedParent is already available in edit mode.
if(selectedParent != null) {
childrenList = service.getChildrenListByParent(selectedParent);
}
}
public void setChildren() {
if(selectedParent != null) {
childrenList = service.getChildrenListByParent(selectedParent);
} else {
childrenList = null;
}
}
// Getters and setters.
}
The children list is to be populated based on their parent i.e. the children list should only contain children associated with a particular parent.
When the first parent in the parent list is selected, the children list should be reset to empty i.e. children should not be visible without their parent.
Since the parent list has a required field validator, it causes validation. When the first item in the parent list is selected, the children list will be prevented from being updated because of required="true". There is technically nothing wrong but existence of children without their parent may give end-users a bad experience.
What should happen is, when the first item in the parent list is selected, it should not cause validation i.e. the validation is to be skipped conditionally.
One way of doing this is to check either the selectedChild or childrenList itself is null/empty. Such as,
required="#{empty selectedChild or empty childrenList}"
But this does not appear to be a canonical way of skipping validation conditionally in such cases.
Does there exist a better way of skipping validation, when the first item in the parent list is selected so that the children list can be emptied as well along with the parent list (The validation should cause in all other cases. For example, when the form itself is submitted either synchronously or asynchronously)?
Basically, you want action-dependent validation. I.e. skip validation when that specific <p:ajax> action is invoked, and not on other actions.
This is unfortunately indeed not trivial to declare in the view. There are several tricks/workarounds for that. Most used one is to just check if the particular action is (not) invoked.
E.g. check if the desired save button is invoked by determining the presence of its client ID in the HTTP request parameter map as available by implicit EL object #{param}:
<h:form>
<p:selectOneMenu ... required="#{not empty param[save.clientId]}">
...
<p:ajax ... />
</p:selectOneMenu>
<p:selectOneMenu ... required="true">
...
</p:selectOneMenu>
<p:commandButton binding="#{save}" ... />
</h:form>
Or check if component's own <p:ajax> is not invoked by determining if component's own client ID does not equal the HTTP request parameter with predefined name javax.faces.source representing the source of the ajax request (the #{component} below is an implicit EL variable representing the current UIComponent):
<h:form>
<p:selectOneMenu ... required="#{param['javax.faces.source'] ne component.clientId}">
...
<p:ajax ... />
</p:selectOneMenu>
<p:selectOneMenu ... required="true">
...
</p:selectOneMenu>
<p:commandButton ... />
</h:form>
Or check if the parent form is submitted by UIForm#isSubmitted(), which would only evaluate true when a "full form submit" is used as in process="#form" (the <p:ajax process> defaults to #this which wouldn't trigger a "full form submit", and the <p:commandButton process> defaults to #form, which would thus trigger a "full form submit"):
<h:form binding="#{form}">
<p:selectOneMenu ... required="#{form.submitted}">
...
<p:ajax ... />
</p:selectOneMenu>
<p:selectOneMenu ... required="true">
...
</p:selectOneMenu>
<p:commandButton ... />
</h:form>
Or without binding the form by referencing the form via UIComponent#getNamingContainer() (if you know the position in the component tree; if the form is e.g. 2 naming container parents back, then use #{component.namingContainer.parent.namingContainer.submitted}):
<h:form>
<p:selectOneMenu ... required="#{component.namingContainer.submitted}">
...
<p:ajax ... />
</p:selectOneMenu>
<p:selectOneMenu ... required="true">
...
</p:selectOneMenu>
<p:commandButton ... />
</h:form>
Take your pick. The first solution has been offered several times before as it's most easy to understand and tweak by starters.
See also:
How to let validation depend on the pressed button?
Primefaces dependent selectOneMenu and required="true"
How to disable the required tag on a selectOneMenu when p:ajax event=change?

Submit empty value to bean anyway on input with required="true"

I've the below input with required="true":
<p:inputText value="#{bean.value}" required="true">
<p:ajax event="change" listener="#{bean.change()}" />
</p:inputText>
When user changes the value, the listener is fired and I can access the changed value. When user empties the field, the listener is not fired and the empty value is not updating in my bean. I gather that this is caused by requried="true". I would like to update my bean with empty value and fire the listener anyway when the user empties the field. How can I achieve this?
You can just use expression language (EL) in the required attribute too. You can then just check if the main submit button of the form has been pressed. Imagine that your form has a "Save" button like below,
<p:inputText ... required="true">
<p:ajax ... />
</p:inputText>
...
<p:commandButton ... action="#{bean.save}" />
Then you can let the required attribute evaluate true only if the button is invoked. You can achieve that by referencing the component via binding and checking if its client ID is present in the HTTP request parameter map:
<p:inputText ... required="#{not empty param[save.clientId]}">
<p:ajax ... />
</p:inputText>
...
<p:commandButton binding="#{save}" ... action="#{bean.save}" />
Note that #{save} is as-is and may absolutely not be bound to a backing bean, and that the variable name must be unique in the current view and EL scope.
See also:
How to let validation depend on the pressed button?
The issue is that if the user clears the required input field then 'required' validator throws an exception and bean setter will not be called. When the form is reloaded then cleared value will show up again from the bean. Here is my workaround:
public String getSomething() {
return isFormValueEmpty("form:something") ? null : this.something;
}
private Boolean isFormValueEmpty(String formFieldName) {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
String formValue = ec.getRequestParameterMap().get(formFieldName);
logger.debug("Check if form value is empty: [{}] [{}]", formFieldName, formValue);
return StringUtils.isEmpty(formValue);
}

Execute Backing Bean Action on p:selectOneRadio

I'm using a radio component on which, when selecting an item I want to execute an action on backing bean (not navigate to an outcome page, but to perform some action and then update current page via Ajax). Problem is I can't get the backing bean value change listener action to execute
<h:form id="one-radio">
<p:selectOneRadio layout="grid"
valueChangeListener="#{myBean.selectRadioItem}" >
<p:ajax process="#this" update="#form" />
<f:selectItems value="#{myBean.radioOptions}" var="radio"
itemLabel="#{radio.name}" itemValue="#{radio.id}" >
</f:selectItems>
</p:selectOneRadio>
</h:form>
and the backing bean method...
public void selectRadioItem(ValueChangeEvent event) {
String[] selectedItems = (String[]) event.getNewValue();
//more...
}
is there something wrong in the code which I'm missing? I've used this same structure for select many check box which is working...
Rodrigo, there's a difference between valueChangeListener and a simple method call via ajax.
Check this answer by BalusC about the difference between valueChangeListener and <f:ajax>
To solve your problem, you could use the listener property of <p:ajax>.
OneRadio component is used to receive only one value, if you want to select a list of values, use SelectOneMenu
Try to do the following:
<p:selectOneRadio layout="grid" value="#{myBean.radioValue}">
<p:ajax process="#this" update="#form" listener="#{myBean.selectRadioItem}"/>
<f:selectItems value="#{myBean.radioOptions}" var="radio"
itemLabel="#{radio.name}" itemValue="#{radio.id}" >
</f:selectItems>
</p:selectOneRadio>
In the backbean, you can remove the event parameter, because the value of the OneRadio component now is a property, which I named as radioValue
String radioValue;
...
public void selectRadioItem() {
String selectedItem = this.radioValue;
//more...
}

How to skip validation for ajax call?

I have an h:inputText, h:selectonemenu and commandbuton. Inputtext is mandatory field and I have defined it as immediate="true". Then I when I click the button I want to pass current value of selectonemenu to managed bean. But its passig null. How can I manage this validation so that it allows me to get the value of selectOneMenu in Managed bean..
My code is..
<h:inputText id="inputSome" required="true" requiredMessage="Pls enter something"/>
<h:message for="inputSome"></h:message>
<h:selectOneMenu id="recepients" value="#{controller.selected}" immediate="true">
<f:selectItem itemLabel="Select" itemValue=""/>
<f:selectItems value="#{controller.tempNameList1}"></f:selectItems>
</h:selectOneMenu>
<p:commandButton value="Add" action="#{controller.submit}"
immediate="true"/>
When you put immediate=true in the commandButton, then Invoke Application phase is directly executed skipping the phases after (and including) validations. So "applying model values" phase is also skipped and the properties of managed bean are remained uninitialized. This causes you passing null for the value of selectOneMenu. The solution is, you have to retrieve the value for selected property of the controller manually, like bellow:
Map<String, String> paramMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
for (String key : paramMap.keySet()) {
if (key.contains("recepients")) {
selected = Integer.parseInt(paramMap.get(key));
}
}

Resources