When we started on our upgrade to Vaadin20+ I decided against using ComboBox.
Instead I just use a TextField with an anchored Grid. The 80% version of that was easy enough, but the remaining 20% has been hard, so I’ve revisited ComboBox from time to time.
However, it seems like every time I revisit it I find a new bug.
If I write it quickly and tab out, the text remains
If I write it slowly and tab out, the text is removed.
Honestly, I don’t know if I can be bothered registering yet another bug.
It also seems terribly slow.
Displaying the Country list is quickish, but every filtering seems to take close to 1s.
I have a Country dropdown in my app as well, using my custom component.
That uses a Grid, filters on the server-side and is more or less immediate.
I also still fundamentally disagree with the design of ComboBox; The idea that the input field is just a filter to find a value in the list. This means if you write “finnland” (slowly) and tab out, the value just disappears instead of being marked as invalid. IMHO this makes it wholly unfit for a data entry application.
Hi Guttorm! You’re always welcome to rant and give critical feedback. I’m glad you’re still sticking with Vaadin.
I don’t think anyone disagrees with the issues you describe. There are bugs, longstanding ones. Priorities aren’t aligned so that we’d get to fix all of them.
The Flow example on the docs page is terribly slow indeed. I’m not sure why. I don’t think that’s representative of the general performance of server-side data providers. The React and Lit examples are instant, since they are completely client side.
The behavior how the input field is cleared, if it doesn’t match any item in the list, is wrong. That’s one more poor design choice on my/our part (almost a decade ago!), which I would like to see fixed. Maybe you can configure the behavior? I don’t remember the API. And changing the behavior now could be a breaking change to all existing apps, so we need to fix it with care. It used to be similarly wrong in Date Picker, if I remember correctly, but that behavior has been fixed at some point.
Thanks, and let me say that you Vaadin guys have always impressed me with how positively you engage with the community. I try to be nice, but I lean more towards the Linus-school of communication
Priorities are hard, but it is frustrating when some issue in the forum points to a several years old unsolved github issue. I do wish Vaadin would prioritize quality and parity with v8 before stuff like AI, or even your Card component (It does look nice though)
Rant or not, but I think your case is related to this ticket
Which gave me motivation to introduce variant of the ComboBox in Directory
It re-uses the client side component, it is just different Java wrapper which ditches the lazy loading mechanism. This version is more suitable for fast inputs you mentioned as the items are preloaded and there is no delay due server round trip when combo is opened, which I think was your problem. However the down side is that this version is not good if you have huge list of items and would like to have the lazy loading.
Interesting. This probably avoids the new bug that I encountered as well.
However, it is still ComboBox and I don’t agree with its behavior.
Our solution for speed is to open the drop-down on demand or when needed.
Users normally just write text and tab out. The server-side validates and opens drop-down if value is invalid.
Also, users only need to write enough to make the selection unique, so in our Country drop-down, you’d just write “fin” and tab to the next field. Server-side looks up and replaces value on screen with “Finland”.
Also, when we display a form, the data the drop-down displays comes from the original query, not from the drop-down itself, meaning there is only one query to display everything. Since I haven’t used ComboBox properly I haven’t checked if its lazy load extends to this, or if it is just for empty fields.
When we do display the drop-down, it is a filtered grid. That is a huge component and you’d expect it to maybe be slow, but it is not. You’ve done a good job there
So, all in all I’m happy with my ComboBox replacement. At least the part that is server-side.
Client-side is where it breaks down, and why I go back to check up on ComboBox from time to time.
( Issue is to treat input field and dropdown as a pair, so that clicking outside both closes the drop-down, but clicking in the input field doesn’t, and to distinguish between blur caused by tab out of input field and arrow-down in to the drop-down )
I gotta admit I sure have (internally) given my own rants about ComboBox too, but I don’t think it is that bad these days if one learns its tricks. The “missing recipe”, is to to keep it always in the “allow new items” mode. Even if you wouldn’t even allow new items :-) Then you are in complete control of what happens when e.g. typing somehing and hitting tab key (before combobox has even triggered the server visite). Here is one quickly typed example:
package in.virit.views;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import org.jetbrains.annotations.NotNull;
import java.util.TreeSet;
import java.util.stream.Stream;
@Route(layout = Layout.class)
public class ComboBoxView extends VerticalLayout {
public TreeSet<Person> people = new TreeSet<>();
{
// 100 random persons
String names[] = {"John", "Jane", "Jack", "Jill", "Joe", "Jenny", "Jim", "Jill", "Jill", "Jill"};
String surnames[] = {"Doe", "Smith", "Johnson", "Brown", "White", "Black", "Green", "Yellow", "Red", "Blue"};
for (int i = 0; i < 100; i++) {
String name = names[(int) (Math.random() * names.length)] + " " + surnames[(int) (Math.random() * surnames.length)];
people.add(new Person(name));
}
}
public ComboBoxView() {
PersonSelect personSelect = new PersonSelect();
personSelect.addValueChangeListener(e -> {
Notification.show("Selected: " + e.getValue());
});
add(personSelect);
}
public Stream<Person> filterPersons(String filter, int limit, int offset) {
System.out.println("filterPersons: " + filter + " " + limit + " " + offset);
return people.stream().filter(p -> p.name().toLowerCase().contains(filter.toLowerCase()))
.skip(offset).limit(limit);
}
public Person addPerson(String name) {
Person person = new Person(name);
people.add(person);
return person;
}
record Person(String name) implements Comparable<Person> {
@Override
public int compareTo(@NotNull ComboBoxView.Person o) {
return name.compareTo(o.name);
}
}
class PersonSelect extends ComboBox<Person> {
{
// lazy loading connection to backend
setItems(q -> filterPersons(q.getFilter().get(), q.getLimit(), q.getOffset()));
setItemLabelGenerator(Person::name);
setAllowCustomValue(true);
addCustomValueSetListener(e -> {
System.out.println("Custom value set: " + e.getDetail());
// The nasty thing in ComboBox is that it might call customValueSet even though
// a value already exists in the list...
// If found existing, just select it otherwise create new and select that
// Note, all kind of logic could be used here. E.g. in this form the example
// expands "Jack Bl" to "Jack Black" and selects that instead of adding new, which might not be desired...
filterPersons(e.getDetail(), 1, 0).findFirst()
.ifPresentOrElse(existing -> {
Notification.show("Found existing, reusing instead of adding new");
setValue(existing);
}, () -> {
// This could be a no op as well if new items should not be allowed
Notification.show("Actually inserting new person....");
Person person = addPerson(e.getDetail());
setValue(person);
});
});
}
}
}
The first thing I notice is the same slowness as I saw in the doc page; Each filtering takes around 1s.
I see this both in Firefox and Chrome.
This is no deal breaker if it otherwise works.
Out of the box, your example seems to work.
However, I don’t want autoOpen, so I turned that off.
Now, when I write “jack” and tab out, nothing happens. No ValueChanged or CustomValueSet event.
If I first open/close the dropdown and then write “jack”, it works.
Looks like this is an issue with lazy-load.
I’m afraid that just confirms my opinion of ComboBox
Hi, I didn’t even know tha tthe autoOpen flag exists. With it indeed looks like the customvalue event is not fired. I’d be ready to doom that as a bug (as the behaviour in the default is different).
I also agree the timeout combobox uses feels bit long to trigger the filtering. At least it should be configurable.
I wonder if there are tickets for these already, probably quite commonly faced issues