web-dev-qa-db-fra.com

Utilisation d'un "Veuillez sélectionner" f: selectItem avec une valeur nulle / vide dans 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 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.

26
Tiny

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.

30
BalusC
  • Si vous voulez éviter les valeurs nulles pour votre composant sélectionné, la manière la plus élégante est d'utiliser le 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="" />
    

Plus de lecture ici , ici , ou ici

4
yannicuLar

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.

2
Danubian Sailor

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.

1
Tiny