问题
The JSF component uses binding and backing bean is of View scope. The component have validator and valueChangeListener set. And when component's value is changed partial request is sent to server. And validator and valueChangListener are called many times but not once within request.
How to make them to be called once during request?
If remove binding the methods are called correctly once.
But is it possible to do not remove binding and make listeners be called once?
Sample of used code is next:
<h:inputText id="txt"
validator="#{myBean.validateValue}"
valueChangeListener="#{myBean.valueChanged}"
binding="#{myBean.txtInput}">
<f:ajax event="valueChange"
execute="@this"
render="@this"/>
</h:inputText>
@ViewScoped
@ManagedBean
public class MyBean {
private HtmlInputText txtInput;
public void valueChanged(ValueChangeEvent ve) {
...
}
public void validateValue(FacesContext context, UIComponent component, Object value) {
...
}
public HtmlTextInput getTxtInput() {
return txtInput;
}
public void setTxtInput(HtmlTextInput txtInput) {
this.txtInput = txtInput;
}
}
The same issue takes place for actionListener and commandLink component if it uses binding and backing bean has View scope.
回答1:
The possible solution could be just to override class which is used by UIInput and UICommand to store validators and listeners. The class is javax.faces.component.AttachedObjectListHolder.
This class straightforward add new listener to backing list not checking if the same listener already is there.
Thus the solution is to check if listener exists and not to add it then.
So take javax.faces.component.AttachedObjectListHolder from jsf-api-2.1.<x>-sources.jar and add it to your project to the corresponding package. Replace method add with such one:
void add(T attachedObject) {
boolean addAttachedObject = true;
if (attachedObject instanceof MethodExpressionActionListener
|| attachedObject instanceof MethodExpressionValueChangeListener
|| attachedObject instanceof MethodExpressionValidator) {
if (attachedObjects.size() > 0) {
StateHolder listener = (StateHolder) attachedObject;
FacesContext context = FacesContext.getCurrentInstance();
Object[] state = (Object[]) listener.saveState(context);
Class<? extends StateHolder> listenerClass = listener.getClass();
for (Object tempAttachedObject : attachedObjects) {
if (listenerClass.isAssignableFrom(tempAttachedObject.getClass())) {
Object[] tempState = (Object[]) ((StateHolder) tempAttachedObject).saveState(context);
if (((MethodExpression) state[0]).getExpressionString().equals(((MethodExpression)tempState[0]).getExpressionString())) {
addAttachedObject = false;
break;
}
}
}
}
}
clearInitialState();
if (addAttachedObject) {
attachedObjects.add(attachedObject);
}
}
After that validators, valueChangeListeners and actionListeners will be triggered only once even when component binding and "View", "Session" or "Application" scopes are used.
来源:https://stackoverflow.com/questions/26183172/how-to-make-jsf-validator-valuechangelistener-and-actionlistener-be-called-once