How to set combobox value in grid?

I have an editable combo box in a grid. When a new name is added to the combo box, it becomes part of the list selection for the combo box so it can be selected for other rows.

Here’s the issue, when I add a few rows and enter a name or two and then change rows, the names appears to be copied to other rows. Example - I add 4 rows, then enter John in the third row and Pete in the fourth row. If I alt-tab back up through the rows, John will appear in the third and second row.

Anyone else had a similar problem? I’m guessing the issue is related to not setting the selected combobox value correctly or the editor being in a wierd state.

Source:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding;
import com.vaadin.navigator.View;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.Grid.SelectionMode;

/**
 * This view contains an editable selection list of names and a fixed selection
 * list of colors. When new names are typed into the name field, the name will
 * become part of the list.
 *
 */
@SuppressWarnings("serial")
@SpringView(name = "gridtest")
public class GridTestView extends CssLayout implements View {

   Grid<Row> grid = new Grid<Row>();
   List<String> colors = Arrays.asList("Red", "Green", "Blue");
   List<Row> items = new ArrayList<Row>();
   List<Name> names = new ArrayList<Name>();

   public GridTestView() {
      setSizeFull();

      grid.setWidth("300px");
      grid.setSelectionMode(SelectionMode.NONE);
      addComponent(grid);

      Binder<Row> binder = new Binder<Row>();

      ComboBox<Name> nameComboBox = createEditableComboBox();
      Binding<Row, Name> nameBind = binder.bind(nameComboBox, Row::getName, Row::setName);
      Column<Row, String> nameColumn = grid.addColumn(row -> row.getName() != null ? row.getName().getName() : "")
            .setCaption("Name");
      nameColumn.setEditorBinding(nameBind);

      ComboBox<String> colorCombo = new ComboBox<String>("Color", colors);
      Binding<Row, String> colorBind = binder.bind(colorCombo, Row::getColor, Row::setColor);
      Column<Row, String> colorColumn = grid.addColumn(row -> row.getColor()).setCaption("Color");
      colorColumn.setEditorBinding(colorBind);

      grid.addItemClickListener(e -> {
         int i = items.indexOf(e.getItem());
         if (i > -1) {
            grid.getEditor().editRow(i);
         }
      });

      grid.getEditor().setBinder(binder);
      grid.getEditor().setBuffered(false);
      grid.getEditor().setEnabled(true);

      Button addRow = new Button("Add Row");
      addComponent(addRow);
      addRow.addClickListener(e -> {
         items.add(new Row());
         grid.setItems(items);
      });
   }

   private ComboBox<Name> createEditableComboBox() {
      ComboBox<Name> cbx = new ComboBox<Name>();

      cbx.setItems(names);
      cbx.setItemCaptionGenerator(Name::getName);
      cbx.setNewItemHandler(s -> {

         // Return if nothing to do.
         if (s == null || s.length() == 0)
            return;

         // trim spaces
         s = s.trim();

         // Check if we have the entry in the list already. If we do, replace
         // the existing's label with our label. This allows a user to modify
         // labels, such as john -> John
         for (Name n : names) {
            if (s.equalsIgnoreCase(n.getName())) {
               n.setName(s);
               cbx.setSelectedItem(n);
               return;
            }
         }
         ;

         // It didn't exist, create new Name and add it to the
         // list of variants
         Name n = new Name(s);
         names.add(n);

         // Update combobox content and set it as the selected value
         cbx.setItems(names);
         cbx.setSelectedItem(n);
      });

      return cbx;
   }

   public class Name {
      private String name = null;

      public Name(String name) {
         setName(name);
      }

      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }
   }

   public class Row {
      Name name = null;
      String color = null;

      public Row() {
         // get next color in the sequence
         color = colors.get(items.size() % colors.size());
      }

      public Name getName() {
         return name;
      }

      public void setName(Name name) {
         this.name = name;
      }

      public String getColor() {
         return color;
      }

      public void setColor(String color) {
         this.color = color;
      }
   }

}

Have you tried following, add cbx.setEmptySelectionAllowed(true) to your createEditableComboBox() method.

Thanks Tatu, much appreciated. I’ll give that a try.

On a completely different subject. I was playout with some of the grid and grid layout components and in the process was speculating when changes are being sent to the client. Example - is there a performance difference between:

CssLayout cl = new CssLayout();
parent.add(cl);

for (item i : items) {
  cl.addComponent(..)
}

vs.

CssLayout cl = new CssLayout();

for (item i : items) {
  cl.addComponent(..)
}

parent.add(cl);

There are some bundling of messaging in batches, to reduce impact on performance, but I myself prefer that

CssLayout cl = new CssLayout();

for (item i : items) {  
  cl.addComponent(..)
}

parent.add(cl);

is the better pattern.

Excellent, thanks for the input Tatu.