<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
Related
I am using JSF 2 and primefaces 3.5. I have an inputText which must be a number between Long.MIN_VALUE and Long.MAX_VALUE.
<p:inputText id="startRange" value="#{attributeBean.attribute.startRange}">
<f:convertNumber />
<f:validateLongRange minimum="#{attributeBean.minimumValue}"
maximum="#{attributeBean.maximumValue}"/>
</p:inputText>
In attributeBean:
public Long getMinimumValue(){
return Long.MIN_VALUE;
}
public Long getMaximumValue(){
return Long.MAX_VALUE;
}
When I enter a huge number like 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 validation message doesn't appear. If come back to this form in an inputText field is 9,223,372,036,854,775,807 value. Can I get a validation message?
Well this is do to the f:converter. The converter tries to convert 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
To a long. The max of a long = 9,223,372,036,854,775,807 so the converter works well :-)
But this behavior is not what you want i guess. So you can validate it yourself on this way. Add a actionListener to your command button: actionListener="#{attributeBean.validateLong}" and remove this:
<f:validateLongRange minimum="#{attributeBean.minimumValue}"
maximum="#{attributeBean.maximumValue}"/>
and add a method like this in your bean:
public void validateLong()
{
if(startRange.compareTo(Long.MIN_VALUE) > 0 && startRange.compareTo(Long.MAX_VALUE) < 0)
{
//Do bussiness logic
}
else
{
//Throw message
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Not good!"));
}
}
Keep in mind that your range with above check is between: -9223372036854775807 and 9223372036854775806. But i don't know if thats a problem.
This problem is solved with help <f:converter converterId="javax.faces.Long"/>
the problem
I'm trying to work with form validation in jsf 1.2. I have a form with rows of two input text fields.
I enter two rows of data, with one bad cell, like this:
| :) | :/ |
| :) | :) |
The validator is called once for each row, but checks both fields.
Each UIInput that fails validation is added to a list of failed UIComponents.
The method for the submit action finally gets to run.
First it restores any saved styles.
Then it loops over the failed UIComponents.
Inside the loop, it saves the current style, then sets the style to "badInput".
But when the page loads, both end-cells have the "badInput" style:
| :) | :/ |
| :) | :/ |
my code
This is my validator, a method on the managed bean that handles this page:
public void validateTime(FacesContext context, UIComponent component, Object value)
{
UIInput out = (UIInput) component.findComponent("out");
for (UIComponent uic : Arrays.asList(component, out))
{
String time = (String) ((UIInput)uic).getSubmittedValue();
if (!StringToTime.isValid(time))
{
// mark that we found invalid times
validTimes = false;
// save the failed component
// the click method will change the style during the render phase
failedUics.add(uic); // List<UIComponent>
badComps.put(uic.getClientId(context), uic); // Map<String, UIComponent>
}
}
}
And here's the table of input fields:
<h:dataTable binding="#{entryHandler.tableAttends}" value="#{entryHandler.attends}" var="range">
<h:column>
<div>
<h:outputLabel>
<h:outputText value="In: " />
<h:inputText value="#{range.start}" id="in" validator="#{entryHandler.validateTime}" />
</h:outputLabel>
<h:outputLabel>
<h:outputText value="Out: " />
<h:inputText value="#{range.end}" id="out" />
</h:outputLabel>
<h:commandLink action="#{entryHandler.delAttend}" value="X" styleClass="removeTime" />
</div>
</h:column>
</h:dataTable>
I've tried applying the bad input style these two ways:
for (UIComponent target : failedUics)
{
log.debug("target client id: " + target.getClientId(context));
Map<String, Object> attr = target.getAttributes();
// save the style before changing it
String style = (String) attr.get("styleClass");
originalStyle.put(target.getClientId(context), style);
// add the badInput css class
if (style == null) style = "";
attr.put("styleClass", "badInput " + style);
}
failedUics = new ArrayList<UIComponent>();
and the second:
UIComponent root = context.getViewRoot();
for (String clientId : badComps.keySet())
{
root.invokeOnComponent(context, clientId, new BadInputCallback(originalStyle));
}
badComps = new HashMap<String, UIComponent>();
where this is the callback function:
private static class BadInputCallback implements ContextCallback
{
private final Map<String, String> originalStyle;
public BadInputCallback(Map<String, String> originalStyle)
{
this.originalStyle = originalStyle;
}
#Override
public void invokeContextCallback(FacesContext context, UIComponent target)
{
Map<String, Object> attr = uic.getAttributes();
// save the style before changing it
String style = (String) attr.get("styleClass");
originalStyle.put(target.getClientId(context), style);
// add the badInput css class
if (style == null) style = "";
attr.put("styleClass", "badInput " + style);
}
}
Your concrete problem is caused because there is physically only one input component in the component tree, whose state changes whenever the parent UIData component iterates over every item of the model. When you want to set the styleClass dynamically, you basically need to let it depend on the currently iterated item, like so:
<h:dataTable ... var="item">
<h:column>
<h:inputText ... styleClass="#{item.bad ? 'badInput' : ''}" />
</h:column>
</h:dataTable>
Or when you're already on JSF 2.x, then check UIInput#isValid() instead whereby the UIInput is referenced via implicit EL variable #{component}, like so:
<h:dataTable ... var="item">
<h:column>
<h:inputText ... styleClass="#{component.valid ? '' : 'badInput'}" />
</h:column>
</h:dataTable>
This problem is already identified before and taken into account in among others the JSF 1.2 targeted SetFocusListener phase listener on The BalusC Code and the JSF 2.0 <o:highlight> component of JSF utility library OmniFaces.
Both have under the covers the same approach: they collect the client IDs of all invalidated input components and pass them as an array to JavaScript code which in turn sets the desired class name via HTML DOM.
See also:
What exactly is #{component} in EL?
how to set ui-state-error class to h:selectOneMenu on validation error
Styling input component after validation failed
Eclipse errors on #{component.valid}: "valid cannot be resolved as a member of component”
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" />
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.
In my JSF2 application, I have "Clear" button, which is supposed to clear all the fields. However, it doesn't always work.
My page fragment:
<h:form id="bi">
<h:inputText value="#{bean.entity.firstname}" />
<h:inputText value="#{bean.entity.surname}" />
<h:commandButton value="Clear" immediate="true" action="#{bean.clear}">
<f:ajax render="bi" />
</h:commandButton>
<h:commandButton value="Submit" action="#{bean.submit}" />
</h:form>
And clear() method in my bean:
public void clear() {
entity = new Entity();
}
If I enter values in the fields, and click "Clear", everything is cleared as expected. However, consider such scenario:
1. Enter value only in one field (both are required by JSR303 annotations on entity).
2. Click "Submit". Error message appears.
3. Click "Clear".
Entered value remains. Why is it not cleared?
Moreover, if I clear it by hand, and click "Clear", it returns to the field. I checked that it comes to the browser in partial response after clicking "Clear" button. I suspect it has something to do with view state.
Moreover, if I add validator="#{bean.validate}" to the field, it enter this validation. Even if button has immediate="true" attribute. Why? Shouldn't immediate button ommit validation?
You've run into a more or less well-known issue regarding updating components for which validation has already happened.
This post is rather old, but still relevant: http://ishabalov.blogspot.com/2007/08/sad-story-about-uiinput.html
There is a community created solution for A4J in JSF 1.2 posted here: http://community.jboss.org/thread/8446?start=15&tstart=0
But unfortunately, this doesn't work directly in JSF 2.0 and in your case it wouldn't work at all since it's A4J specific. Nevertheless it might be a source of inspiration.
Basically you need to walk the component tree and clear its state. The neatest thing is to clear exactly the state of the components that you are going to re-render. But you might take the brute-force approach and just clear all if your particular application or page can tolerate that.
I wound up having to avoid submit or action to get the form to clear properly. I used actionListener with a void bean method instead.
But then I faced the problem of conditionally needing navigation which is usually done with a String method from action. I used ExternalContext.redirect() to accomplish that which I learned from the following:
JSF PostConstruct Exception Handling - Redirect
JSF navigation redirect to previous page
my page code:
<p:commandButton value="Login" update=":loginForm"
actionListener="#{loginBean.login}"/>
my bean code:
public void login() {
RtsLDAPAD laLdap = new RtsLDAPAD();
boolean lbAuthenticated = false;
try
{
lbAuthenticated = laLdap.login(userName, password);
System.out.println(
"The Result is " + lbAuthenticated + " for " + userName);
}
catch (Exception aeRTSEx)
{
aeRTSEx.printStackTrace();
}
if (lbAuthenticated) {
try {
FacesContext.getCurrentInstance().getExternalContext().redirect("taskform.jsf");
} catch (IOException e) {
e.printStackTrace();
}
} else {
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.addMessage(null,
new FacesMessage("Login failed for " + userName + "."));
UIViewRoot uiViewRoot = facesContext.getViewRoot();
HtmlInputText inputText = null;
Password pwd = null;
inputText = (HtmlInputText) uiViewRoot.findComponent("loginForm:username");
inputText.setSubmittedValue(null);
inputText.setValue(null);
inputText.setLocalValueSet(false);
inputText.setValid(true);
pwd = (Password) uiViewRoot.findComponent("loginForm:password");
pwd.setSubmittedValue(null);
pwd.setValue(null);
pwd.setLocalValueSet(false);
pwd.setValid(true);
userName = null;
password = null;
}
}