Je remplis un <p:selectOneMenu/>
De la base de données comme suit.
<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"/>
L'option sélectionnée par défaut, lorsque cette page est chargée est,
<f:selectItem itemLabel="Select" itemValue="#{null}"/>
Le convertisseur:
@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;
}
}
Lorsque le premier élément du menu représenté par <f:selectItem>
Est sélectionné et que le formulaire est ensuite soumis, la value
obtenue dans la méthode getAsObject()
est Select
qui est le libellé de <f:selectItem>
- le premier élément de la liste qui n'est intuitivement pas attendu du tout.
Lorsque l'attribut itemValue
de <f:selectItem>
Est alors défini sur une chaîne vide, il jette Java.lang.NumberFormatException: For input string: ""
Dans la méthode getAsObject()
même si l'exception est précisément interceptée et enregistré pour ConverterException
.
Cela semble fonctionner en quelque sorte, lorsque l'instruction return
de la fonction getAsString()
est modifiée de
return value instanceof Country?((Country)value).getCountryId().toString():null;
à
return value instanceof Country?((Country)value).getCountryId().toString():"";
null
est remplacé par une chaîne vide mais en retournant une chaîne vide lorsque l'objet en question est null
, à son tour, cela pose un autre problème comme démontré ici .
Comment faire fonctionner correctement ces convertisseurs?
A également essayé avec org.omnifaces.converter.SelectItemsConverter
Mais cela n'a fait aucune différence.
Lorsque la valeur de l'élément sélectionné est null
, JSF ne rendra pas <option value>
, Mais seulement <option>
. En conséquence, les navigateurs soumettront plutôt l'étiquette de l'option. Ceci est clairement spécifié dans spécification HTML (accentuation du mien):
value = cdata [CS]
Cet attribut spécifie la valeur initiale du contrôle. Si cet attribut n'est pas défini, la valeur initiale est définie sur le contenu de l'élément OPTION.
Vous pouvez également le confirmer en consultant le moniteur de trafic HTTP. Vous devriez voir l'étiquette d'option soumise.
Vous devez plutôt définir la valeur de l'élément sélectionné sur une chaîne vide. JSF rendra alors un <option value="">
. Si vous utilisez un convertisseur, vous devez en fait renvoyer une chaîne vide ""
À partir du convertisseur lorsque la valeur est null
. Ceci est également clairement spécifié dans Converter#getAsString()
javadoc (accent sur le mien):
getAsString
...
Retourne: une chaîne de longueur nulle si la valeur est nulle , sinon le résultat de la conversion
Donc, si vous utilisez <f:selectItem itemValue="#{null}">
En combinaison avec un tel convertisseur, un <option value="">
Sera rendu et le navigateur soumettra juste une chaîne vide au lieu de l'étiquette d'option.
En ce qui concerne la valeur de chaîne vide soumise (ou null
), vous devez en fait laisser votre convertisseur déléguer cette responsabilité à l'attribut required="true"
. Ainsi, lorsque le value
entrant est null
ou une chaîne vide, vous devez immédiatement renvoyer null
. Fondamentalement votre convertisseur d'entité doit être implémenté comme suit:
@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);
}
Quant à votre problème particulier avec cela,
mais renvoyer une chaîne vide lorsque l'objet en question est nul, à son tour, encourt un autre problème comme démontré ici .
Comme répondu là-bas, il s'agit d'un bogue dans Mojarra et contourné dans <o:viewParam>
Depuis OmniFaces 1.8. Donc, si vous mettez à niveau au moins OmniFaces 1.8.3 et utilisez son <o:viewParam>
Au lieu de <f:viewParam>
, Alors vous ne devriez plus être affecté par ce bogue.
Les OmniFaces SelectItemsConverter
devraient également fonctionner aussi bien dans ce cas. Il renvoie une chaîne vide pour null
.
noSelectionOption
.Quand noSelectionOption="true"
, le convertisseur n'essaiera même pas de traiter la valeur.
De plus, lorsque vous combinez cela avec <p:selectOneMenu required="true">
vous obtiendrez une erreur de validation lorsque l'utilisateur essaiera de sélectionner cette option.
Une dernière touche, vous pouvez utiliser l'attribut itemDisabled
pour indiquer clairement à l'utilisateur qu'il ne peut pas utiliser cette option.
<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"/>
Maintenant, si vous voulez pouvoir définir une valeur nulle , vous pouvez "tricher" le convertisseur pour retourner une valeur nulle, en utilisant
<f:selectItem itemLabel="Select" itemValue="" />
Vous mélangez quelques éléments, et je ne sais pas très bien ce que vous voulez réaliser, mais essayons
Cela provoque évidemment la levée de Java.lang.NumberFormatException dans son convertisseur.
Ce n'est rien d'évident. Vous ne vérifiez pas dans le convertisseur si la valeur est une chaîne vide ou nulle, et vous devriez le faire. Dans ce cas, le convertisseur doit retourner null.
Pourquoi rend-il Select (itemLabel) comme sa valeur et non une chaîne vide (itemValue)?
La sélection doit avoir quelque chose de sélectionné. Si vous ne fournissez pas de valeur vide, le premier élément de la liste sera sélectionné, ce qui n'est pas quelque chose que vous attendez.
Corrigez simplement le convertisseur pour qu'il fonctionne avec des chaînes vides/nulles et laissez le JSF réagir à null
renvoyé comme valeur non autorisée. La conversion est appelée en premier, puis vient la validation.
J'espère que cela répond à vos questions.
En plus de son caractère incomplet, cette réponse était obsolète, car j'utilisais Spring au moment de ce post:
J'ai modifié la méthode getAsString()
du convertisseur pour retourner une chaîne vide au lieu de renvoyer null
, quand aucun objet Country
n'est trouvé comme (en plus de quelques autres changements),
@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.
}
}
Et <f:selectItem>
's itemValue
pour accepter une valeur null
comme suit.
<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"/>
Cela génère le code HTML suivant.
<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>
Auparavant, le code HTML généré ressemblait,
<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>
Remarquez le premier <option>
sans attribut value
.
Cela fonctionne comme prévu en contournant le convertisseur lorsque la première option est sélectionnée (même si require
est défini sur false). Lorsque itemValue
est changé en autre que null
, alors il se comporte de façon imprévisible (je ne comprends pas cela).
Aucun autre élément de la liste ne peut être sélectionné s'il est défini sur une valeur non nulle et que l'élément reçu dans le convertisseur est toujours une chaîne vide (même si une autre option est sélectionnée).
En outre, lorsque cette chaîne vide est analysée en Long
dans le convertisseur, le ConverterException
qui est provoqué après le NumberFormatException
est levé ne signale pas l'erreur dans le UIViewRoot
(au moins cela devrait arriver). La trace de pile d'exceptions complète peut être vue à la place sur la console du serveur.
Si quelqu'un pouvait exposer un peu de lumière à ce sujet, j'accepterais la réponse, si elle est donnée.