Search code examples
javacomboboxvaadinvaadin7

Adding new elements on ComboBox


I'm developing a Vaadin app, and I've decided to use the SuggestingComboBox idea, that allows better searches between items. This works great, but now I can't figure out how to add new elements, that aren't already in the container with the setNewItemHandler method.
For refference, here is the SuggestingComBox code:

public class SuggestingComboBox extends ComboBox {

    public SuggestingComboBox() {
        setItemCaptionMode(ItemCaptionMode.PROPERTY);
        setItemCaptionPropertyId("name");
    }

    @Override
    protected Filter buildFilter(String filterString, 
            FilteringMode filteringMode) {
        return new SuggestingContainer.SuggestionFilter(filterString);
    }
}

And the SuggestingContainer code, that comes along the combobox:

public abstract class SuggestingContainer<T> extends BeanItemContainer<T> {

    public SuggestingContainer(Class<? super T> type)
            throws IllegalArgumentException {
        super(type);
    }

    @Override
    protected void addFilter(Filter filter) 
            throws UnsupportedFilterException {
        if(filter instanceof SuggestionFilter) {
            SuggestionFilter suggestionFilter = (SuggestionFilter) filter;
            filterItems(suggestionFilter.getFilterString());
        } else 
            super.addFilter(filter);
    }

    //This method is to be overriden
    protected abstract void filterItems(String filterString);

   public static class SuggestionFilter implements Container.Filter {
       private String filterString;

       public SuggestionFilter(String filterString) {
           this.filterString = filterString;
       }

       public String getFilterString() {
           return filterString;
       }

       @Override
       public boolean passesFilter(Object itemId, Item item) 
               throws UnsupportedOperationException {
           return false;
       }

       @Override
       public boolean appliesToProperty(Object propertyId) {
           return false;
       }
  }

This container is used as this:

public SuggestingField extends CustomField<Model> {
    private SuggestingComboBox suggestingCB;
    private SuggestingContainer<ObjectItem> container;

    public SuggestingField() {
        suggestingCB = new SuggestingComboBox();
        container = new SuggestingContainer<ObjectItem>(ObjectItem.class) {
            @Override
            protected void filterItems(String filterString) {               
                removeAllItems();

                List<Model> result; //Obtain here the results
                List<ObjectItem> lItems = result.stream()
                        .map(m -> new ObjectItem(m.getId()+"", m.toString(), m))
                        .collect(Collectors.toList());
                addAll(lItems);
            }
        };
        suggestingCB.setContainerDataSource(container);
        suggestingCB.setImmediate(true);
        suggestingCB.setItemCaptionPropertyId("text");
    }
}

Also, I have a POJO Model and that ObjectItem class, that I have to use, beacuse I don't get to work the toString caption mode.

public class Model {
    private int id;
    private String name;
    //Getters and setters; equals/hash
}
public class ObjectItem {
    private String id;
    private String text;
    private Object o;
    //Equals/hash and getters/setters
}

So, my problem is: I'm trying to allow creating new Model(s) in the field, using the setNewItemHandler method. This is what I've tried so far.

public SuggestingField extends CustomField<Model> {
...
    public void setNewElementsAllowed(boolean allowed) {
        this.suggestingCB.setNewItemsAllowed(allowed);
        this.suggestingCB.setImmediate(allowed);
        if(allowed) {
            suggestingCB.setNewItemHandler(caption -> {
                Model m = new Model();
                m.setNombre(caption);

                setValue(m);
            });
        }
    }
    public void setValue(Model m) {
        super.setValue(m);
        if(m == null)
            suggestingCB.setValue(null);
        else {
            ObjectItem it = new ObjectItem(""+m.getId(),
                m.getName(), m);
            if(container.size() == 0 || !container.containsId(it.getId()))
                container.addItem(it);

            suggestingCB.setValue(it);
            suggestingCB.select(it);
        }
    }

After debuggind this code, I've found that the newElementHandler is used, and it adds a new item to the container, but nothing is shown in the ComboBox and the next handler's execution, the container is empty, as if no item was inserted.


Solution

  • I've been able to identify the error. When you select one item in a ComboBox, the filterString becomes "", so, if you select something that isn't in the DB, and recall the query, it won't be in the List<Model> result and thus, it will be unselected.
    A way to overcome this is to modify SuggestingField as here:

    public SuggestingField extends CustomField<Model> {
        private Model model;
        ...
        public SuggestingField() {
            ...
            container = new SuggestingContainer<ObjectItem>(ObjectItem.class) {
                @Override
                protected void filterItems(String filterString) {               
                    removeAllItems();
    
                    if("".equals(filterString)) {
                        if(model != null) {
                            ObjectItem it = new ObjectItem();
                            it.setId(model.getId()+"");
                            it.setText(model.toString());
                            it.setObject(model);
    
                            addItem(it);
                            return;
                        }
                    }
    
                    List<Model> result; //Obtain here the results
                    List<ObjectItem> lItems = result.stream()
                        .map(m -> new ObjectItem(m.getId()+"", m.toString(), m))
                        .collect(Collectors.toList());
                    addAll(lItems);
                }
            };
            suggestingCB.addValueChangeListener(ev -> {
                this.model = (Model) suggestingCB.getValue();
            });
    

    This allows to maintain the new item through filters.