在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()
方法中获得的value
是Select
,它是<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>