En utilisant un "S'il vous plaît sélectionner" f: selectItem avec valeur nulle / vide à l'intérieur d'un p: selectOneMenu
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 soumis alors, le value
obtenu dans la méthode getAsObject()
est Select
qui est l'étiquette de <f:selectItem>
- le premier élément dans la liste qui est intuitivement pas prévu du tout.
quand l'attribut itemValue
de <f:selectItem>
est placé sur une chaîne vide alors, il lance java.lang.NumberFormatException: For input string: ""
dans la méthode getAsObject()
même si l'exception est précisément capturée et enregistrée pour ConverterException
.
cela semble en quelque sorte fonctionner, quand le return
déclaration du getAsString()
est changé 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 retourner une chaîne vide lorsque l'objet en question Est null
, à son tour, pose un autre problème comme démontré ici .
Comment faire fonctionner correctement de tels convertisseurs?
également essayé avec org.omnifaces.converter.SelectItemsConverter
mais il n'a fait aucune différence.
6 réponses
lorsque la valeur de l'élément sélectionné est null
, alors JSF ne rendra pas <option value>
, mais seulement <option>
. Par conséquent, les navigateurs soumettront l'étiquette de l'option à la place. Ceci est clairement spécifié dans spécification HTML (emphasis mine):
value = cdata [CS]
Cet attribut spécifie la valeur initiale de la commande. Si cet attribut n'est pas défini, la valeur initiale est définir le contenu de l'élément OPTION.
vous pouvez également le confirmer en regardant le moniteur de trafic HTTP. Vous devriez voir l'étiquette de l'option soumise.
vous devez définir la valeur de l'élément select à une chaîne vide à la place. JSF produira alors un <option value="">
. Si vous utilisez un convertisseur, alors vous devriez retourner une chaîne vide ""
du convertisseur quand la valeur est null
. Ce est également clairement spécifié dans Converter#getAsString()
javadoc (l'emphase est mienne):
getAsString
...
Retourne: une Chaîne de longueur nulle si la valeur est null , sinon le résultat de la conversion
donc si vous utilisez <f:selectItem itemValue="#{null}">
en combinaison avec un tel convertisseur, alors un <option value="">
sera rendu et le le navigateur soumettra juste une chaîne vide au lieu de l'étiquette d'option.
en ce qui concerne la valeur de la chaîne vide soumise (ou null
), vous devriez 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, alors vous devez retourner null
immédiatement. fondamentalement votre convertisseur d'entité doit être mis en œuvre 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 ceci,
mais retourner une chaîne vide lorsque l'objet en question est nul, à son tour, entraîne un autre problème comme démontré ici .
comme répondu là-bas, il s'agit d'un bug à Mojarra et contourné dans <o:viewParam>
depuis OmniFaces 1.8. Donc, si vous mettez à jour vers 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 bug.
La OmniFaces SelectItemsConverter
devrait également travailler comme bonnes dans cette circonstance. Elle renvoie une chaîne vide pour null
.
- si vous voulez éviter valeurs nulles pour votre composant sélectionné, la façon la plus élégante est d'utiliser le
noSelectionOption
.
quand noSelectionOption="true"
, le convertisseur n'essaiera même pas de traiter la valeur.
Plus, lorsque vous combinez cela avec <p:selectOneMenu required="true">
vous obtiendrez une erreur de validation, lorsque l'utilisateur essaie de sélectionner cette option.
une dernière touche, vous pouvez utiliser le itemDisabled
attribut de faire comprendre à 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 être en mesure de définir une valeur nulle , vous pouvez 'tricher' le convertisseur pour retourner une valeur nulle, en utilisant
<f:selectItem itemLabel="Select" itemValue="" />
Vous êtes mélange un peu les choses, et il n'est pas totalement clair pour moi ce que vous voulez atteindre, mais nous allons essayer
cela provoque évidemment la java.lang.NumberFormatException d'être jeté dans son convertisseur.
il n'y a rien d'évident. Vous ne Vérifiez pas dans le convertisseur si la valeur est vide ou chaîne nulle, et vous devriez. Dans ce cas, le convertisseur doit retourner null.
pourquoi render Select (itemLabel) comme valeur et non vide chaîne (itemValue)?
le select doit avoir quelque chose de sélectionné. Si vous ne fournissez pas la valeur vide, le premier élément de la liste sera sélectionné, ce qui n'est pas quelque chose que vous attendez.
il suffit de fixer le convertisseur pour qu'il fonctionne avec des chaînes vides/nulles et de laisser le JSF réagir avec null
retourné comme valeur non autorisée. La conversion est appelée d'abord, puis vient la validation.
j'espère que cela répondra à vos questions.
En plus de l'incomplétude, cette réponse était obsolète, puisque j'ai été en utilisant au Printemps, au moment de ce post :
j'ai modifié la méthode getAsString()
du convertisseur pour retourner une chaîne vide au lieu de retourner 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 de null
comme suivre.
<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"/>
génère le 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>
plus tôt, le 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 à false). Lorsque itemValue
est remplacé par un autre que null
, puis il se comporte de manière imprévisible (Je ne comprends pas cela).
aucun autre élément de la liste ne peut être sélectionné, s'il est défini à 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 divisé en Long
dans le convertisseur, le ConverterException
qui est causé après le NumberFormatException
est jeté ne signale pas l'erreur dans le UIViewRoot
(au moins cela devrait arriver). Le stacktrace d'exception complète peut être vu sur la console du serveur à la place.
si quelqu'un pouvait exposer un peu de lumière sur ceci, j'accepterais la réponse, si elle est donnée.
cela me fait du bien:
<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>
Le Convertisseur
@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>