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

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.

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

Related

vaadin 14.2.2 Grid not creating an output

I am playing around with vaadin 14.2.2 for testing purpose. However, I got to the first problem using Grid immediately. The Example from https://vaadin.com/docs/v14/flow/components/tutorial-flow-grid.html
List<Person> people = Arrays.asList(
new Person("Nicolaus Copernicus", 1543),
new Person("Galileo Galilei", 1564),
new Person("Johannes Kepler", 1571));
// Create a grid bound to the list
Grid<Person> grid = new Grid<>();
grid.setItems(people);
grid.addColumn(Person::getName).setHeader("Name");
grid.addColumn(Person::getYearOfBirth)
.setHeader("Year of birth");
layout.add(grid);
does not generate an output in the visualization. Also the hint in several GitHub issues to use
grid.setSizeFull();
does not solve this issue.
Has anybody any idea how to solve this?
The following snippet works for me on Vaadin 14.2.2 + Spring:
#Route("MainView")
public class MainView extends VerticalLayout {
public MainView() {
List<Person> people = Arrays.asList(
new Person("Nicolaus Copernicus", 1543),
new Person("Galileo Galilei", 1564),
new Person("Johannes Kepler", 1571));
// Create a grid bound to the list
Grid<Person> grid = new Grid<>();
grid.setItems(people);
grid.addColumn(Person::getName).setHeader("Name");
grid.addColumn(Person::getYearOfBirth).setHeader("Year of birth");
add(grid);
}
class Person {
String name;
Integer yearofbirth;
public Person(String name, Integer yearofbirth) {
this.name = name;
this.yearofbirth = yearofbirth;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getYearOfBirth() {
return yearofbirth;
}
public void setYearOfBirth(Integer yearofbirth) {
this.yearofbirth = yearofbirth;
}
}
}

Vaadin excel export with converter values

I'm trying to export an Excel corresponding to a FilterTable using Vaadin TableExport. That Filtertable has some columns storing Dates and other class type elements, so I'm using setConverter function to print them as specific Strings:
filerTable.setConverter("dateColumn", dateConverter);
filerTable.setConverter("myClassColumn", myClassConverter);
dateConverter and myClassConverter are instances of some classes to print that column values as Strings.
The problem comes when I want to export the table as an Excel: That setConverter conversions are not being applied to the output file. For example, date cells are being exported as string ('42741,0080787037' instead of '06/01/17 0:11'). The code section to export the Excel file is:
ExcelExport exp = new ExcelExport(new CustomTableHolder(filerTable), "excel.xls");
exp.setRowHeaders(true);
exp.export();
Is there any way to export the table exactly as shown, having applied setConverter function?
Looking at the add-on sources, it seems that this feature is supported but 2 things have to happen in order for it to work:
you have to use a PropertyFormatTable (nothing fancy, just a wrapper over table for this custom purpose)
set the setUseTableFormatPropertyValue(true) on the ExcelExport
Code:
public class ExcelExportTableComponent extends VerticalLayout {
public ExcelExportTableComponent() {
// basic table configuration
Table table = new PropertyFormatTable();
BeanItemContainer<Person> itemContainer = new BeanItemContainer<>(Person.class);
table.setContainerDataSource(itemContainer);
table.setConverter("age", new AgeConverter());
// add some dummy data to the table
Random random = new Random();
for (int i = 0; i < 10; i++) {
itemContainer.addItem(new Person("Name " + i, "Surname " + i, random.nextInt(99) + 1));
}
// add components to the layout
addComponent(table);
addComponent(new Button("Export to excel", event -> {
ExcelExport excelExport = new ExcelExport(table);
excelExport.setUseTableFormatPropertyValue(true);
excelExport.excludeCollapsedColumns();
excelExport.setReportTitle("Demo Report");
excelExport.export();
}));
}
// basic bean for data binding
public static class Person {
private String name;
private String surname;
private int age;
public Person(String name, String surname, int age) {
this.name = name;
this.surname = surname;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// custom converter
private static class AgeConverter implements Converter<String, Integer> {
#Override
public Integer convertToModel(String value, Class<? extends Integer> targetType, Locale locale) throws ConversionException {
return Integer.valueOf(value.substring(0, value.indexOf(" years")));
}
#Override
public String convertToPresentation(Integer value, Class<? extends String> targetType, Locale locale) throws ConversionException {
return String.valueOf(value) + " years";
}
#Override
public Class<Integer> getModelType() {
return Integer.class;
}
#Override
public Class<String> getPresentationType() {
return String.class;
}
}
}
Output:

Vaadin - Does ComboBox Lazy Load by Default?

Vaadin 7.6.2
BeanItemContainer
BeanItemContainer<CountryBean> countryBeanContainer
= new BeanItemContainer<>(CountryBean.class);
countryBeanContainer.addAll(CountryData.list);
country.setContainerDataSource(countryBeanContainer);
country.setItemCaptionMode(AbstractSelect.ItemCaptionMode.PROPERTY);
country.setItemCaptionPropertyId("name");
country.setTextInputAllowed(true);
...
...
CountryBean
public class CountryBean {
private String value;
private String name;
public CountryBean(String value, String name) {
this.value = value;
this.name = name;
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
CountryData
public abstract class CountryData {
public static final List<CountryBean> list =
Collections.unmodifiableList(Arrays.asList(
new CountryBean("AF", "Afghanistan"),
new CountryBean("AX", "Ă…land Islands"),
new CountryBean("AL", "Albania"),
new CountryBean("DZ", "Algeria"),
new CountryBean("AS", "American Samoa"),
new CountryBean("AD", "Andorra"),
new CountryBean("AO", "Angola"),
new CountryBean("AI", "Anguilla"),
...
...
So, I have this setup running in a ComboBox and it works great. But my question is: Is this list of 200+ countries compiled into the client side code, or is it lazy loaded from the server as the user types or pages through the list of choices? I would like to understand how this works, because I may need to have (say) 5 country fields in my UI.
ComboBox is handling lazy loading between client side and server out of the box. Only the rows which are visible in the dropdown are fetched from server to client. So when you do filtering or navigate between different pages on the dropdown, it fetches only those rows from the server.

Right-align column contents in Vaadin Grid?

In the new Vaadin Grid widget (alternative to venerable Table), how does one right-align numbers or other content in a column?
The simplest way I can think of is to define your own CSS classes and style generator, pretty much similar to what I'd had done when working with tables.
#Theme("mytheme")
#Widgetset("com.matritza.MyAppWidgetset")
public class MyUI extends UI {
#WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
// meh, default stuff
}
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
// create a grid
Grid grid = new Grid("Grid test");
// create a specific container for the grid to hold our persons
BeanItemContainer<Person> container = new BeanItemContainer<>(Person.class);
grid.setContainerDataSource(container);
// define our own style generator
grid.setCellStyleGenerator(new Grid.CellStyleGenerator() {
#Override
public String getStyle(Grid.CellReference cellReference) {
if ("age".equals(cellReference.getPropertyId())) {
// when the current cell is number such as age, align text to right
return "rightAligned";
} else {
// otherwise, align text to left
return "leftAligned";
}
}
});
// generate some dummy data
for (int i = 0; i < 10; i++) {
container.addItem(new Person("Name " + i, "Surname " + i, i));
}
layout.addComponent(grid);
}
// basic class to populate the grid in a fast & simple way
public class Person {
private String name;
private String surname;
private int age;
private Person(String name, String surname, int age) {
this.name = name;
this.surname = surname;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
And the basic CSS styles
#mixin mytheme {
#include valo;
// Insert your own theme rules here
.leftAligned {
text-align: left;
}
.rightAligned {
text-align: right;
}
}
And you should see something like
By the way, in Java 8 and later, the new Lambda syntax for that style generator would be:
grid.setCellStyleGenerator(( Grid.CellReference cellReference ) -> {
if ( "age".equals( cellReference.getPropertyId() ) ) {
// when the current cell is number such as age, align text to right
return "rightAligned";
} else {
// otherwise, align text to left
return "leftAligned";
}
});
One can also use already present styles like v-align-right, v-align-middle, etc. Just see what themes like Valo already contain, and extend existing themes only when needed.
Here's simple example how one could implement cell generator with regexp (matching one or multiple fields based on name of field)
public class RegexpCellStyleGenerator implements CellStyleGenerator {
private String regex = ".*"; // defaults all
String style = "v-align-right"; // default is here just as example
// special version useful only when one wants to style all fields inside grid
public RegexpCellStyleGenerator(String style) {
super();
this.style = style;
}
public RegexpCellStyleGenerator(String regex, String style) {
super();
this.regex = regex;
this.style = style;
}
#Override
public String getStyle(CellReference cellReference) {
String propertyId = cellReference.getPropertyId().toString();
if (propertyId.matches(regex)) {
return style;
}
return null;
}
and as this is only partially useful as most grids have multiple fields composite generator could be handy
public class CompositeCellStyleGenerator implements CellStyleGenerator {
List<CellStyleGenerator> generators = new ArrayList<>();
public CompositeCellStyleGenerator() {}
public void addCellStyleGenerator(CellStyleGenerator generator) {
generators.add(generator);
}
#Override
public String getStyle(CellReference cellReference) {
List<String> styles = new ArrayList<>();
for (CellStyleGenerator generator : generators) {
String style = generator.getStyle(cellReference);
if (style != null) {
styles.add(style);
}
}
if (!styles.isEmpty()) {
return styles.stream().collect(Collectors.joining(" "));
}
return null;
}
Composite generator joins all styles together and can be used like this. If there's multiple styles for one column both are applied.
RegexpCellStyleGenerator yearGenerator = new RegexpCellStyleGenerator("yearOfFoundation", "v-align-right");
RegexpCellStyleGenerator nameGenerator = new RegexpCellStyleGenerator("name", "v-align-center");
RegexpCellStyleGenerator nameGenerator2 = new RegexpCellStyleGenerator("name", "v-label-huge");
CompositeCellStyleGenerator compositeGenerator = new CompositeCellStyleGenerator();
compositeGenerator.addCellStyleGenerator(yearGenerator);
compositeGenerator.addCellStyleGenerator(nameGenerator);
compositeGenerator.addCellStyleGenerator(nameGenerator2);
grid.setCellStyleGenerator(compositeGenerator);
Note that composite generator can use generic generators like one with regexp definitions and more complex use case specific ones.
Hope this helps those who try to find easy way to style cells. Happy Experimenting.

How to select combobox by id or value using with BeanItemContainer?

I am using BeanItemContainer for my comboboxes to satisfy key-value pairs.
#SuppressWarnings("serial")
public class ComboBoxItem implements Serializable {
private String id;
private String description;
public ComboBoxItem(final String id, final String description) {
this.id = id;
this.description = description;
}
public final void setId(final String id) {
this.id = id;
}
public final void setDescription(final String description) {
this.description = description;
}
public final String getId() {
return id;
}
public final String getDescription() {
return description;
}
}
I created a sample combobox as below
List<ComboBoxItem> lstAuctionDateList = new ArrayList<ComboBoxItem>();
lstAuctionDateList.add(new ComboBoxItem("all", "All"));
BeanItemContainer<ComboBoxItem> auctionDateItems = new BeanItemContainer<ComboBoxItem>(ComboBoxItem.class,
lstAuctionDateList);
final ComboBox cbAuctionDate = new ComboBox("Auction Date", auctionDateItems);
cbAuctionDate.addStyleName("small");
cbAuctionDate.setNullSelectionAllowed(false);
cbAuctionDate.setTextInputAllowed(false);
cbAuctionDate.setItemCaptionPropertyId("description");
cbAuctionDate.addValueChangeListener(new ValueChangeListener() {
public void valueChange(final ValueChangeEvent event) {
if (cbAuctionDate.getValue() != null) {
System.out.println(((ComboBoxItem) cbAuctionDate.getValue()).getId());
System.out.println(((ComboBoxItem) cbAuctionDate.getValue()).getDescription());
}
}
});
It is fine but I can't select any of combobox items by using below codes
cbAuctionDate.select("all");
cbAuctionDate.select("All");
cbAuctionDate.setValue("all");
cbAuctionDate.setValue("All");
What am I wrong ? How can I select my comboxes by programmatically ?
when using a (bean) container and adding items, the identity of the item itself is used as the itemId in the container. E.g. cbActionDate.select(lstAuctionDateList[0]) should work.
You either have yo make your objects immutable or use ways to tell the container, what it has to use for an id (E.g. setBeanIdProperty("id") or setBeanIdResolver).
Making the object immutable should be easy right now (make the class and the private attributes final, drop the setters and let your IDE generate equals and hashCode for you)
You don't need the cbAuctionDate.addItem("All") call, you already have such a item in your collection
I would try it that way:
List<ComboBoxItem> lstAuctionDateList = new ArrayList<ComboBoxItem>();
ComboBoxItem allItems= new ComboBoxItem("all", "All");
lstAuctionDateList.add(allItems);
....
...
cbAuctionDate.select(allItems);
Now I created custom ComboBox component for my problem
public class ComboBox extends CustomComponent implements Serializable {
private com.vaadin.ui.ComboBox comboBox;
private BeanItemContainer<ComboBoxItem> entries = new BeanItemContainer<ComboBoxItem>(ComboBoxItem.class);
public ComboBox() {
comboBox = new com.vaadin.ui.ComboBox();
comboBox.addStyleName("small");
comboBox.setNullSelectionAllowed(false);
comboBox.setTextInputAllowed(false);
setCompositionRoot(comboBox);
}
public ComboBox(final String caption) {
comboBox = new com.vaadin.ui.ComboBox();
comboBox.addStyleName("small");
comboBox.setNullSelectionAllowed(false);
comboBox.setTextInputAllowed(false);
setCaption(caption);
setCompositionRoot(comboBox);
}
public ComboBox(final String caption, final List<ComboBoxItem> items) {
comboBox = new com.vaadin.ui.ComboBox();
comboBox.addStyleName("small");
comboBox.setNullSelectionAllowed(false);
comboBox.setTextInputAllowed(false);
setCaption(caption);
if (items != null && items.size() > 0) {
entries.addAll(items);
comboBox.setContainerDataSource(entries);
comboBox.setItemCaptionMode(ItemCaptionMode.PROPERTY);
addItems(entries);
comboBox.select(items.get(0));
comboBox.setItemCaptionPropertyId("description");
}
setCompositionRoot(comboBox);
}
public final void addItems(final List<ComboBoxItem> items) {
if (items != null && items.size() > 0) {
entries.addAll(items);
comboBox.setContainerDataSource(entries);
comboBox.setItemCaptionMode(ItemCaptionMode.PROPERTY);
addItems(entries);
comboBox.select(items.get(0));
comboBox.setItemCaptionPropertyId("description");
}
}
private void addItems(final BeanItemContainer<ComboBoxItem> items) {
comboBox.addItems(items);
}
public final void addItem(final ComboBoxItem item) {
if (item != null) {
comboBox.setContainerDataSource(entries);
comboBox.addItem(item);
comboBox.setItemCaptionPropertyId("description");
}
}
public final void selectByIndex(final int index) {
Object[] ids = comboBox.getItemIds().toArray();
comboBox.select(((ComboBoxItem) ids[index]));
}
public final void selectById(final String id) {
Object[] ids = comboBox.getItemIds().toArray();
for (int i = 0; i < ids.length; i++) {
if (((ComboBoxItem) ids[i]).getId().equals(id)) {
selectByIndex(i);
break;
}
}
}
public final void selectByItemText(final String description) {
Object[] ids = comboBox.getItemIds().toArray();
for (int i = 0; i < ids.length; i++) {
if (((ComboBoxItem) ids[i]).getDescription().equals(description)) {
selectByIndex(i);
break;
}
}
}
public final int getItemCount() {
return comboBox.getItemIds().toArray().length;
}
public final String getSelectedId() {
return ((ComboBoxItem) comboBox.getValue()).getId();
}
public final String getSelectedItemText() {
return ((ComboBoxItem) comboBox.getValue()).getDescription();
}
public final void addValueChangeListener(final ValueChangeListener listener) {
comboBox.addValueChangeListener(listener);
}
}
and below is test codes
final ComboBox combo = new ComboBox("My ComboBox");
combo.addItem(new ComboBoxItem("all", "All"));
// Add with list
List<ComboBoxItem> items = new ArrayList<ComboBoxItem>();
items.add(new ComboBoxItem("one", "One"));
items.add(new ComboBoxItem("two", "Two"));
items.add(new ComboBoxItem("three", "Three"));
combo.addItems(items);
combo.addItem(new ComboBoxItem("four", "Four"));
combo.addItem(new ComboBoxItem("five", "five"));
combo.selectByIndex(3);
combo.addValueChangeListener(new ValueChangeListener() {
public void valueChange(final ValueChangeEvent event) {
System.out.println(combo.getSelectedId() + " --- " + combo.getSelectedItemText());
}
});

Resources