在p:selectOneMenu中使用“请选择”f:selectItem,其中包含null /空值

时间:2013-06-11 19:49:37

标签: jsf primefaces converter jsf-2.2 selectonemenu

我正在从数据库填充<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>的{​​{1}}属性设置为空字符串时,它会在java.lang.NumberFormatException: For input string: ""方法中抛出getAsObject(),即使异常被捕获并注册为ConverterException

return的{​​{1}}语句从

更改时,这似乎有效
getAsString()

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

return value instanceof Country?((Country)value).getCountryId().toString():""; 被空字符串替换,但当有问题的对象为null时返回空字符串,反过来又会出现另一个问题,如here所示。

如何使这些转换器正常工作?

也尝试使用null,但没有区别。

6 个答案:

答案 0 :(得分:29)

当选择项值为null时,JSF不会呈现<option value>,而只会<option>。因此,浏览器将改为提交选项标签。这在HTML specification(强调我的)中有明确规定:

  

value = cdata [CS]

     

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

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

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

  

getAsString

     

...

     

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

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

至于处理空字符串提交值(或null),您实际应该让您的转换器将此责任委托给required="true"属性。因此,当传入的valuenull或空字符串时,您应立即返回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);
}

关于你的特殊问题,

  

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

正如那里所回答的那样,这是Mojarra中的一个错误,并且自OmniFaces 1.8以来在<o:viewParam>被绕过。因此,如果您升级到至少OmniFaces 1.8.3并使用其<o:viewParam>代替<f:viewParam>,那么您不应再受此错误的影响。

OmniFaces SelectItemsConverter在这种情况下也应该可以正常工作。它返回null的空字符串。

答案 1 :(得分:4)

  • 如果您想要选择组件的避免空值,最优雅的方法是使用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="" />
    

更多阅读hereherehere

答案 2 :(得分:2)

你混合了一些东西,我并不完全清楚你想要达到的目标,但让我们试试

  

这显然会导致抛出java.lang.NumberFormatException   在它的转换器。

这一点并不明显。如果value为empty或null String,则不检入转换器,您应该这样做。在这种情况下,转换器应返回null。

  

为什么它将Select(itemLabel)渲染为其值而不是空值   string(itemValue)?

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

修复转换器以使用空/空字符串,并让JSF对返回的null做出反应,作为不允许的值。首先调用转换,然后进行验证。

我希望能回答你的问题。

答案 3 :(得分: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属性。

当选择第一个选项时,这可以按预期绕过转换器(即使require设置为false)。当itemValue更改为null以外的其他内容时,其行为无法预测(我不明白这一点)。

如果列表中的其他项目设置为非空值且转换器中收到的项目始终为空字符串(即使选择了其他选项),也不能选择列表中的其他项目。

此外,当转换器中将此空字符串解析为Long时,引发ConverterException之后引起的NumberFormatException不报告{{1}中的错误(至少这应该发生)。可以在服务器控制台上看到完整的异常堆栈跟踪。

如果有人可以揭露这一点,我会接受答案,如果有的话。

答案 4 :(得分:-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.
    }
}

答案 5 :(得分:-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>