89

What's the difference between the following two pieces of code - with regards to listener placement?

<h:selectOneMenu ...>
    <f:selectItems ... />
    <f:ajax listener="#{bean.listener}" />
</h:selectOneMenu>

and

<h:selectOneMenu ... valueChangeListener="#{bean.listener}">
    <f:selectItems ... />
</h:selectOneMenu>
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
Danijel
  • 6,584
  • 16
  • 58
  • 109

2 Answers2

185

The valueChangeListener will only be invoked when the form is submitted and the submitted value is different from the initial value. It's thus not invoked when only the HTML DOM change event is fired. If you would like to submit the form during the HTML DOM change event, then you'd need to add another <f:ajax/> without a listener(!) to the input component. It will cause a form submit which processes only the current component (as in execute="@this").

<h:selectOneMenu value="#{bean.value}" valueChangeListener="#{bean.changeListener}">
    <f:selectItems ... />
    <f:ajax />
</h:selectOneMenu>

When using <f:ajax listener> instead of valueChangeListener, it would by default executed during the HTML DOM change event already. Inside UICommand components and input components representing a checkbox or radiobutton, it would be by default executed during the HTML DOM click event only.

<h:selectOneMenu value="#{bean.value}">
    <f:selectItems ... />
    <f:ajax listener="#{bean.ajaxListener}" />
</h:selectOneMenu>

Another major difference is that the valueChangeListener method is invoked during the end of the PROCESS_VALIDATIONS phase. At that moment, the submitted value is not been updated in the model yet. So you cannot get it by just accessing the bean property which is bound to the input component's value. You need to get it by ValueChangeEvent#getNewValue(). The old value is by the way also available by ValueChangeEvent#getOldValue().

public void changeListener(ValueChangeEvent event) {
    Object oldValue = event.getOldValue();
    Object newValue = event.getNewValue();
    // ...
}

The <f:ajax listener> method is invoked during INVOKE_APPLICATION phase. At that moment, the submitted value is already been updated in the model. You can just get it by directly accessing the bean property which is bound to the input component's value.

private Object value; // +getter+setter.

public void ajaxListener(AjaxBehaviorEvent event) {
    System.out.println(value); // Look, (new) value is already set.
}

Also, if you would need to update another property based on the submitted value, then it would fail when you're using valueChangeListener as the updated property can be overridden by the submitted value during the subsequent UPDATE_MODEL_VALUES phase. That's exactly why you see in old JSF 1.x applications/tutorials/resources that a valueChangeListener is in such construct been used in combination with immediate="true" and FacesContext#renderResponse() to prevent that from happening. After all, using the valueChangeListener to execute business actions has actually always been a hack/workaround.

Summarized: Use the valueChangeListener only if you need to intercept on the actual value change itself. I.e. you're actually interested in both the old and the new value (e.g. to log them).

public void changeListener(ValueChangeEvent event) {
    changeLogger.log(event.getOldValue(), event.getNewValue());
}

Use the <f:ajax listener> only if you need to execute a business action on the newly changed value. I.e. you're actually interested in only the new value (e.g. to populate a second dropdown).

public void ajaxListener(AjaxBehaviorEvent event) {
    selectItemsOfSecondDropdown = populateItBasedOn(selectedValueOfFirstDropdown);
}

If you're actually also interested in the old value while executing a business action, then fall back to valueChangeListener, but queue it to the INVOKE_APPLICATION phase.

public void changeListener(ValueChangeEvent event) {
    if (event.getPhaseId() != PhaseId.INVOKE_APPLICATION) {
        event.setPhaseId(PhaseId.INVOKE_APPLICATION);
        event.queue();
        return;
    }

    Object oldValue = event.getOldValue();
    Object newValue = event.getNewValue();
    System.out.println(newValue.equals(value)); // true
    // ...
}
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • @BalusC, is there a reason not to obtain the _old_ value from the backing object in the setter before setting it to the _new_ value that is passed in? Something like this: `logger.trace( "setting changeTypes from {} to {}", this.changeTypes, changeTypes );`. It seems like you could use the old and new values obtained in that fashion to do _business logic_ directly in the setter as well as simple logging, but I don't know if this would cause side effects... – Lucas Jan 04 '13 at 23:06
  • @BalusC is it possible to also get the changed value through AjaxBehaviorEvent? I have – Paullo Jul 07 '15 at 20:22
  • @Paullo: http://stackoverflow.com/questions/14750185/ajax-for-valuechangelistener/ – BalusC Jul 07 '15 at 20:29
  • Thanks @BalusC But i dont think ValueChangeListener will fit in my case as I have some execute and render values as shown – Paullo Jul 07 '15 at 20:58
  • I indeed found this answer helpfull regarding my question that I posted today http://stackoverflow.com/questions/34926653/java-lang-nullpointerexception-at-view-requestbeanpage2-nouvelledateaction?noredirect=1#comment57586949_34926653, I changed System.out.println("dateValueChanged : "+ date.toString()); with System.out.println("dateValueChanged : "+ event.getNewValue()); – samouray Jan 21 '16 at 15:09
  • @Paullo Store the value progammatically in an instance attribute of the bean before change. At the next change you will have the value. – The Bitman Mar 29 '17 at 11:29
9

for the first fragment (ajax listener attribute):

The "listener" attribute of an ajax tag is a method that is called on the server side every time the ajax function happens on the client side. For instance, you could use this attribute to specify a server side function to call every time the user pressed a key

but the second fragment (valueChangeListener) :

The ValueChangeListener will only be called when the form is submitted, not when the value of the input is changed

*you might like to view this handy answer

Community
  • 1
  • 1
a.u.r
  • 1,153
  • 2
  • 19
  • 32