使用绑定和View范围时,如何使JSF验证程序,valueChangeListener和actionListener一次调用,但不能多次调用

问题描述:

JSF组件使用binding,支持bean是View scope.组件已设置validatorvalueChangeListener.并且当组件的值改变时,部分请求被发送到服务器.并且validatorvalueChangListener被多次调用,但在请求中不会被调用一次.

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?

如果删除binding,则方法将被正确调用一次.

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;
}    

}

如果actionListenercommandLink组件使用绑定并且支持bean具有View scope,则会发生相同的问题.

The same issue takes place for actionListener and commandLink component if it uses binding and backing bean has View scope.

可能的解决方案可能只是覆盖UIInputUICommand用于存储validatorslisteners的类.类别为javax.faces.component.AttachedObjectListHolder.

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.

此类直接将新的listener添加到后备列表中,而不检查是否已经存在相同的listener. 因此,解决方案是检查listener是否存在,然后不添加它.

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.

因此,从jsf-api-2.1.<x>-sources.jar中提取javax.faces.component.AttachedObjectListHolder并将其添加到您的项目中,并放入相应的包中.用这样的方法替换方法add:

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);
    }
}

在此验证器之后,即使使用组件绑定和视图",会话"或应用程序"范围,valueChangeListener和actionListener也将仅触发一次.

After that validators, valueChangeListeners and actionListeners will be triggered only once even when component binding and "View", "Session" or "Application" scopes are used.