I'm building a form in primefaces and need to run validation on a specific field of all items in a <p:dataList>. Specifically I need to make sure there are only a maximum 3 different values in any number of items in the list.
<p:dataList value="#{myBean.myItems}" var="it"
id="myDataList" rowIndexVar="rowIndex">
...
<p:inputNumber value="#{it.fieldToValidate}">
<f:validator validatorId="myValidator" />
</p:inputNumber>
...
</p:dataList>
And the validator:
#FacesValidator(value = "myValidator")
public class MyValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
if (component == null)
return;
Set<BigDecimal> myValues = new HashSet<BigDecimal>();
//now add all values to the set, but how to get them?
if (myValues.size() > 3) {
throw new ValidatorException(new FacesMessage(
FacesMessage.SEVERITY_ERROR, "To many different values",
"There can be only three"));
}
}
}
I have read Validation across multiple fields in JSF/PrimeFaces and
JSF doesn't support cross-field validation, is there a workaround? and
How validate two password fields by ajax?
but they don't solve my problem because I don't know the contents of my data list beforehand.
I am using JSF-2.1.7, Primefaces-6.0.4 and jdk-1.6, ancient I know but it can't be helped...
Related
Usually the PrimeFaces tag for messages shows the global as well as the field specific messages. What I want to do is to just show the global messages (globalOnly="true") but also show a common message if the validation of any field fails. No matter how many fields fail, just one message like 'Please correct your data'. The field specific errors are already shown next to the input fields so no need to display them twice.
I'm using composite components for each of my inputs (Textbox, Dropdown, Radios, ...). The validation of each field should be done on blur with textboxes and probably on valuechanged for dropdowns and radios.
Now there are two types of validation I want to handle. First is the standard validators brought by JSF itself. required="true" for example, but also validateRegex, validateLength, ...
And then there are values I've to check against another backend. For those I would probably create methods in my bean and call them as listener of my
<p:inputText id="#{cc.attrs.name}"
value="#{cc.attrs.val}"
styleClass="#{cc.attrs.iconClass}"
required="#{cc.attrs.required}"
requiredMessage="#{cc.attrs.requiredMessage}">
<p:ajax event="blur" process="#form" update="outer-wrapper"
listener="#{cc.attrs.someValidationMethod}" />
</p:inputText>
So basically I want to have just one global message if any of the field validations fails. I know could just render an additional box with rendered="#{facesContext.validationFailed}" but I prefer to have a global message. Is there a out-of-the-box setting or does it have to be implemented?
I use a phase listener to do so. This is roughly my implementation (using OmniFaces):
public class ValidationFailedListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void beforePhase(PhaseEvent event) {
if (Faces.isValidationFailed()) {
Messages.addGlobalError("Your validation failed message");
}
}
#Override
public void afterPhase(PhaseEvent event) {
// NOOP
}
}
You should register it in your faces-config.xml:
<lifecycle>
<phase-listener>your.ValidationFailedListener</phase-listener>
</lifecycle>
My actual implementation uses a message bundle to display a localized message.
If you cannot use OmniFaces, here is the relevant code using vanilla JSF:
if (FacesContext.getCurrentInstance().isValidationFailed()) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Your validation failed message",
null);
FacesContext.getCurrentInstance().addMessage(null, message);
}
I'm going through validation in JSF and I see lots of examples of very basic logic. Frankly, I put them in the same category where the HelloWorld examples go. I can't imagine placing error messages in xhtml files, using a separate validation method for each validated field or employing bean validation.
What I want to do, is have a single method on the backing bean that will execute validation for each field, logging error messages driven by keys in i18n property files.
Can this be done? If so, how do we register that method as validating method, how do we obtain submitted field values for evaluation, and how do we register error messages?
<h:inputText id="username" value="#{bean.username}" label="UserName" binding="#{bean.component}"/>
<h:message for="username" />
<h:commandButton value="Submit" action="#{bean.actionMethod}" />
In your bean class,
private UIComponent component;
public UIComponent getComponent() {
return component;
}
public void setComponent(UIComponent component) {
this.component = component;
}
public String actionMethod() {
if (!validate()) {
return null;
}
// do your action method logic
}
private boolean validate() {
FacesContext context = FacesContext.getCurrentInstance();
//do validation for your fields and add to faces messages
FacesMessage msg = new FacesMessage(severity, summary, detail);
context.addMessage(component.getClientId(), msg);
// do for other fields
return status;
}
Refer this to get component client id
How to add a message to a specific component from JSF backing bean
I get javax.faces.FacesException: java.lang.NullPointerException when I type something in the zip code and hit submit with country set to default null value. If I select the country and then type something everything works. I tried SubmittedValue, but it is working the opposite way - with null is working and after this is giving null exception.
#FacesValidator("zipV")
public class ZipValidator implements Validator {
LocaleBean Bean = new LocaleBean();
String language;
private static final String ZIP_PATTERN_BG = "\\d{4}";
private static final String ZIP_PATTERN_US = "\\d{5}";
private static final String ZIP_PATTERN_DEFAULT = "[A-Za-z0-9]*";
private String zip_pattern;
private Pattern pattern;
private Matcher matcher;
private String country;
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
language = Bean.getLanguage();
UIInput Input = (UIInput) component.getAttributes().get("country");
country = Input.getValue().toString();
String zip = (String) value;
if (country == null || country.isEmpty()) {
return;
}
switch (country) {
case "BGR":
zip_pattern = ZIP_PATTERN_BG;
break;
case "USA":
zip_pattern = ZIP_PATTERN_US;
break;
default:
zip_pattern = ZIP_PATTERN_DEFAULT;
break;
}
pattern = Pattern.compile(zip_pattern);
matcher = pattern.matcher(value.toString());
if (!matcher.matches()) {
switch (language) {
case "en": {
FacesMessage msg = new FacesMessage("Invalid zip.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
case "bg": {
FacesMessage msg = new FacesMessage("Невалиден пощенски код.");
msg.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(msg);
}
}
}
}
}
Here's the view:
<h:selectOneMenu id="country" value="#{account.country}" required="true" requiredMessage="#{msg['register.required']}" binding="#{country}">
<f:selectItem itemValue="#{null}" itemLabel="#{msg['register.countryQ']}"/>
<f:selectItems value="#{account.countries}"/>
<f:ajax listener="#{account.loadStates()}" render="state"/>
</h:selectOneMenu>
<h:inputText id="zipcode" required="true" requiredMessage="#{msg['register.required']}" value="#{account.zipcode}">
<f:validator validatorId="zipV"/>
<f:attribute name="country" value="#{country}"/>
</h:inputText>
Here,
country = Input.getValue().toString();
you should not be using toString() at all. You should be casting it:
country = (String) Input.getValue();
Otherwise it will throw NullPointerException if the getValue() returned null. As its javadoc clearly says, a NullPointerException will be thrown among others when you attempt to call an instance method on null (like as you did with toString()).
Please note that this problem is technically unrelated to JSF. It's just basic Java. The java.lang package of the exception is a very good hint in this. If you got an exception of javax.faces (or javax.el) package, then we can talk about a true JSF (or EL) problem.
See also:
jsf validate two fields in one time
JSF doesn't support cross-field validation, is there a workaround?
Unrelated to the concrete problem, I'd really respect Java naming conventions. Variable names start with lowercase. Use input instead of Input. Also, your manual control of localization is strange. What if you ever need to support 10 languages? Do you expand the switches over all place? Make use of JSF builtin localization facilities with <resource-bundle> and ResourceBundle#getBundle().
hi i wrote a custom a validator which gets the system name and compare it against the id in database, now i wanna apply a check if this value is exactly the same, user must be allowed to click the button and move on else some error message should be displayed. and i am really confused how to call the validator() on through ajax.
my view page code is
<h:commandButton action="sample?faces-redirect=true" value="submit">
<f:ajax execute="#{csample.UserValidator}" render="#form" >
<h:inputText name="idtext" value="#{csampleBean.id}" />
</f:ajax>
</h:commandButton>
and my custom validator
public void UserValidator(FacesContext context, UIComponent toValidate, Object value)
throws UnknownHostException, ValidatorException, SQLException, NamingException
{
java.net.InetAddress localMachine = java.net.InetAddress.getLocalHost();
String machine= localMachine.getHostName();
String query = "select * from USER_ where USER_ID = '"+machine+"'";
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();
Statement stat = conn.createStatement();
//get customer data from database
ResultSet result = stat.executeQuery(query);
if (query==machine)
// what to do here
conn.close();
need some guidance
You need to create a class implementing the Validator interface. On validation fail, just throw a ValidatorException with a FacesMessage. JSF will then take care that the FacesMessage ends up in the right <h:message> associated with the input component.
You can register the custom validator to JSF by annotating it with #FacesValidator with therein the validator ID. You can reference it in <h:inputXxx validator> or <f:validator validatorId>.
Here's a kickoff example:
#FacesValidator("userValidator")
public class UserValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
// ...
if (!valid) {
String message = "Sorry, validation has failed because [...]. Please try again.";
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null));
}
}
}
Which is been used as follows (note: <h:inputText> does not have name attribute! instead use id; also note that your initial code snippet has some nesting which isn't making any sense):
<h:inputText id="idtext" value="#{csampleBean.id}" validator="userValidator">
<f:ajax render="idtextMessage" />
</h:inputText>
<h:message id="idtextMessage" for="idtext" />
<h:commandButton action="sample?faces-redirect=true" value="submit" />
See also:
How to perform validation in JSF, how to create a custom validator in JSF
Unrelated to the concrete problem, your JDBC code is leaking DB resources. Please fix that as well.
I have a simple request scoped entity / pojo which has a Enum and a String as properties.
public Enum Type
{
None,
Email,
Fax;
}
#ManagedBean(name = "testEntity")
#RequestScoped
public class TestEntity
{
private Type type; //Default = None
private String address;
//getter and setter
}
This Enum has a field 'Email' which identifies a e-mail address with a related address.
In JSF I now want to enable/disable a validator of a address InputText field regarding the currently selected type in a SelectOneMenu.
<h:form id="formId">
<p:selectOneMenu id="type" value="#{testEntity.type}>
<p:ajax event="change" update=":formId:address"/>
<f:selectItem itemLabel="E-mail" itemValue="Email"/>
<f:selectItem itemLabel="Fax" itemValue="Fax"/>
</p:selectOneMenu>
<p:inputText id="address" value="#{testEntity.address}">
<f:validator validatorId="emailValidator" disabled="#{testEntity.type != 'Email'}"/>
</p:inputText>
<!-- button to call bean method with testEntity as param -->
</h:form>
It is not working the validator is never active but the ajax call is working since I can see the change value in other fields.
That's unfortunately not possible. The <f:xxx> tags are taghandlers (not UI components) which run during view build time, not during view render time. So if it's disabled during building of the view, it'll always be disabled until the view is recreated (e.g. by new request or a non-null navigation).
You'd need to have a "global" validator which delegates further to the desired validator based on the type attribute.
E.g.
<p:inputText ... validator="#{testEntity.validateAddress}" />
with
public void validateAddress(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (type == Email) {
context.getApplication().createValidator("emailValidator").validate(context, component, value);
}
}
Update OmniFaces has recently added a new <o:validator> tag which should solve exactly this problem as follows:
<o:validator validatorId="emailValidator" disabled="#{testEntity.type != 'Email'}"/>
See the showcase example here.
Maybe someone is interested in how I solved it thanks to BalusC help.
Pass type component clientId to custom converter.
<f:attribute name="typeComponentId" value=":formId:type"/>
Validator:
public class TestEntity implements Validator
{
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
{
final String typeComponentId = (String)component.getAttributes().get("typeComponentId");
final UIInput compType = (UIInput)context.getViewRoot().findComponent(typeComponentId);
if(compType != null)
{
final Type type = (Type)compType.getValue();
if(type == Type.Email)
new EmailValidator().validate(context, component, value);
}
}
}
Edit:
Not working inside a ui:repeat component such as p:datatable.