Используя "Пожалуйста, выберите" f: selectItem с нулевым / пустым значением внутри p: selectOneMenu

я заселяю <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> выбирается и форма представляется затем,value получил в getAsObject() метод Select, который является меткой <f:selectItem> - первый пункт в списке, который интуитивно не ожидается все.

когда на <f:selectItem> устанавливается в пустую строку, затем он бросает java.lang.NumberFormatException: For input string: "" на getAsObject() метод, даже если исключение точно поймано и зарегистрировано для ConverterException.

это как-то работает, когда return сообщении getAsString() изменилась с

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

to

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

null заменяется пустой строкой, но возвращает пустую строку, когда рассматриваемый объект null, в свою очередь, возникает еще одна проблема, как показано здесь.

как заставить такие преобразователи работать правильно?

также пробовал с org.omnifaces.converter.SelectItemsConverter но он сделал никакой разницы.

6 ответов


когда значение select item равно null, тогда JSF не будет отображать <option value>, но только <option>. Как следствие, браузеры будут отправлять метку опции вместо этого. Это четко указано в спецификация HTML (выделено мной):

value = cdata [CS]

этот атрибут задает начальное значение элемента управления. если этот атрибут не задан, начальное значение устанавливается на содержимое опции элемент.

вы также можете подтвердить это, посмотрев на монитор трафика HTTP. Вы должны увидеть метку опции, отправляемую.

вместо этого вам нужно установить значение select item в пустую строку. Затем JSF отобразит <option value="">. Если вы используете конвертер, то на самом деле вы должны возвращать пустую строку "" от преобразователя, когда значение null. Это также четко указано в Converter#getAsString() javadoc (курсив мой):

getAsString

...

Returns: строка нулевой длины, если значение равно null, в противном случае результат преобразования

если вы используете <f:selectItem itemValue="#{null}"> в сочетании с таким преобразователем, то <option value=""> будет отображаться, и браузер отправит только пустую строку вместо метки опции.

что касается обработки пустой строки, представленной значением (или null), вы на самом деле пусть ваш конвертер делегирует эту ответственность . Итак, когда входящий value is null или пустая строка, тогда вы должны вернуть 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, в свою очередь возникает другая проблема, как продемонстрировал здесь.

как ответил Там, это ошибка в Mojarra и обошел в <o:viewParam> начиная с OmniFaces 1.8. Поэтому, если вы обновите хотя бы OmniFaces 1.8.3 и используете его <o:viewParam> вместо <f:viewParam>, тогда вы больше не должны быть затронуты этой ошибкой.

В OmniFaces SelectItemsConverter также должны работать, как хорошо в этом случае. Он возвращает пустую строку для null.


  • если вы хотите избежать нулевые значения для выбранного компонента, самый элегантный способ-использовать noSelectionOption.

, когда noSelectionOption="true", конвертер даже не будет пытаться обработать значение.

плюс, когда вы объединяете это с <p:selectOneMenu required="true"> вы получите ошибку проверки, когда пользователь попытается выбрать этот параметр.

последний штрих, вы можете использовать


вы смешиваете несколько вещей, и мне не совсем ясно, чего вы хотите достичь, но давайте попробуем

это, очевидно, вызывает java.ленг.NumberFormatException быть брошенным в конвертере.

в этом нет ничего очевидного. Вы не проверяете конвертер, если значение пустое или нулевая строка, и вы должны. В этом случае преобразователь должен возвращать null.

почему он отображает Select (itemLabel) как его значение, а не пустой строки (itemValue)?

в select должно быть что-то выбрано. Если вы не предоставите пустое значение, будет выбран первый элемент из списка,что не является ожидаемым.

просто исправьте конвертер для работы с пустыми / нулевыми строками и позвольте JSF реагировать на returned null как недопустимое значение. Сначала вызывается преобразование,затем идет проверка.

Я надеюсь, что ответы на ваши вопросы.


в дополнение к неполноте этот ответ был устаревшим, так как я использовал весну во время этого поста :

Я изменил преобразователь getAsString() метод для возврата пустой строки вместо возврата null, если не Country объект найден как (в дополнение к некоторым другим изменениям),

@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> ' s 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> С .

это работает, как ожидалось, минуя преобразователь, когда выбран первый вариант (хотя require имеет значение false). Когда itemValue изменено на другое, чем null, тогда он ведет себя непредсказуемо (я этого не понимаю).

никакие другие элементы в списке могут быть выбраны, если задано непустое значение и элемент, полученный в конвертере, всегда является пустой строкой (даже если выбран другой вариант).

кроме того, когда эта пустая строка анализируется для Long в конвертер,ConverterException, который возникает после NumberFormatException брошен не сообщает об ошибке в UIViewRoot (по крайней мере это должно произойти). Полное исключение stacktrace можно увидеть на консоли сервера вместо этого.

если бы кто-то мог осветить это, я бы принял ответ, если он дан.


это работает для меня :

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

Конвертер

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