Search code examples
vaadinvaadin-flowvaadin14

Is there a way to have the Combobox render the selected value like the Select in Vaadin Flow?


For example in the Select component the selected value is rendered as shown here. However when it comes to the ComboBox it is not rendered, only on the dropdown as shown here. I need to use the ComboBox because I need the search functionality, that is to have the item selected as they type in the value because there may be a lot of values. Ideally it would be great to merge the Select and ComboBox but barring that I'm wondering if there's a way to render the selected value.


Solution

  • EDIT TO ADD: said feature has been implemented in Vaadin 24 via the HasPrefix and HasSuffix interfaces: https://github.com/vaadin/platform/issues/3637 So you can use setPrefixComponent to add a prefix Icon to any component that supports it, including a ComboBox.

    You can't use an arbitrary Renderer, because the text input is, well, a text input. As noted in the comments below the question, what you're really after is an icon in front of the value of the input, and while there's no nice API in ComboBox for this, you can frankenstein together a solution using the prefix slot of the vaadin-text-field input. I've adapted an example using the Cookbook recipe here. Note that there's an enhancement request that would make handling prefix/suffix components in ComboBox easier: https://github.com/vaadin/flow-components/issues/1594

    public class AboutView extends Div {
    
        public AboutView() {
            ComboBox<Person> comboBox = new ComboBox<>();
            comboBox.setItems(getPersons());
            // Renderer for the drop down
            comboBox.setRenderer(new ComponentRenderer<Div, Person>(person -> {
                Div container = new Div();
                container.add(person.getIcon().create(), new Span(person.getName()));
                return container;
            }));
            // on value change: either clear the prefix slot or create a new Icon there
            comboBox.addValueChangeListener(e -> {
                Person p = e.getValue();
                if (p == null) {
                    PrefixUtil.clearSlot(comboBox, "prefix");
                    return;
                }
                PrefixUtil.setPrefixComponent(comboBox, p.getIcon().create());
            });
            comboBox.setItemLabelGenerator(Person::getName);
            add(comboBox);
        }
    
        public List<Person> getPersons() {
            List<Person> persons = new ArrayList<>();
            Person person1 = new Person("Foo", VaadinIcon.ARROW_BACKWARD);
            Person person2 = new Person("Bar", VaadinIcon.BAR_CHART);
            Person person3 = new Person("Baz", VaadinIcon.PUZZLE_PIECE);
            persons.add(person1);
            persons.add(person2);
            persons.add(person3);
            return persons;
        }
    
        public static class Person {
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            private String name;
    
            public VaadinIcon getIcon() {
                return icon;
            }
    
            public void setIcon(VaadinIcon icon) {
                this.icon = icon;
            }
    
            private VaadinIcon icon;
    
            public Person(String name, VaadinIcon icon) {
                this.name = name;
                this.icon = icon;
            }
        }
    
        public static class PrefixUtil {
    
            private static Stream<Element> getElementsInSlot(HasElement target,
                                                             String slot) {
                return target.getElement().getChildren()
                        .filter(child -> slot.equals(child.getAttribute("slot")));
            }
    
            public static void setPrefixComponent(Component target, Component component) {
                clearSlot(target, "prefix");
    
                if (component != null) {
                    component.getElement().setAttribute("slot", "prefix");
                    target.getElement().appendChild(component.getElement());
                }
            }
    
            private static void clearSlot(Component target, String slot) {
                getElementsInSlot(target, slot).collect(Collectors.toList())
                        .forEach(target.getElement()::removeChild);
            }
    
            private static Component getChildInSlot(HasElement target, String slot) {
                Optional<Element> element = getElementsInSlot(target, slot).findFirst();
                if (element.isPresent()) {
                    return element.get().getComponent().get();
                }
                return null;
            }
    
            public static Component getPrefixComponent(Component target) {
                return getChildInSlot(target, "prefix");
            }
        }
    }