在p:selectOneMenu中使用“请select”f:selectItem,其中null / empty值

我正在从数据库填充一个<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"/> 

默认select的选项,当这个页面被加载的时候,

 <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; } } 

当select由<f:selectItem>表示的菜单中的第一项并且提交表单时,则在getAsObject()方法中获得的valueSelect ,它是<f:selectItem>的标签 -清单,这是直观的,不是所有的预期。

当将<f:selectItem>itemValue属性设置为空string时,即使exception被精确捕获并注册为ConverterException也会在getAsObject()方法中引发java.lang.NumberFormatException: For input string: ""

这似乎工作,当getAsString()return语句从更改

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

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

null被一个空stringreplace,但是当所讨论的对象为空时返回一个空string,反过来引起另一个问题,如这里所示 。

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

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

当select项的值为null ,JSF将不呈现<option value> ,而只是<option> 。 因此,浏览器会提交选项的标签。 这在HTML规范 (我的重点)中明确规定:

value = cdata [CS]

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

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

您需要将select的项目值设置为空string。 然后JSF将呈现一个<option value=""> 。 如果您使用的是转换器,那么当值为null时,实际上应该从转换器返回一个空string"" 。 这也明确指定在Converter#getAsString() javadoc(强调我的):

getAsString

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

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

至于处理空string提交值(或null ),你应该实际上让你的转换器把这个责任委托给required="true"属性。 所以,当传入的value null或空string,那么你应该立即返回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); } 

至于你的这个问题,

但是当所讨论的对象为空时返回一个空string,反过来会引起另一个问题,如这里所演示的。

正如在那里回答的那样,这是Mojarra中的一个错误,并且自从OmniFaces 1.8以来在<o:viewParam>中被绕过。 所以如果你至less升级到OmniFaces 1.8.3,并使用它的<o:viewParam>而不是<f:viewParam> ,那么你不应该受到这个bug的影响。

OmniFaces SelectItemsConverter也应该在这种情况下工作。 它为null返回一个空string。

  • 如果你想避免select组件的空值,最优雅的方法是使用noSelectionOption

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

另外,当你将<p:selectOneMenu required="true"><p:selectOneMenu required="true">时,当用户尝试select该选项时,将会出现validation错误。

最后一点,你可以使用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="" /> 

更多的阅读, 在这里 ,或在这里

你在混合一些东西,而且我不清楚你想达到什么目的,但是让我们试试看吧

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

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

为什么它呈现Select(itemLabel)作为它的值而不是一个空string(itemValue)?

select必须有select的东西。 如果你没有提供空值,那么列表中的第一个元素将被选中,这不是你所期望的。

只需修改转换器以使用空/空string,并让JSF对不允许的值作出反应返回null 。 首先调用转换,然后进行validation。

我希望能回答你的问题。

除了不完整之外,这个答案已经被弃用了,因为在这篇文章中我使用了Spring:

我修改了转换器的getAsString()方法返回一个空string,而不是返回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> 

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

当第一个选项被选中时(即使require被设置为false),这可以按预期的方式绕过转换器。 当itemValue更改为非null ,则它的行为不可预知(我不明白这一点)。

如果列表中没有其他项目可以select,如果它被设置为非空值,并且转换器中收到的项目总是空string(即使select了另一个选项)。

此外,当这个空string在转换器中被parsing为Long时,引发NumberFormatException之后引发的ConverterException不会报告UIViewRoot的错误(至less这应该发生)。 可以在服务器控制台上看到完整的exception堆栈跟踪。

如果有人能够揭露这一点,我会接受答案,如果给出。

这对我完全有用:

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