Usando un “Por favor seleccione” f: Seleccione un elemento con un valor nulo / vacío dentro de un ap: seleccione un elemento

Estoy

un

de la base de datos de la siguiente manera.

 

La opción seleccionada por defecto, cuando se carga esta página,

  

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  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 y se envía el formulario, el value obtenido en el método getAsObject() es Select que es la etiqueta de – el primer elemento en el lista que intuitivamente no se espera en absoluto.

Cuando el atributo itemValue de 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 statement 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 aquí .

¿Cómo hacer que esos convertidores funcionen correctamente?

También se intentó con org.omnifaces.converter.SelectItemsConverter pero no hizo ninguna diferencia.

Cuando el valor del elemento seleccionado es null , JSF no procesará , sino solo . 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 . 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 en combinación con dicho convertidor, se mostrará un 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 aquí .

Como se contestó allí, este es un error en Mojarra y se omite en desde OmniFaces 1.8. Entonces, si actualizas al menos a OmniFaces 1.8.3 y usas su lugar de , 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 .

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

       
  • Ahora, si desea poder establecer un valor nulo , puede ‘engañar’ al convertidor para que devuelva un valor nulo, utilizando

      

Más lectura aquí , aquí o aquí

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.

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 para aceptar un valor null siguiente manera.

      

Esto genera el siguiente HTML.

  

Anteriormente, el HTML generado parecía,

  

Observe la primera 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.

Esto me funciona completamente:

      

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. } } 
 public void limparSelecao(AjaxBehaviorEvent evt) { Object submittedValue = ((UIInput)evt.getSource()).getSubmittedValue(); if (submittedValue != null) { getPojo().setTipoCaixa(null); } }