26

我正在<p:selectOneMenu/>从数据库中填充如下。

<p:selectOneMenu id="cmbCountry" 
                 value="#{bean.country}"
                 required="true"
                 converter="#{countryConverter}">

    <f:selectItem itemLabel="Select" itemValue="#{null}"/>

    <f:selectItems var="country"
                   value="#{bean.countries}"
                   itemLabel="#{country.countryName}"
                   itemValue="#{country}"/>

    <p:ajax update="anotherMenu" listener=/>
</p:selectOneMenu>

<p:message for="cmbCountry"/>

加载此页面时,默认选择的选项是,

<f:selectItem itemLabel="Select" itemValue="#{null}"/>

转换器:

@ManagedBean
@ApplicationScoped
public final class CountryConverter implements Converter {

    @EJB
    private final Service service = null;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        try {
            //Returns the item label of <f:selectItem>
            System.out.println("value = " + value);

            if (!StringUtils.isNotBlank(value)) {
                return null;
            } // Makes no difference, if removed.

            long parsedValue = Long.parseLong(value);

            if (parsedValue <= 0) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "", "Message"));
            }

            Country entity = service.findCountryById(parsedValue);

            if (entity == null) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_WARN, "", "Message"));
            }

            return entity;
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "", "Message"), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return value instanceof Country ? ((Country) value).getCountryId().toString() : null;
    }
}

当从 代表的菜单<f:selectItem>中选择第一个项目并提交表单时,该方法中value获得的是哪个标签- 列表中的第一个项目,这在直觉上是完全不期望的。getAsObject()Select<f:selectItem>

然后,当 的itemValue属性<f:selectItem>设置为空字符串时,它会java.lang.NumberFormatException: For input string: ""getAsObject()方法中抛出,即使该异常被精确捕获并注册为ConverterException

当 the 的return 语句getAsString()

return value instanceof Country?((Country)value).getCountryId().toString():null;

return value instanceof Country?((Country)value).getCountryId().toString():"";

null被一个空字符串替换,但当有问题的对象是时返回一个空字符串null,反过来又会引发另一个问题,如此处所示

如何使此类转换器正常工作?

也尝试过,org.omnifaces.converter.SelectItemsConverter但没有任何区别。

4

6 回答 6

33

When the select item value is null, then JSF won't render <option value>, but only <option>. 因此,浏览器将提交选项的标签。这在HTML 规范中明确规定(强调我的):

value = cdata [CS]

此属性指定控件的初始值。如果未设置此属性,则将初始值设置为 OPTION 元素的内容。

您还可以通过查看 HTTP 流量监视器来确认这一点。您应该看到正在提交的选项标签。

您需要将选择项值设置为空字符串。然后 JSF 将呈现一个<option value="">. 如果您使用的是转换器,那么您实际上应该""在值为 时从转换器返回一个空字符串nullConverter#getAsString()这在javadoc中也有明确规定(强调我的):

getAsString

...

返回:如果值为 null ,则返回零长度字符串,否则为转换结果

因此,如果您<f:selectItem itemValue="#{null}">与这样的转换器结合使用,<option value="">则将呈现 a 并且浏览器将仅提交一个空字符串而不是选项标签。

至于处理提交的空字符串值(或null),您实际上应该让您的转换器将此责任委托给required="true"属性。因此,当传入value的是null或空字符串时,您应该null立即返回。基本上你的实体转换器应该如下实现:

@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
    if (value == null) {
        return ""; // Required by spec.
    }

    if (!(value instanceof SomeEntity)) {
        throw new ConverterException("Value is not a valid instance of SomeEntity.");
    }

    Long id = ((SomeEntity) value).getId();
    return (id != null) ? id.toString() : "";
}

@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
    if (value == null || value.isEmpty()) {
        return null; // Let required="true" do its job on this.
    }

    if (!Utils.isNumber(value)) {
        throw new ConverterException("Value is not a valid ID of SomeEntity.");
    }

    Long id = Long.valueOf(value);
    return someService.find(id);
}

至于你对此的特殊问题,

但是当有问题的对象为空时返回一个空字符串,反过来又会引发另一个问题,如此处所示

正如那边回答的那样,这是 Mojarra 中的一个错误,<o:viewParam>自 OmniFaces 1.8 起就被绕过了。因此,如果您至少升级到 OmniFaces 1.8.3 并使用它<o:viewParam>而不是<f:viewParam>,那么您应该不会再受到此错误的影响。

在这种情况下,OmniFacesSelectItemsConverter应该也能正常工作。它返回一个空字符串null

于 2014-06-20T07:21:34.100 回答
5
  • 如果要避免select 组件的 null 值,最优雅的方法是使用noSelectionOption.

当 时noSelectionOption="true",转换器甚至不会尝试处理该值。

另外,当您将其与您结合使用<p:selectOneMenu required="true">时,当用户尝试选择该选项时,您将收到验证错误。

最后一点,您可以使用该itemDisabled属性向用户明确表示他不能使用此选项。

<p:selectOneMenu id="cmbCountry"
                 value="#{bean.country}"
                 required="true"
                 converter="#{countryConverter}">

    <f:selectItem itemLabel="Select"
                  noSelectionOption="true"
                  itemDisabled="true"/>

    <f:selectItems var="country"
                   value="#{bean.countries}"
                   itemLabel="#{country.countryName}"
                   itemValue="#{country}"/>

    <p:ajax update="anotherMenu" listener=/>
</p:selectOneMenu>

