web-dev-qa-db-ja.com

p:selectOneMenu内でnull /空の値を持つ「選択してください」f:selectItemを使用する

次のように、データベースから_<p:selectOneMenu/>_を設定しています。

_<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"/>
_

このページがロードされるときにデフォルトで選択されるオプションは、

_<f:selectItem itemLabel="Select" itemValue="#{null}"/>
_

コンバーター:

_@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;
    }
}
_

_<f:selectItem>_で表されるメニューの最初の項目が選択され、フォームが送信されると、getAsObject()メソッドで取得されたvalueSelectになります。 _<f:selectItem>_のラベル-直感的にはまったく期待されないリストの最初の項目。

_<f:selectItem>_のitemValue属性が空の文字列に設定されると、例外が正確にキャッチされて登録されていても、getAsObject()メソッドで_Java.lang.NumberFormatException: For input string: ""_をスローします。 ConverterException

getAsString()returnステートメントが次のように変更されると、これはどういうわけか機能するようです。

_return value instanceof Country?((Country)value).getCountryId().toString():null;
_

_return value instanceof Country?((Country)value).getCountryId().toString():"";
_

nullは空の文字列に置き換えられますが、問題のオブジェクトがnullの場合は空の文字列を返します。これは here のように別の問題を引き起こします。

そのようなコンバーターを適切に動作させる方法は?

_org.omnifaces.converter.SelectItemsConverter_も試してみましたが、違いはありませんでした。

26
Tiny

選択項目の値がnullの場合、JSFは_<option value>_をレンダリングせず、_<option>_のみをレンダリングします。結果として、ブラウザは代わりにオプションのラベルを送信します。これは HTML仕様 (強調鉱山)で明確に指定されています:

_value = cdata [CS]_

この属性は、コントロールの初期値を指定します。 この属性が設定されていない場合、初期値はOPTION要素の内容に設定されます。

これを確認するには、HTTPトラフィックモニターを確認することもできます。送信されているオプションラベルが表示されます。

代わりに、選択項目の値を空の文字列に設定する必要があります。その後、JSFは_<option value="">_をレンダリングします。コンバーターを使用している場合、値がnullの場合、実際にはコンバーターから空の文字列_""_を返す必要があります。これは Converter#getAsString() javadoc(emphasis mine)でも明確に指定されています:

getAsString

...

戻り値:値がnullの場合は長さゼロの文字列、それ以外の場合は変換の結果

したがって、_<f:selectItem itemValue="#{null}">_をこのようなコンバーターと組み合わせて使用​​すると、_<option value="">_がレンダリングされ、ブラウザーはオプションラベルの代わりに空の文字列のみを送信します。

空の文字列で送信された値(またはnull)の処理については、実際にコンバータにこの責任を_required="true"_属性に委任させる必要があります。したがって、着信valuenullまたは空の文字列の場合、すぐにnullを返す必要があります。 基本的にエンティティコンバータは次のように実装する必要があります:

_@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);
}
_

これに関するあなたの特定の問題に関しては、

しかし、問題のオブジェクトがnullの場合は空の文字列を返すため、別の問題が発生します here

あちらで答えたように、これはMojarraのバグであり、OmniFaces 1.8以降の_<o:viewParam>_でバイパスされています。したがって、少なくともOmniFaces 1.8.3にアップグレードし、_<o:viewParam>_の代わりに_<f:viewParam>_を使用する場合、このバグの影響はもうありません。

OmniFaces SelectItemsConverterもこの状況でうまく機能するはずです。 nullに対して空の文字列を返します。

30
BalusC
  • 選択コンポーネントのnull値をavoidしたい場合、最もエレガントな方法はnoSelectionOptionを使用することです。

noSelectionOption="true"の場合、コンバーターは値を処理しようとさえしません。

さらに、それを<p:selectOneMenu required="true">と組み合わせると、ユーザーがそのオプションを選択しようとすると、検証エラーが発生します。

最後に、itemDisabled属性を使用して、ユーザーにこのオプションを使用できないことを明確にすることができます。

<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"/>
  • これで、null値を設定できるようにしたい場合、コンバータを「チート」してnull値を返すことができます。

    <f:selectItem itemLabel="Select" itemValue="" />
    

もっと読む herehere 、または here

4
yannicuLar

あなたはいくつかのことを混ぜています、そしてあなたが達成したいことは私には完全に明確ではありませんが、試してみましょう

これにより、明らかにコンバーターでJava.lang.NumberFormatExceptionがスローされます。

それは明らかではありません。値が空または文字列がnullの場合、コンバータをチェックインしません。その場合、コンバーターはnullを返す必要があります。

空の文字列(itemValue)ではなく、値としてSelect(itemLabel)をレンダリングするのはなぜですか?

選択には何かが選択されている必要があります。空の値を指定しない場合、リストの最初の要素が選択されますが、これは予期したものではありません。

コンバーターを修正して空/ヌル文字列を処理し、返されたnullが許可されていない値としてJSFに反応するようにします。変換が最初に呼び出され、次に検証が行われます。

それがあなたの質問に答えることを願っています。

2
Danubian Sailor

この投稿の時点でSpringを使用していたため、不完全さに加えて、この回答は廃止されました:

nullオブジェクトが見つからない場合(他の変更に加えて)、Countryを返す代わりに空の文字列を返すように、コンバーターのgetAsString()メソッドを変更しました。

@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.
    }
}

そして<f:selectItem>itemValueは、次のようにnull値を受け入れます。

<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"/>

これにより、次のHTMLが生成されます。

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

以前、生成されたHTMLは次のようになりました。

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

最初の<option>value属性なし。

これは、最初のオプションが選択されたときに(requireがfalseに設定されていても)コンバーターをバイパスして期待どおりに機能します。 itemValuenull以外に変更されると、予期しない動作をします(これは理解できません)。

リスト内のその他の項目は、null以外の値に設定されており、コンバーターで受信した項目が常に空の文字列である場合は選択できません(別のオプションが選択されていても)。

さらに、この空の文字列がコンバーターでLongに解析されるとき、ConverterExceptionがスローされた後に発生するNumberFormatExceptionは、UIViewRoot(少なくともこれは起こるはずです)。代わりに、サーバーコンソールで完全な例外スタックトレースを確認できます。

誰かがこれに何らかの光を当てることができれば、与えられれば答えを受け入れます。

1
Tiny