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.

23
demandé sur Community 2013-06-11 23:49:37

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 .

25
répondu BalusC 2017-05-23 12:26:17
  • 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="" />
    

plus de lecture ici , ici , ou ici

3
répondu yannicuLar 2017-05-23 12:17:59

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.

2
répondu Danubian Sailor 2013-06-11 21:03:13

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.

1
répondu Tiny 2015-07-24 12:38:32

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.
    }
}
-1
répondu Nassim MOUALEK 2015-07-24 12:43:25
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>
-3
répondu Gleidosn 2017-04-03 14:48:56