Использование "Пожалуйста, выберите" f:selectItem с нулевым / пустым значением внутри a 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>
- первый пункт в списке, который интуитивно не ожидается вообще.
Когда itemValue
атрибут <f:selectItem>
затем устанавливается пустая строка java.lang.NumberFormatException: For input string: ""
в getAsObject()
метод, хотя исключение точно пойман и зарегистрирован для ConverterException
,
Это как-то работает, когда return
заявление getAsString()
изменен с
return value instanceof Country?((Country)value).getCountryId().toString():null;
в
return value instanceof Country?((Country)value).getCountryId().toString():"";
null
заменяется пустой строкой, но возвращает пустую строку, когда рассматриваемый объект null
В свою очередь, возникает другая проблема, как показано здесь.
Как заставить такие конвертеры работать правильно?
Также пробовал с org.omnifaces.converter.SelectItemsConverter
но это не имело значения.
6 ответов
Когда выбранное значение элемента null
то JSF не будет рендериться <option value>
, но только <option>
, Как следствие, браузеры будут отправлять метку опции. Это четко указано в спецификации HTML (выделено мной):
value = cdata [CS]
Этот атрибут указывает начальное значение элемента управления. Если этот атрибут не задан, начальное значение устанавливается на содержимое элемента OPTION.
Вы также можете подтвердить это, посмотрев на монитор трафика HTTP. Вы должны увидеть отправляемую метку опции.
Вместо этого вам нужно установить пустую строку для элемента select. JSF будет затем визуализировать <option value="">
, If you're using a converter, then you should actually be returning an empty string ""
from the converter when the value is null
, This is also clearly specified in Converter#getAsString()
Javadoc (выделение мое):
getAsString
...
Returns: a zero-length String if value is null, otherwise the result of the conversion
Так что если вы используете <f:selectItem itemValue="#{null}">
in combination with such a converter, then a <option value="">
will be rendered and the browser will submit just an empty string instead of the option label.
As to dealing with the empty string submitted value (or null
), you should actually let your converter delegate this responsibility to the required="true"
приписывать. So, when the incoming value
является null
or an empty string, then you should return null
немедленно. Basically your entity converter should be implemented like follows:
@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);
}
As to your particular problem with this,
but returning an empty string when the object in question is null, in turn incurs another problem as demonstrated here.
As answered over there, this is a bug in Mojarra and bypassed in <o:viewParam>
since OmniFaces 1.8. So if you upgrade to at least OmniFaces 1.8.3 and use its <o:viewParam>
вместо <f:viewParam>
, then you shouldn't be affected anymore by this bug.
The OmniFaces SelectItemsConverter
should also work as good in this circumstance. It returns an empty string for null
,
- Если вы хотите избежать нулевых значений для выбранного компонента, самый элегантный способ - это использовать
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"/>
Теперь, если вы хотите установить нулевое значение, вы можете "обмануть" конвертер, чтобы получить нулевое значение, используя
<f:selectItem itemLabel="Select" itemValue="" />
Вы смешиваете несколько вещей, и мне не совсем ясно, чего вы хотите достичь, но давайте попробуем
Это, очевидно, вызывает исключение java.lang.NumberFormatException в его конвертере.
В этом нет ничего очевидного. Вы не проверяете в конвертере, является ли значение пустым или нулевым String, и вы должны. В этом случае конвертер должен вернуть ноль.
Почему он отображает Select (itemLabel) в качестве значения, а не пустую строку (itemValue)?
Выбор должен иметь что-то выбранное. Если вы не предоставите пустое значение, будет выбран первый элемент из списка, а это не то, чего вы ожидаете.
Просто исправьте конвертер для работы с пустыми / пустыми строками и позвольте JSF реагировать на возвращенные null
как недопустимое значение. Сначала вызывается преобразование, затем проверка.
Я надеюсь, что отвечает на ваши вопросы.
В дополнение к неполноте, этот ответ устарел, так как я использовал Spring во время этого поста:
Я изменил конвертер 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>
без value
приписывать.
Это работает, как и ожидалось, минуя конвертер, когда выбран первый вариант (хотя require
установлено в false). когда itemValue
изменен на другой, чем null
тогда он ведет себя непредсказуемо (я этого не понимаю).
Другие элементы в списке не могут быть выбраны, если для него задано ненулевое значение и элемент, полученный в преобразователе, всегда является пустой строкой (даже если выбран другой параметр).
Кроме того, когда эта пустая строка анализируется Long
в конвертере ConverterException
который вызван после NumberFormatException
брошен не сообщает об ошибке в UIViewRoot
(по крайней мере это должно произойти). Полная трассировка стека исключений можно увидеть на консоли сервера.
Если бы кто-то мог пролить свет на это, я бы принял ответ, если он будет дан.
Это полностью работает для меня:
<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>