【问题标题】:ValueChangeListener for BigDecimal values not called if only scale changed如果仅更改比例,则不调用 BigDecimal 值的 ValueChangeListener
【发布时间】:2017-04-12 08:28:20
【问题描述】:

我有一个 JSF 2.2 应用程序,用户必须在 <h:inputText> 中输入 BigDecimal 值。对于这样的输入字段,valueChangeListener 配置为在输入更改时调用。这是 XHTML 代码:

<h:form id="theForm">
  <h:inputText id="bd" value="#{bean.bd}"
               valueChangeListener="#{bean.bdChangedListener()}"/>
  <h:commandButton id="submit" value="Submit" />
</h:form>

这在大多数情况下都可以正常工作。 bdChangedListener() 方法被调用,当值改变并且提交按钮被按下时。该值已正确提交给模型。

但是,如果我输入1.1 并将其更改为1.10,新值将提交给模型,但valueChangeListener从不调用!调试显示,原因在于javax.faces.component.UIInput#compareValues()。 JavaDoc 来自 this method 说:

如果新值与之前的值不同,则返回 true。 首先通过将 value 传递给 equals 方法来比较两个值 论据前。如果该方法返回 true,则返回 true。如果说 方法返回 false,并且两个参数都实现 java.lang.Comparable,通过传递值来比较两个值 compareTo 方法上的参数先前。如果此方法返回 true 返回 0,否则返回 false。

所以在我看来,这是故意的。但为什么呢?

用户输入发生了变化,并且存在与BigDecimal 的比例相关的应用程序。所以 JSF 不应该只是忽略更改的输入,而是通知我!它用新值更新模型,为什么会跳过 valueChangeListener 方法?

我怎样才能避免这种行为并以干净的方式获得通知? (我知道我可以挂钩到二传手,但这不是我所说的干净的方式!)

有什么想法吗?

进一步阅读和cmets

除了上面我想提到的,我已经阅读过BigDecimal equals() versus compareTo() 之类的问题。我确实理解为什么 BigDecimal 的 equals()compareTo() 表现得像他们一样,我会说这是正确的。 BigDecimal 的行为不是问题,UIInput.compareValues() 是问题。

此外,转换器(如 cmets 中的建议或已删除的答案)也无法挽救我的一天。用户输入已正确转换,我需要 BidDecimal,包括我的应用程序中的确切比例。任何返回 BigDecimal 的转换器都不会改变观察到的行为。

围绕 BigDecimal 的包装类可能会解决我的问题,但不是我认为的干净的解决方案。我真的很想知道为什么 UIInput 的行为方式如此。

【问题讨论】:

  • 抱歉,1.1 和 1.10 是相等的。这只是格式更改而不是值。
  • 数字可能相等,但 BigDecimal 对象不相等。为什么 equals() 否则会返回 false?在某些情况下,实际 BigDecimal 的规模很重要。这不仅与格式有关,而且与计算有关,这可能会根据实际规模给出不同的结果。
  • @MartinHöller 但是是相等的,它是一个ValueChangeListener
  • @EJP 现在我们越来越哲学了 ;-) &lt;h:input&gt;价值是什么? BigDecimal 对象还是它所代表的数字?但我明白了你的意思,这可能确实是观察到的行为的原因。我仍然觉得这很不直观。
  • @Downvoter 请告诉我你为什么不赞成这个问题。否则我无法为未来学习。

标签: jsf


【解决方案1】:

它确实按照指定的方式运行。就是这样。您已经非常正确地确定了javax.faces.component.UIInput#compareValues() 的根本原因。解决方案是让它跳过额外的Comparable#compareTo() 检查。

最好的办法是扩展 HtmlInputText 并相应地覆盖 compareValues()

@FacesComponent(createTag=true)
public class InputBigDecimal extends HtmlInputText {

    @Override
    protected boolean compareValues(Object previous, Object value) {
        return !Objects.equals(previous, value);
    }

}

然后将&lt;h:inputText&gt; 替换为&lt;x:inputBigDecimal&gt;,如下所示。

<... 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>

注意:代码是完整的。由于 JSF 2.2 的 createTag=true,不需要额外的 XML 配置文件。您只会错过 IDE 的 XML 智能感知,但这是另一个问题。

【讨论】:

  • 我理解这个解决方案,是的,它会起作用,但为什么要这样做而不是创建一个自定义的 MyBigDecimal?这样看来,用户界面正在适应模型的行为,而模型应该被“纠正”?
  • @Kukeltje 因为我同意 ValueChangeListener 在这里的行为不直观,并且包装器不是 clean 解决方案。
  • 感谢 BalusC!对于我的问题,这至少是一个非常干净的解决方法。
【解决方案2】:

BalusC 的回答描述了一种在不改变应用程序行为的情况下以干净的方式解决问题的通用方法。另一个可行的解决方法是使用 AJAX-listener 而不是 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>

listener 方法必须有不同的签名:

public void ajaxListener(AjaxBehaviorEvent event)
{
    // Do whatever you like here.
    // The new value is already in the model.
}

请注意,一旦 DOM 更改事件触发(而不是在按下 sumbit 按钮时)就会调用 AJAX 侦听器,并且不会检查实际值是否已更改,就像 valueChangeListener 所做的那样。

有关两种方法差异的详细信息,请参阅 BalusC 对When to use valueChangeListener or f:ajax listener? 的出色回答。

【讨论】:

    猜你喜欢
    • 2016-11-21
    • 2021-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-18
    • 2011-06-23
    • 2022-01-20
    • 1970-01-01
    相关资源
    最近更新 更多