1

I have a problem with h:selectManyListbox, when the items are populated with POJO's and the noSelectionOption is true (for h:selectManyListbox with enums as items, it works as I expected).

Bean

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

When I try to submit the page I always get Validation Error: Value is not valid. If I remove the hideNoSelectionOption and the correponding <f:selectItem itemValue="#{null}" itemLabel="--" noSelectionOption="true" /> everything works fine, however I really would like to have this noSelectionOption on my list.

I tried using OmniFaces SelectItemsConverter and even creating my own custom converter, but with no luck. No matter what I try I cannot overcome this validation error.

Meanwhile I found a not so nice workaround:

If my availableItems variable is a Map<String, String> instead of a List:

private Map<String, String> availableItems = null;

and if I add a null entry to the map:

    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, "--");
    }

then, everything works as expected, except the noSelectionOption is not preselected on the page.

I this the expected component behaviour, or am I missing something?

Thanks in advance for your help!

areal
  • 157
  • 5
  • Instead of the BaseDTO I suggest using an entity class like 'Item', 'Entry' or whatever (which has not neccessarily to be a JPA entity class) and a repository (sometimes also called service) like ItemRepo or ItemService from which you can access the entities. Then you can inject the repo/service into your backing bean MyBean and populate a List of the entities in a @PostConstruct method. It would also be helpful if you post the code of your custom converter. – Rudolf Held Oct 06 '21 at 13:13
  • My BaseDTO class is a POJO (not a JPA entity class) which carries the data from the persistence layer to the view. The List variable on MyBean is populated using a service, like you suggested - my example is simplified for clarity. Note that everything works fine if I remove the noSelectionOption. – areal Oct 06 '21 at 22:56

2 Answers2

1

First of all, the noSelectionOption/hideNoSelectionOption attribute pair has been misunderstood here. Please remove them. It has completely no use in your context. In order to better understand their originally intended purpose, head to the answer of Best way to add a "nothing selected" option to a selectOneMenu in JSF, which is summarized as follows:

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.

In your specific case, you have a multi-select listbox. It makes in first place no sense to have a "nothing selected" option in such user interface element. You simply need to deselect everything in order to have a "nothing selected" state. This isn't possible in for example a single-select dropdown, because you cannot deselect the selected option in first place. Hence such user interface element has the need for a "nothing selected" option. But, again, this isn't needed for a multi-select listbox. I do however understand that it's useful to have an actionable element which automagically deselects everything within the listbox. This could have been done via a link or button somewhere near the listbox.

In any case, I've been able to reproduce the described issue in Mojarra 2.3.17. The root problem is that the "empty string submitted value" isn't represented by an empty string array anymore, but by a string array with a single item, an empty string. Therefore all checks related to "empty string submitted value" failed afterwards. I don't think that this is a bug in JSF itself, but that it's just a case of unexpected usage of a multi-select component.

You could work around this all by explicitly disabling the item during all phases other than the render response phase (the 6th phase). It'll be selectable but be automatically removed from the selected items as built-in measure against tampered requests. This way the "empty string submitted value" will be an empty array as expected.

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

Noted should be that this cannot be solved with a custom converter or validator. JSF would not allow the custom converter to have returned null, and this specific "Value is not valid" validation is done by built-in validator against tampered requests which cannot be replaced/disabled. Our best bet would probably have been to change/respecify the behavior of noSelectionOption="true" as this is indeed way too often misunderstood. It should probably internally be treated the same way as a disabled item.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • You are absolutly right - It makes in first place no sense to have a "nothing selected" option in such user interface element. The problem originated because I was unable understand how to deselect a single selected option. Obviously it's as simple as selecting (using Ctrl). Thank you once again! – areal Dec 22 '21 at 16:55
0

It looks like it is trying to validate the value and it cannot. Have you tried using a custom Validator instead?

  • 1
    Is this an answer to the question problem? If your intent is to post an answer then be more affirmative and do not leave a question mark in the answer otherwise your answer will be flagged by bots for removal. – Steve Dec 11 '21 at 15:22
  • Yes, as I wrote, I tried using OmniFaces SelectItemsConverter and even creating my own custom converter, but with no luck. – areal Dec 13 '21 at 12:12
  • You say SelectItemsConverter, but I am asking if you have tried using a custom Validator. Note that Faces does conversion (using Converter API), but it also does validation (using Validator API). And the error message seems to indicate the validation fails. – Manfred Riem Dec 13 '21 at 19:22