JSF SelectManyListbox with noSelectionOption="true" - Validation Error: Value is not valid

JSF SelectManyListbox with noSelectionOption="true" - Validation Error: Value is not valid

我在使用 h:selectManyListbox 时遇到问题,当项目填充了 POJO 并且 noSelectionOption 为真时(对于 h:selectManyListbox 以枚举作为项目,它按我预期的方式工作) .

豆子

@Named
@ViewScoped
public class MyBean implements Serializable {

    private static final long serialVersionUID = 1L;

    private List<BaseDTO> availableItems = null;

    private String[] selectedItems = null;

    @PostConstruct
    private void initialize() {
        loadAvailableItems();
    }

    private void loadAvailableItems() {
        availableItems = Arrays.asList(new BaseDTO("entityId", "entityDescription"), new BaseDTO(...), ...);
    }

    public List<BaseDTO> getAvailableItems() {
        return availableItems;
    }
    
    public String[] getSelectedItems() {
        return selectedItems;
    }

    public void setSelectedItems(String[] selectedItems) {
        this.selectedItems = selectedItems;
    }

}

BaseDTO

public class BaseDTO {

    private String id;

    private String description;

    public BaseDTO(String id, String description) {
        this.id = id;
        this.description = description;
    }

    public String getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return id;
    }
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        BaseDTO other = (BaseDTO) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

}

XHTML

<h:selectManyListbox value="#{myBean.selectedItems}" hideNoSelectionOption="false" size="4">
    <f:selectItem itemValue="#{null}" itemLabel="--" noSelectionOption="true" />
    <f:selectItems value="#{myBean.availableItems}" var="entry" itemValue="#{entry.id}" itemLabel="#{entry.description}" />
</h:selectManyListbox>

当我尝试提交页面时,我总是得到 Validation Error: Value is not valid。 如果我删除 hideNoSelectionOption 和相应的 <f:selectItem itemValue="#{null}" itemLabel="--" noSelectionOption="true" /> 一切正常,但是我真的很想在我的列表中添加这个 noSelectionOption

我尝试使用 OmniFaces SelectItemsConverter 甚至创建我自己的自定义转换器,但没有成功。无论我尝试什么,我都无法克服这个验证错误。

同时我发现了一个不太好的解决方法:

如果我的 availableItems 变量是 Map<String, String> 而不是 List:

private Map<String, String> availableItems = null;

如果我向地图添加一个空条目:

    private void loadAvailableItems() {
        List<BaseDTO> dtoList = Arrays.asList(new BaseDTO("entityId", "entityDescription"));
        availableItems = dtoList.stream().collect(Collectors.toMap(BaseDTO::getId, BaseDTO::getDescription));
        availableItems.put(null, "--");
    }

然后,一切都按预期进行,除了 noSelectionOption 没有在页面上预先选择。

这是预期的组件行为,还是我遗漏了什么?

在此先感谢您的帮助!

看起来它正在尝试验证该值,但它不能。您是否尝试过使用自定义验证器?

首先,noSelectionOption/hideNoSelectionOption属性对在这里被误解了。请删除它们。它在您的上下文中完全没有用。为了更好地理解他们的初衷,请前往Best way to add a "nothing selected" option to a selectOneMenu in JSF的答案,总结如下:

The primary purpose of this attribute pair is to prevent the web site user from being able to re-select the "no selection option" when the component has already a non-null value selected.

在您的特定情况下,您有一个多 select 列表框。首先,在这样的用户界面元素中使用“nothing selected”选项毫无意义。您只需要 deselect 一切,以达到“无 selected”状态。这在例如单个 select 下拉列表中是不可能的,因为您不能首先 deselect selected 选项。因此,此类用户界面元素需要“无 selected”选项。但是,再说一遍,多 select 列表框不需要这样做。然而,我确实明白,拥有一个可操作的元素是很有用的,它可以自动取消 select 列表框中的所有内容。这可以通过列表框附近某处的 link 或按钮完成。

无论如何,我已经能够在 Mojarra 2.3.17 中重现所描述的问题。根本问题是“空字符串提交值”不再由空字符串数组表示,而是由具有单个项目的字符串数组表示,一个空字符串。因此,所有与“空字符串提交值”相关的检查随后都失败了。我不认为这是 JSF 本身的错误,而只是意外使用多 select 组件的情况。

您可以通过在呈现响应阶段(第 6 阶段)以外的所有阶段显式禁用该项目来解决所有问题。它将 select 可用,但会自动从 select 编辑的项目中删除,作为针对篡改请求的内置措施。这样“提交的空字符串值”将是预期的空数组。

<h:selectManyListbox value="#{myBean.selectedItems}" size="4">
    <f:selectItem itemValue="#{null}" itemLabel="--" itemDisabled="#{facesContext.currentPhaseId.ordinal ne 6}" />
    <f:selectItems value="#{myBean.availableItems}" var="entry" itemValue="#{entry.id}" itemLabel="#{entry.description}" />
</h:selectManyListbox>

请注意,这无法通过自定义转换器或验证器解决。 JSF 不允许自定义转换器返回 null,并且此特定的“值无效”验证由内置验证器针对不能 replaced/disabled 的篡改请求完成。我们最好的选择可能是 change/respecify noSelectionOption="true" 的行为,因为这确实经常被误解。它可能应该在内部以与禁用项目相同的方式处理。