I have a JSF 2.2 application where the user has to input BigDecimal
values in an <h:inputText>
. For such an input-field, a valueChangeListener
is configured to be called on input changes. Here is the XHTML code:
<h:form id="theForm">
<h:inputText id="bd" value="#{bean.bd}"
valueChangeListener="#{bean.bdChangedListener()}"/>
<h:commandButton id="submit" value="Submit" />
</h:form>
This works fine in most cases. The bdChangedListener()
method is called, when the value changed and the submit-button is pressed. The value is correctly committed to the model.
However, if I entered 1.1
and change it to 1.10
, the new value is committed to the model, but the valueChangeListener
is never called! Debuging showed, that the reason for this is in javax.faces.component.UIInput#compareValues()
. The JavaDoc from
this method says:
Return true if the new value is different from the previous value. First compare the two values by passing value to the equals method on argument previous. If that method returns true, return true. If that method returns false, and both arguments implement java.lang.Comparable, compare the two values by passing value to the compareTo method on argument previous. Return true if this method returns 0, false otherwise.
So it seems to me, this is intentionally. But why?
The user-input changed, and there are applications, where the scale of a BigDecimal
is relevant. So JSF should not just ignore the changed input but notify me! It updates the model with the new value, why would it skip the valueChangeListener-method?
How could I avoid this behavior and get notified in a clean way? (I know I could hook into the setter, but that's not what I call a clean way!)
Any ideas?
In addition to the above I want to mention, that I've already read questions like BigDecimal equals() versus compareTo(). I do understand why BigDecimal's equals()
and compareTo()
behave like they do and I'd say it is correct. The behavior of BigDecimal is not the problem, the UIInput.compareValues()
is the problem.
Also a converter (as suggested in comments or already deleted answers) won't save my day. The user input is correctly converted and I need the BidDecimal including the exact scale in my application. Any converter returning a BigDecimal won't change the observed behavior.
A wrapper class around BigDecimal could possibly solve my problem, but is not what I consider a clean solution. I really want to know why UIInput behaves the way it does.
It's indeed behaving as specified. It is what it is. You have very correctly nailed down the root cause to javax.faces.component.UIInput#compareValues()
. The solution would be to let it skip the additional Comparable#compareTo()
check.
Your best bet is to extend the HtmlInputText
and override the compareValues()
accordingly.
@FacesComponent(createTag=true)
public class InputBigDecimal extends HtmlInputText {
@Override
protected boolean compareValues(Object previous, Object value) {
return !Objects.equals(previous, value);
}
}
Then just replace <h:inputText>
by <x:inputBigDecimal>
as below.
<... xmlns:x="http://xmlns.jcp.org/jsf/component">
...
<h:form id="theForm">
<x:inputBigDecimal id="bd" value="#{bean.bd}"
valueChangeListener="#{bean.bdChangedListener()}" />
<h:commandButton id="submit" value="Submit" />
</h:form>
Note: code is complete as-is. No additional XML config files are necessary thanks to JSF 2.2's createTag=true
. You'll only miss IDE's XML intellisense on this, but that's a different problem.
BalusC's answer describes a generic approach to fix the problem in a clean way without changing application behavior. Antoher possible workaround that works, but slightly changes your application behavior, is to use an AJAX-listener
instead of the valueChangeListener
:
<h:form id="theForm">
<h:inputText id="bd" value="#{bean.bd}">
<f:ajax listener="#{bean.ajaxListener}"/>
</h:inputText>
<h:commandButton id="submit" value="Submit" />
</h:form>
The listener
method must have a different signature:
public void ajaxListener(AjaxBehaviorEvent event)
{
// Do whatever you like here.
// The new value is already in the model.
}
Note, that the AJAX-listener is called as soon as the DOM change event fires (not when the sumbit button is pressed) and does not check if the actual value changed, as valueChangeListener
does.
Details about differences in the two approaches can be found in BalusC's excellent answer to When to use valueChangeListener or f:ajax listener?.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With