I need to validate across multiple fields in such a way that validation should be violated only when one of the given fields is violated.
It is distinct from cross field validation in which a value of one field is dependent upon the value(s) of one or more of the rest of the fields.
Given below a simple scenario.
<p:inputText id="txt1" value="#{testBean.txt1}" required="false" maxlength="45"/>
<p:inputText id="txt2" value="#{testBean.txt2}" required="false" maxlength="45"/>
<p:inputText id="txt3" value="#{testBean.txt3}" required="false" maxlength="45"/>
<p:commandButton id="btnSubmit" actionListener="#{testBean.insert}"
icon="ui-icon-check" value="Save"/>
In which, validation violation should be occurred only when one of the given three text fields is left blank. If anyone of them is filled with a value then, all should be validated. In which case, validation should not be violated.
How to proceed with this scenario? Does JSF/PrimeFaces provide some way to perform validation in this way?
I have a hard time in wrapping my head around your concrete functional requirement, but I believe you're looking for "all or none" validation. I.e. either all fields should be blank, or all fields should be filled. JSF utility library OmniFaces has a validator for exactly this purpose, the <o:validateAllOrNone>.
Here's how you could use it:
<p:inputText id="txt1" value="#{testBean.txt1}" maxlength="45" />
<p:inputText id="txt2" value="#{testBean.txt2}" maxlength="45" />
<p:inputText id="txt3" value="#{testBean.txt3}" maxlength="45" />
<o:validateAllOrNone components="txt1 txt2 txt3" />
Of course!
Primefaces provide a lot of ways that can satisfact you. First of all, you can make validations in your MBean method. In your case, you're calling insert method, so you can do something like this:
public String insert(){
boolean error = false;
if(txt1.isEmpty()){
error = true;
}
if(txt2.isEmpty()){
error = true;
}
if(txt3.isEmpty()){
error = true;
}
if(error == true){
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_WARN,"Empty fields!", "Insert something in at least one input!"));
return null;
}else{
return "myPage"
}
}
Note that you can improve the validations by yourself, following your needs. You can also change the message from:
FacesMessage.SEVERITY_WARN
to:
FacesMessage.SEVERITY_INFO
FacesMessage.SEVERITY_ERROR
FacesMessage.SEVERITY_FATAL
What can give your application a better error message.
Before this works, add this above your input fields:
<p:messages id="messages" showDetail="true" autoUpdate="true" closable="true" />
Probably this will work like a charm! If you're interested, check the Primefaces Messages showcase, where you can find some examples and understand better how <p:messages> works. Also, feel free to check <p:growl>, that in my opinion is a lot better than simple messages. Check out the growl here.
Hope I helped you (:
Related
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>
In a basic registration screen (with button register records the screen) there are two panels:
Data panel:
Address panel:
I can register by completing only the Data panel. It is not necessary to fill the Address panel. However, if at least one field of the Address panel is filled, then all other fields in the same panel should be required.
How can I achieve this?
You need to check in the required attribute if the other inputs have submitted a non-empty value. Since this can result in quite some boilerplate, here's a kickoff example with only 3 input components.
<h:form id="form">
<h:inputText id="input1" value="#{bean.input1}" required="#{empty param['form:input2'] and empty param['form:input3']}" />
<h:inputText id="input2" value="#{bean.input2}" required="#{empty param['form:input1'] and empty param['form:input3']}" />
<h:inputText id="input3" value="#{bean.input3}" required="#{empty param['form:input1'] and empty param['form:input2']}" />
</h:form>
An alternative is to bind the components to the view and use UIInput#getValue() to check the value of the previous components and UIInput#getSubmittedValue() to check them for next components (components are namely processed in the order as they appear in the component tree). This way you don't need to hardcode client ID's. You only need to ensure that binding names doesn't conflict with existing managed bean names.
<h:inputText binding="#{input1}" value="#{bean.input1}" required="#{empty input2.submittedValue and empty input3.submittedValue}" />
<h:inputText binding="#{input2}" value="#{bean.input2}" required="#{empty input1.value and empty input3.submittedValue}" />
<h:inputText binding="#{input3}" value="#{bean.input3}" required="#{empty input1.value and empty input2.value}" />
You'll understand that this produces ugly boilerplate when you have more and more components. The JSF utility library OmniFaces has a <o:validateAllOrNone> validator for the exact purpose. See also the live demo. Based on your quesiton tags, you're using OmniFaces, so you should already be set with just this:
<o:validateAllOrNone components="input1 input2 input3" />
<h:inputText id="input1" value="#{bean.input1}" />
<h:inputText id="input2" value="#{bean.input2}" />
<h:inputText id="input3" value="#{bean.input3}" />
First you should add a method to backing bean something like this:
public boolean isAddressPanelRequired() {
// Check if at least one field is entered
// and return true if it is and false othervise
}
Each input element on address panel should have required="#{backingBean.addressPanelRequired}"
Then add onblur ajax listener on each input component on address panel which process that component, and updates address panel.
I have a form with, say a text field and a multi-select field.
<h:form id="form2">
<h:messages for="text3" />
<h:inputText id="text3" value="#{danielBean.text3}" required="true"/>
<br/>
<h:messages for="select1" />
<h:selectManyMenu id="select1" value="#{danielBean.selStrings}"
style="height:8em;" required="true"/>
<f:selectItems value="#{danielBean.allStrings}" var="_item" itemLabel="#{_item} (length #{_item.length()})" />
<br/>
<p:commandButton
value="Submit"
action="#{danielBean.save()}"
update=":form2"
>
</h:form>
On submit, both get validated, and if validation is successful, the relevant variables in the backing bean are updated. Fair enough.
The problem happens when the validation (of the multi-select) is NOT successful. In my Bean, I have (particularly for the List<String>s allStrings and selStrings)...
#PostConstruct
public void init() {
text3 = "";
allStrings.clear();
allStrings.add("This");
allStrings.add("is");
allStrings.add("a");
allStrings.add("test");
selStrings.clear();
selStrings.add("a");
}
...so that the multi-select has one pre-selected option. If the user unselects that option (i.e. no options chosen), the validation will -of course- fail, an error message will be displayed...
...but the multiselect will not be empty. It will show the content from the bean, i.e. "a" selected. This is confusing to the user - getting an error message "input required", and being shown a filled-out field.
This appears to be a feature of JSF's lifecycle, see this article by BalusC:
"When JSF renders input components, then it will first test if the submitted value is not null and then display it, else if the local value is not null and then display it, else it will display the model value."
This works fine for the text field text3, because it submits as an empty string, not null.
The problem is that zero selected options from a multi-select means that the submitted value is null, that the local copy (I guess, since it's the first submit) is null, so that the model value ("a" selected) is displayed.
I do not want that.
How can I force JSF to use the null value it got submitted when rendering the validation response?
Thanks in advance
Antares42
There is no solution for this (other than reporting an issue to JSF guys and/or hacking in JSF source code). There's however a workaround: update only the components which really need to be updated on submit. Currently, you've set ajax to update the entire form. How about updating just the messages?
<h:messages id="text3_m" for="text3" />
...
<h:messages id="select1_m" for="select1" />
...
<p:commandButton ... update="text3_m select1_m" />
You can if necessary make use of PrimeFaces Selectors to minimize the boilerplate if you have rather a lot of fields:
<h:messages for="text3" styleClass="messages" />
...
<h:messages for="select1" styleClass="messages" />
...
<p:commandButton ... update="#(.messages)" />
Is it possible to conditionally update JSF components only when validation succeeds?
I would like to be able to do something like
<p:commandLink process="#form" listener="#{foo}"
update="something somethingElse">
where "something" only gets updated if validation is successful.
Is there any way that can be done or is that just not supported in JSF?
I've been able to rig up kind of a hack with hidden commandLinks but not entirely satisfied:
<p:commandLink process="#form" listener="#{foo}"
update="somethingElse" oncomplete="if (!args.validationFailed) $("#link").click();">
<p:commandLink style="display:none" id="link"
update="something">
I don't think the message suggestion necessarily answers the question asked. Suppose he wants to update something OTHER than a message?
I've not tried this myself, but another approach that might work is to use remotecommand.
<p:remoteCommand id='good' update='goodUpdates'/>
<p:remoteCommand id='bad' update='badUpdate'/>
<p:commandButton oncomplete='if (your-test) good() else bad()'/>
Note, this would necessitate another round-trip to the server and thus performance is a consideration.
The <h:message> (or PrimeFaces' counterpart <p:message>) is intented for this. Or, in your case maybe better, the <h:messages> (or <p:messages>).
public void submit() {
// ...
if (fail) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Fail", null));
}
}
with
<h:messages id="messages" />
<p:commandLink process="#form" action="#{bean.submit}" update="messages something" />
Note that you're as well supposed to use a normal Validator implementation to perform the validation. If it throws a ValidatorException, then the action won't be invoked anyway. Doing validation inside action method is a smell.
So, you can validate input using JSR-303 annotations on the binding bean property:
class Ticket {
#MinAge(18)
Person person;
}
class Person {
#Min(1) #Max(100)
int age;
}
<p:inputText id="age" value="#{bean.ticket.person.age}" />
Here, the property Person.age is validated (between 1..100) with no problem.
The problem is, I want to validate the outer instance (person.age >= 18). But how to make the property bean.ticket.person be known to validation?
I want something like:
<p:inputText id="age" value="#{bean.ticket.person.age}">
<f:validate value="#{bean.ticket.person}" />
</p:inputText>
Or:
<p:inputText id="age" value="#{bean.ticket.person.age}">
<f:validator id="jsr303-validator" value="#{bean.ticket.person}" />
</p:inputText>
The problem is, I can't pass a value to <f:validator />. I want to add extra properties to the validation process, more then only the inputs appeared on the page.
P.S. This is a simplified example, the real application is:
...
<p:inputText id="principalLabel" value="${activeACL.principal.label}" readonly="true" />
<p:commandButton value="Choose..." onclick="choosePrincipalDialog.show()" />
...
<p:commandButton value="Save" action="${bean.saveACL}" oncomplete="editACLDialog.hide()" update="index" />
And activeACL of type ACL_DTO:
class ACL_DTO {
...
#IdRequired
Principal_DTO principal;
}
Here, choosePrincipalDialog's actionListener will implicit change the ${activeACL.principal.id}, which is initially null. IdRequired is a custom constraint which constrains an object's id member property is not null or -1.
Though, I can change to use #NotNull on the id property, and add a hidden-input to enable the validation on the id:
class Principal_DTO {
...
#NotNull
#Min(0)
Long id;
}
...
<h:inputHidden id="principalId" value="${activeACL.principal.id}" />
<h:inputText id="principalLabel" ...
But, in this way I can't reuse the validation messages any more. Give message like "This string should not be null", "This value should not be -1" to the user seems meaningless.
Firstly, I think that you should make sure to use the beans only as a place where you get your input available. After that, you assign the value to the model. In my opinion the beans should not be used directly as a model but rather as a web-form-backing beans (Every web form has different bean).
Secondly, I agree that you you come across scenarios where you have dependency to other data to preform the validation (e.g. If the user selected US as a country, the postcode etc. should not be left empty). You can have a look at here how to implement custom annotation:
Cross field validation with Hibernate Validator (JSR 303)
If you do not use bean validation you can always implement a custom validator class or even define a validation method directly in the bean: http://docs.oracle.com/javaee/6/tutorial/doc/bnavb.html#bnave