I'm trying to do a conditional styling on a JSF application and trying to implement it as follows
<h:inputText id="contact_phone2" value="#{jobs_Builder.selected.contactPhone2}" title="#{lbl.ContactPhone2}" binding="#{phoneNumberValidator.myComponent}" style="#{phoneNumberValidator.valid ? 'border-color:black;' : 'border-color:red;'}"/>
In the PhoneNumberValidator.java class I have the bellow code
private UIComponent myComponent;
public UIComponent getMyComponent() {
return myComponent;
}
public void setMyComponent(UIComponent myComponent) {
this.myComponent = myComponent;
}
public boolean isValid(){
UIInput input = (UIInput) myComponent;
String value = input.toString();
return value != null && value.length() > 2;
}
Note that the validation logic is dummy one and not the correct one.
When I debug this, the validation code is hit only when the page is initially loading, so this is not working in runtime. The expected behavior is to have this in the runtime so whenever user types something, the validation logic should trigger, and based on that the styling should be changed.
Related
I cannot see why the question is duplicate. If I debug the code then - when the button is clicked - no new value of projectSelected is being detected. Even the hashCode is the same. The equals method of the ProjectEntity only contains the id which is the same since it comes from the database and is not changed anywhere. Null values don't exist in the selection.
There was, however, too much code to reproduce the problem. I removed unnecessary code and the problem still persists.
Original question: In the following form with 3 <p:selectOneMenu> -fields if the submit button is clicked a valueChangeEvent is fired for the projectSelector field although it hasn't changed. Why is that? Like that the actual action behind the button is never called. I would expect a valueChangeEvent to be fired only in case the project changes.
Update: Trying to find the cause I replaced the ProjectEntity with String and then it worked. So I thought it must be the equals method of ProjectEntity but that only compares the id. I debugged further and found out that the selected value is being compared with a ProjectEntity with all fields set to null which gives a false and hence a valueChangeEvent. So the question is why is there a ProjectEntity with all fields set to null? I debugged into UIInput.compareValues which has that "null"-ProjectEntity being the previous value. That is being returned by UIOuput.getLocalValue. Where does it come from?
Update2: Even when using the equals and hashCode from selectOneMenu shows after submit always the last item in the list as selected item the behaviour does not change. I created an ear file readily to be deployed to e.g. a wildfly and would appreciate any help since I am stuck on this question.
<h:form>
<p:outputLabel value="#{msgs.timeProject}"/>
<p:selectOneMenu value="#{timeBean.model.projectSelected}"
converter="projectConverter"
onchange="submit()"
valueChangeListener="#{timeBean.projectChanged}"
immediate="true"
required="true">
<f:selectItems value="#{timeBean.model.allProjects}"
var="singleProject"
itemValue="#{singleProject}"
itemLabel="#{singleProject.name}"/>
</p:selectOneMenu>
<p:commandButton value="#{msgs.send}"
action="#{timeBean.myAction}"
ajax="false"/>
<p:outputLabel value="#{timeBean.model.resultValue}"
rendered="#{not empty timeBean.model.resultValue}"/>
</h:form>
The converter
#FacesConverter(value = "projectConverter")
public class ProjectConverter implements Converter {
#Inject
private ProjectService projectService;
#Override
public Object getAsObject(final FacesContext facesContext, final UIComponent uiComponent, final String projectName) {
if (StringUtils.isEmpty(projectName)) {
return null;
}
final List<ProjectEntity> projects = projectService.findAll();
for (ProjectEntity project : projects) {
if (StringUtils.equals(projectName, project.getName())) {
return project;
}
}
return null;
}
#Override
public String getAsString(final FacesContext facesContext, final UIComponent uiComponent, final Object value) {
if (value == null) {
return null;
}
if (value instanceof ProjectEntity) {
return ((ProjectEntity) value).getName();
}
return "???projectName???";
}
}
The equals-method of the ProjectEntity
#Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final ProjectEntity that = (ProjectEntity) o;
return id != null ? id.equals(that.id) : that.id == null;
}
And the change listener inside the timeBean
public void projectChanged(final ValueChangeEvent event) {
final ProjectEntity projectSelected = (ProjectEntity) event.getNewValue();
model.setProjectSelected(projectSelected);
final FacesContext context = FacesContext.getCurrentInstance();
context.renderResponse();
}
The TimeModel
public class TimeModel {
private ProjectEntity projectSelected;
private List<ProjectEntity> allProjects;
private String resultValue;
... getters and setters ...
I'll guess, that the problem resides inside the ProjectConverter class, cause it may run into troubles to assign a valid projectService instance. Maybe you remove the injection and try to compute the value programatically in the getAsObject, getAsString methods by explicit cdi-finders.
I remember to run in a similar situation, when i was injecting in a ServletFilter.
The basic converter (merely a prototype) converting to and fro between String and java.time.LocalDateTime.
#FacesConverter("localDateTimeConverter")
public class LocalDateTimeConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return ZonedDateTime.parse(submittedValue, DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime());
} catch (IllegalArgumentException | DateTimeException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (!(modelValue instanceof LocalDateTime)) {
throw new ConverterException("Message");
}
Locale locale = context.getViewRoot().getLocale();
return DateTimeFormatter.ofPattern(pattern, locale).withZone(ZoneId).format(ZonedDateTime.of((LocalDateTime) modelValue, ZoneOffset.UTC));
}
}
The date times to be submitted to the database should be based on Locale.ENGLISH. Therefore, it is constant / static in getAsObject().
The date times to be retrieved from the database i.e. to be presented to end-users are based on a selected locale of the user's choice. Therefore, Locale is dynamic in getAsString().
Date time is submitted using a <p:calendar> which is not localized to avoid troublesome.
This will work as expected unless a <p:calendar> component being submitted itself or some other components on the same form being submitted fail during conversion / validation in which case, the calendar component will be pre-filled with a localized date-time which will then fail to be converted in getAsObject() in all subsequent attempts to submit the form unless the localized date-time in the given <p:calendar> is reset to the default locale manually.
The following will pass on the very first attempt to submit the form as there is no conversion / validation violation.
If there is however, a conversion error in one of the fields as follows,
then both the dates in the calendar components will be changed according to the selected locale (hi_IN), since there is a conversion error in one of the fields which will obviously fail to be converted in getAsObject() in subsequent attempts, if the form holding the components is attempted to be submitted after fixing the conversion error by providing the field a correct value.
Any suggestion?
In the converter's getAsString() you're using the view's locale for formatting the date.
Locale locale = context.getViewRoot().getLocale();
In order to use a component-specific locale, it has to be provided as component attribute. Here's an example provided that <locale-config><default-locale> in faces-config.xml is set to en.
<p:calendar ... locale="#{facesContext.application.defaultLocale}">
In the converter you can extract it as below:
Locale locale = (Locale) component.getAttributes().get("locale");
The base converter example of your converter has in the meanwhile been altered to take this properly into account: How to use java.time.ZonedDateTime / LocalDateTime in p:calendar.
I have a problem when dynamically instantiating a PF 3.4.2 AutoComplete component.
The component intially renders ok, its value is refreshed on partial processing
but the suggestions are never displayed.
I am instantiating this control the following way :
AutoComplete ac = (AutoComplete) context.getApplication().createComponent(AutoComplete.COMPONENT_TYPE);
final String varName = "p";
ValueExpression ve = JSFUtils.createValueExpression("#{minContext.selected.sen}"), Sen.Type);
ac.setValueExpression("value", ve);
ac.setForceSelection(true);
ac.setVar(varName);
ValueExpression itemLabel = JSFUtils.createValueExpression("#{sc:senLibelle(p)}"), String.class);
ac.setValueExpression("itemLabel", itemLabel);
ValueExpression itemValue = JSFUtils.createValueExpression("#{" + varName + "}");
ac.setValueExpression("itemValue", itemValue);
MethodExpression completeMethod = JSFUtils.createMethodExpression("#{senUtils.completeAllSens}", List.class,new Class[]{String.class});
ac.setCompleteMethod(completeMethod);
then adding it to parent control using
getChildrens().add(ac);
The parent component is a derivation of PF PanelGrid. I use this approach successfully to generate various edition panels and it works like a charm. But I can not figure why it does not with autoComplete.
The parent control looks like :
#FacesComponent(SenatDataTableEntryDetail.SENAT_COMPONENT_TYPE)
public class SenatDataTableEntryDetail extends PanelGrid {
/** Leaving renderer unchanged, so that PF renderer for PanelGrid is used.
*/
public static final String SENAT_COMPONENT_FAMILY = "fr.senat.faces.components";
public static final String SENAT_COMPONENT_TYPE = SENAT_COMPONENT_FAMILY + ".SenatDataTableEntryDetail";
private enum PropertyKeys { mapper, bean; }
#Override
public void encodeBegin(FacesContext context) throws IOException {
super.encodeBegin(context);
addDynamicChildren(context);
}
#Override
public boolean getRendersChildren()
{
return true;
}
...
private Boolean isInitialized() {
return (Boolean)getStateHelper().eval(SENAT_INITIALIZED,false);
}
private void setInitialized(Boolean param) {
getStateHelper().put(SENAT_INITIALIZED, param);
}
private void addDynamicChildren(FacesContext context) throws IOException {
if(isInitialized()) {
return;
}
setInitialized(true);
/* components are instiated and added as children only once */
}
}
It just adds children to the panel grid.
The other aspects of custom component declaration (in taglib and so on) are ok.
The problem doest not seem to be in EL expressions, completeMethod definition, etc. If I include in my test xhtml page an instanciation of the p:autoComplete with the very same parameters, it just works as expected :
<p:autoComplete value="#{minContext.selected.sen}" forceSelection="true"
var="p" itemLabel="#{sc:senLibelle(p)}" itemValue="#{p}"
completeMethod="#{senUtils.completeAllSens}"/>
I noticed that the PF AutoComplete component is a bit special as it renders differently
when a query is detected. See AutoCompleteRenderer source code in http://primefaces.googlecode.com/files/primefaces-3.4.2.zip .
In the "dynamically instantiated" case, the decode method of this component is not called. I failed to find why those last days, but did not succeed.
I look forward for your suggestions on what to check to correct this annoying "bug".
So, the problem was in id generation (see the two comments).
The beginning of component instantiation becomes :
AutoComplete ac = (AutoComplete) context.getApplication().createComponent(AutoComplete.COMPONENT_TYPE);
ac.setParent(this);
ac.setId(...some application specific unique id generation...);
final String varName = "p";
This way, the naming container is properly taken in account on client id generation.
I have a jsf composite component implemented from two p:calendar components.
The idea is when the first calendar is selected, the value of the second calendar need to be reset. There is a problem when the validation takes place, and the reset of the second calendar is not performed.
After reading posts I decided to use EditableValueHolder in my validator.
I have custom validator: in which I added the following code:
#Override
public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException {
//....
resetValues(fc);
}
public void resetValues(FacesContext fc) {
PartialViewContext partialViewContext = fc.getPartialViewContext();
Collection<String> renderIds = partialViewContext.getRenderIds();
UIComponent input;
UIViewRoot viewRoot = fc.getViewRoot();
for (String renderId : renderIds) {
input = viewRoot.findComponent(renderId);
if (input.isRendered() && input instanceof EditableValueHolder) {
EditableValueHolder editableValueHolder = (EditableValueHolder) input;
editableValueHolder.setSubmittedValue(null);
editableValueHolder.setValue(null);
editableValueHolder.setValid(true);
editableValueHolder.setLocalValueSet(false);
}
}
}
After debug I can see that each code line is passed, but nothing is happening on jsf side.
This is not the right moment to reset the values. They will be overridden anyway for the current component after the validate() method leaves and also for the second calendar once it get validated. You need to perform the reset somewhere after the update model values phase, preferably before the invoke action phase, so that you've chance to change the model value in an action(listener) method. You could use an ActionListener or a PhaseListener for this.
By the way, the JSF utility library OmniFaces has a reuseable solution for this in flavor of ResetInputAjaxActionListener.
I have an issue with the attributes values of a validator component.
Apparently the validator is created when I first visit a page.
Please see my code below:
<h:inputText value="#{qsetting.value}" rendered="#{qsetting.dataType=='Double'}">
<mw:validateRange min="#{qsetting.minValue}" max="#{qsetting.maxValue}" />
</h:inputText>
The inputText component is rerendered through ajax but apparently, including the value that is displayed.
Unfortunately, the qsetting.minValue and qsetting.maxValue are not refreshed, causing my validator to not work correctly.
Is there a possibility to refresh the validator, to make sure it re-retrieves its attributes or to just create a new instance of the validator?
The validator class itself is currently implementing "Validator, Serializable".
Also, I'm using jsf1.2 with facelets...
Thanks,
Steven
I've hit this problem in a non-ajax environment a few times over the years, and hit it again today. The addition of Ajax doesn't really change anything since a validator attribute is never evaluated again once the page is initially built, ajax or otherwise.
The only solution I've come up with is to set the validator attribute to a validator expression, then evaluate that expression inside the validate method.
One other issue I hit (also with JSF 1.2 and Facelets) is that not all EL variables worked. I had to use a static managed bean as the root of my expression to access the value. A facelet ui:param value as a root would not work. I haven't tested to see what else may not correctly evaluate. This could be due to another bug in the design of JSF itself. See http://myfaces.apache.org/core12/myfaces-api/apidocs/javax/faces/context/FacesContext.html#getELContext%28%29.
For example, instead of:
max="#{qsetting.maxValue}"
use
maxExpression="qsetting.maxValue"
Then
public String getMax(FacesContext context) {
Application app = context.getApplication();
ExpressionFactory exprFactory = app.getExpressionFactory();
ValueExpression ve = exprFactory.createValueExpression(context.getELContext(),
"#{" + getMaxExpression() + "}",
String.class);
Object result = ve.getValue(context.getELContext());
return (String)result;
}
public String getMaxExpression() {
return this.maxExpression;
}
public void setMaxExpression(String maxExpression) {
this.maxExpression = maxExpression;
}
//// StateHolder
public boolean isTransient() {
return isTransient;
}
public void setTransient(boolean newTransientValue) {
isTransient = newTransientValue;
}
public Object saveState(FacesContext context) {
Object[] state = new Object[1];
state[0] = maxExpression;
return state;
}
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[]) state;
maxExpression = (String) values[0];
}
UPDATE 2012-09-19:
After investigating how MyFaces Commons solves this problem, the better solution is to change the rules Facelets uses to evaluate validator and converter attribute expressions.
It basically comes down to adding a new validator or converter MetaRule which, when applied, checks to see if the attribute value is non-literal. If it is non-literal, call a special method on your validator or converter which passes in the value expression rather than the current value.
http://svn.apache.org/viewvc/myfaces/commons/trunk/myfaces-commons-validators/src/main/java/org/apache/myfaces/commons/validator/_ValidatorRule.java?view=markup
The validator at that point needs to store the value expression as state and evaluate it when needed. MyFaces commons provides all of the complicated infrastructure to make this happen generically, but you could dump all of that and write a simple custom rule and directly manage the ValueExpression yourself, similar to what I originally posted.