<p:message for="cmbCountry"/>
  • 现在,如果您确实希望能够设置一个空值,您可以“欺骗”转换器返回一个空值,方法是使用

    <f:selectItem itemLabel="Select" itemValue="" />
    

更多阅读在这里这里这里

于 2014-06-26T09:05:04.050 回答
2

你混合了一些东西,我并不完全清楚你想要实现什么,但让我们试试

这显然会导致 java.lang.NumberFormatException 在其转换器中被抛出。

它没有什么明显的。如果 value 为空或 null String,您不检查转换器,您应该检查。在这种情况下,转换器应该返回 null。

为什么它将 Select (itemLabel) 呈现为其值而不是空字符串 (itemValue)?

选择必须有选择的东西。如果您不提供空值,则会选择列表中的第一个元素,这不是您所期望的。

只需修复转换器以使用空/空字符串,并让 JSF 对返回null的值做出反应。首先调用转换,然后是验证。

我希望这能回答你的问题。

于 2013-06-11T21:03:13.657 回答
1

除了不完整之外,这个答案也被弃用了,因为我在这篇文章的时候使用的是 Spring:

我修改了转换器的getAsString()方法以返回一个空字符串而不是返回null,当没有Country找到对象时(除了一些其他更改),

@Controller
@Scope("request")
public final class CountryConverter implements Converter {

    @Autowired
    private final transient Service service = null;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        try {
            long parsedValue = Long.parseLong(value);

            if (parsedValue <= 0) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "", "The id cannot be zero or negative."));
            }

            Country country = service.findCountryById(parsedValue);

            if (country == null) {
                throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_WARN, "", "The supplied id doesn't exist."));
            }

            return country;
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "", "Conversion error : Incorrect id."), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return value instanceof Country ? ((Country) value).getCountryId().toString() : ""; //<--- Returns an empty string, when no Country is found.
    }
}

<f:selectItem>'itemValue接受一个null值如下。

<p:selectOneMenu id="cmbCountry"
                 value="#{stateManagedBean.selectedItem}"
                 required="true">

    <f:selectItem itemLabel="Select" itemValue="#{null}"/>

    <f:selectItems var="country"
                   converter="#{countryConverter}"
                   value="#{stateManagedBean.selectedItems}"
                   itemLabel="#{country.countryName}"
                   itemValue="${country}"/>
</p:selectOneMenu>

<p:message for="cmbCountry"/>

这将生成以下 HTML。

<select id="form:cmbCountry_input" name="form:cmbCountry_input">
    <option value="" selected="selected">Select</option>
    <option value="56">Country1</option>
    <option value="55">Country2</option>
</select>

早些时候,生成的 HTML 看起来像,

<select id="form:cmbCountry_input" name="form:cmbCountry_input">
    <option selected="selected">Select</option>
    <option value="56">Country1</option>
    <option value="55">Country2</option>
</select>

注意第一个<option>没有value属性。

This works as expected bypassing the converter when the first option is selected (even though requireis set to false). 当itemValue更改为 other thannull时,它的行为不可预测(我不明白这一点)。

如果列表中的其他项目设置为非空值并且转换器中接收的项目始终为空字符串(即使选择了另一个选项),则无法选择列表中的其他项目。

Long此外,当在转换器中解析这个空字符串时,抛出ConverterException后导致的NumberFormatException不会报告错误UIViewRoot(至少应该发生这种情况)。可以在服务器控制台上看到完整的异常堆栈跟踪。

如果有人可以对此有所了解,我会接受答案,如果给出的话。

于 2013-07-18T21:45:21.870 回答
-1

这对我完全有用:

<p:selectOneMenu id="cmbCountry"
                 value="#{bean.country}"
                 required="true"
                 converter="#{countryConverter}">

    <f:selectItem itemLabel="Select"/>

    <f:selectItems var="country"
                   value="#{bean.countries}"
                   itemLabel="#{country.countryName}"
                   itemValue="#{country}"/>

    <p:ajax update="anotherMenu" listener=/>
</p:selectOneMenu>

转换器

@Controller
@Scope("request")
public final class CountryConverter implements Converter {

    @Autowired
    private final transient Service service = null;

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.trim().equals("")) {
            return null;
        }
        //....
        // No change
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return value == null ? null : value instanceof Country ? ((Country) value).getCountryId().toString() : null;
        //**** Returns an empty string, when no Country is found ---> wrong should return null, don't care about the rendering.
    }
}
于 2014-06-19T22:32:23.643 回答
-3
public void limparSelecao(AjaxBehaviorEvent evt) {
    Object submittedValue = ((UIInput)evt.getSource()).getSubmittedValue();

    if (submittedValue != null) {
        getPojo().setTipoCaixa(null);
    }
}
<p:selectOneMenu id="tipo"
                 value="#{cadastroCaixaMonitoramento.pojo.tipoCaixa}" 
                 immediate="true"
                 required="true"
                 valueChangeListener="#{cadastroCaixaMonitoramento.selecionarTipoCaixa}">

    <f:selectItem itemLabel="Selecione" itemValue="SELECIONE" noSelectionOption="false"/>

    <f:selectItems value="#{cadastroCaixaMonitoramento.tiposCaixa}" 
                   var="tipo" itemValue="#{tipo}"
                   itemLabel="#{tipo.descricao}" />

    <p:ajax process="tipo"
            update="iten_monitorado"
            event="change" listener="#{cadastroCaixaMonitoramento.limparSelecao}" />
</p:selectOneMenu>
于 2017-04-03T13:34:42.847 回答