jsf - selectitems - Usando un "Por favor seleccione" f: Seleccione un elemento con un valor nulo/vacĂo dentro de un ap: seleccione un elemento
selectonemenu jsf dinamico (6)
Cuando el valor del elemento seleccionado es null
, JSF no procesará <option value>
, sino solo <option>
. Como consecuencia, los navegadores enviarán la etiqueta de la opción en su lugar. Esto está claramente especificado en la especificación HTML (énfasis mío):
value = cdata [CS]
Este atributo especifica el valor inicial del control. Si este atributo no está establecido, el valor inicial se establece en los contenidos del elemento OPTION.
También puede confirmar esto mirando el monitor de tráfico HTTP. Debería ver la etiqueta de opción que se envía.
En su lugar, debe establecer el valor del elemento seleccionado en una cadena vacía. JSF luego renderizará un <option value="">
. Si está utilizando un convertidor, entonces debería devolver una cadena vacía ""
del convertidor cuando el valor sea null
. Esto también se especifica claramente en Converter#getAsString()
javadoc (énfasis mío):
getAsString
...
Devuelve: una cadena de longitud cero si el valor es nulo ; de lo contrario, el resultado de la conversión
Entonces, si usa <f:selectItem itemValue="#{null}">
en combinación con dicho convertidor, se mostrará un <option value="">
y el navegador enviará solo una cadena vacía en lugar de la etiqueta de opción .
En cuanto a tratar con el valor enviado de cadena vacía (o null
), debería dejar que su convertidor delegue esta responsabilidad en el atributo required="true"
. Entonces, cuando el value
entrante es null
o una cadena vacía, entonces debe devolver null
inmediatamente. Básicamente, el convertidor de su entidad se debe implementar de la siguiente manera:
@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);
}
En cuanto a su problema particular con esto,
pero devolver una cadena vacía cuando el objeto en cuestión es nulo, a su vez incurre en otro problema como se demuestra here .
Como se contestó allí, este es un error en Mojarra y se omite en <o:viewParam>
desde OmniFaces 1.8. Entonces, si actualizas al menos a OmniFaces 1.8.3 y usas su <o:viewParam>
lugar de <f:viewParam>
, entonces ya no deberías estar afectado por este error.
El OmniFaces SelectItemsConverter
también debería funcionar tan bien en esta circunstancia. Devuelve una cadena vacía para null
.
Estoy <p:selectOneMenu/>
un <p:selectOneMenu/>
de la base de datos de la siguiente manera.
<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"/>
La opción seleccionada por defecto, cuando se carga esta página,
<f:selectItem itemLabel="Select" itemValue="#{null}"/>
El convertidor:
@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;
}
}
Cuando se selecciona el primer elemento del menú representado por <f:selectItem>
y se envía el formulario, el value
obtenido en el método getAsObject()
es Select
que es la etiqueta de <f:selectItem>
- el primer elemento en el lista que intuitivamente no se espera en absoluto.
Cuando el atributo itemValue
de <f:selectItem>
se establece en una cadena vacía, arroja java.lang.NumberFormatException: For input string: ""
en el método getAsObject()
aunque la excepción se getAsObject()
y registra de manera precisa para ConverterException
.
Esto de alguna manera parece funcionar, cuando se cambia la declaración de return
de getAsString()
return value instanceof Country?((Country)value).getCountryId().toString():null;
a
return value instanceof Country?((Country)value).getCountryId().toString():"";
null
se reemplaza por una cadena vacía, pero devolver una cadena vacía cuando el objeto en cuestión es null
, a su vez incurre en otro problema como se demuestra here .
¿Cómo hacer que esos convertidores funcionen correctamente?
También se intentó con org.omnifaces.converter.SelectItemsConverter
pero no hizo ninguna diferencia.
Estás mezclando algunas cosas, y no está completamente claro para mí lo que quieres lograr, pero probemos
Obviamente, esto causa que la java.lang.NumberFormatException sea lanzada en su convertidor.
No es nada obvio en eso. No verifica en el convertidor si el valor está vacío o Cadena nula, y debería. En ese caso, el convertidor debería devolver nulo.
¿Por qué renderiza Select (itemLabel) como su valor y no como una cadena vacía (itemValue)?
El seleccionar debe tener algo seleccionado. Si no proporciona un valor vacío, se seleccionará el primer elemento de la lista, que no es algo que usted esperaría.
Simplemente arregle el convertidor para trabajar con cadenas vacías / nulas y deje que el JSF reaccione a null
devuelto como valor no permitido. La conversión se llama primero, luego viene la validación.
Espero que responda tus preguntas.
Esto me funciona completamente:
<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>
El convertidor
@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.
}
}
Además de lo incompleto, esta respuesta fue obsoleta, ya que estaba usando Spring en el momento de esta publicación:
He modificado el método getAsString()
del convertidor para devolver una cadena vacía en lugar de devolver null
, cuando no se encuentra ningún objeto Country
como (además de algunos otros cambios),
@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.
}
}
Y <f:selectItem>
para aceptar un valor null
siguiente manera.
<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"/>
Esto genera el siguiente 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>
Anteriormente, el HTML generado parecía,
<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>
Observe la primera <option>
sin atributo de value
.
Esto funciona como se esperaba al puentear el convertidor cuando se selecciona la primera opción (aunque require
se establece en falso). Cuando itemValue
se cambia por otro que no sea null
, entonces se comporta de manera impredecible (no entiendo esto).
No se pueden seleccionar otros elementos en la lista, si se establece en un valor no nulo y el elemento recibido en el convertidor es siempre una cadena vacía (aunque se selecciona otra opción).
Además, cuando esta cadena vacía se analiza en Long
en el convertidor, la NumberFormatException
ConverterException
que se produce después de que se lanza NumberFormatException
no informa el error en el UIViewRoot
(al menos esto debería suceder). La stacktrace de excepción completa se puede ver en la consola del servidor.
Si alguien pudiera exponer algo de luz sobre esto, aceptaría la respuesta, si se da.
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>
- Si desea evitar valores nulos para su componente seleccionado, la forma más elegante es usar
noSelectionOption
.
Cuando noSelectionOption="true"
, el convertidor ni siquiera intentará procesar el valor.
Además, cuando combina eso con <p:selectOneMenu required="true">
obtendrá un error de validación cuando el usuario intente seleccionar esa opción.
Un último toque, puede usar el atributo itemDisabled
para dejar en claro al usuario que no puede usar esta opción.
<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"/>
Ahora, si desea poder establecer un valor nulo , puede ''engañar'' al convertidor para que devuelva un valor nulo, utilizando
<f:selectItem itemLabel="Select" itemValue="" />