Skip to content

Instantly share code, notes, and snippets.

@canthony
Created October 16, 2013 11:03
Show Gist options
  • Save canthony/7006061 to your computer and use it in GitHub Desktop.
Save canthony/7006061 to your computer and use it in GitHub Desktop.
Generated CheckBox column reflects the table selection. Selecting a table row set's the checkbox to checked. Deslecting a row sets the checkbox column to unchecked. Toggling the checkbox selects/deselcts the row. A bit ugly, frankly, but it works.
package uk.org.backstage;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.data.Property;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Table;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import javax.servlet.annotation.WebServlet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@SuppressWarnings("serial")
public class TableCheckBoxUI extends UI {
private static final String CHECKBOX_COLUMN = "selected";
/**
* This map keeps track of all the created and attached checkboxes, by ItemId
*/
protected Map<Object, CheckBox> itemIdToCheckbox = new HashMap<Object, CheckBox>();
/**
* This is the last selection value of the table. We need to keep track of what *was* selected,
* so that we can "deselect" the checkboxes.
*/
private Object lastSelectionValue;
/**
* A simple flag to stop a circular event
*/
private boolean ignorePropertyChangeEventInCheckBoxListener;
@WebServlet(urlPatterns = {"/tablecheckbox/*", "/VAADIN/*"}, asyncSupported = true)
@VaadinServletConfiguration(productionMode = false, ui = TableCheckBoxUI.class)
public static class Servlet extends VaadinServlet {
}
@Override
protected void init(VaadinRequest request) {
IndexedContainer container = createContainer();
Table table = createTable(container);
VerticalLayout mainLayout = new VerticalLayout();
mainLayout.addComponent(table);
mainLayout.setSizeFull();
setContent(mainLayout);
}
private Table createTable(IndexedContainer container) {
final Table table = new Table();
table.setImmediate(true);
table.setContainerDataSource(container);
table.setSizeFull();
table.setColumnExpandRatio("number", 1f);
table.addGeneratedColumn(CHECKBOX_COLUMN, new Table.ColumnGenerator() {
@Override
public Object generateCell(final Table source, final Object itemId, Object columnId) {
final CheckBox checkBox = new CheckBox("", isItemIdSelected(source, itemId));
checkBox.addValueChangeListener(new Property.ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent valueChangeEvent) {
// Don't react to the event if we're being changed from the table-value-change event
if (!ignorePropertyChangeEventInCheckBoxListener) {
Boolean selected = (Boolean) valueChangeEvent.getProperty().getValue();
if (selected) {
source.select(itemId);
} else {
source.unselect(itemId);
}
}
}
});
// Let's keep track of the checkboxes
checkBox.addAttachListener(new AttachListener() {
@Override
public void attach(AttachEvent event) {
itemIdToCheckbox.put(itemId, checkBox);
}
});
checkBox.addDetachListener(new DetachListener() {
@Override
public void detach(DetachEvent event) {
itemIdToCheckbox.remove(itemId);
}
});
return checkBox;
}
});
table.setMultiSelect(true);
table.setSelectable(true);
// Let's select a few initial items
Set<Integer> selectedItems = new HashSet<Integer>();
selectedItems.add(1);
selectedItems.add(4);
table.setValue(selectedItems);
this.lastSelectionValue = table.getValue();
this.ignorePropertyChangeEventInCheckBoxListener = false;
/*
* When the table value - i.e. the selection - changes, make sure
* any attached checkboxes are updated
*/
table.addValueChangeListener(new Property.ValueChangeListener() {
@Override
public void valueChange(Property.ValueChangeEvent event) {
ignorePropertyChangeEventInCheckBoxListener = true;
Object newSelectionValue = event.getProperty().getValue();
/* Deselect all of the old checkboxes, then reselect all of the new ones.
If we wanted to be really smart, you could work out which checkboxes you wanted to change.
I don't care about being smart right now */
setCheckBoxes(lastSelectionValue, false);
setCheckBoxes(newSelectionValue, true);
lastSelectionValue = newSelectionValue;
ignorePropertyChangeEventInCheckBoxListener = false;
}
});
/* Just some cosmetics : no caption for the checkbox, and make it the first column */
table.setColumnHeader(CHECKBOX_COLUMN, "");
table.setVisibleColumns(CHECKBOX_COLUMN, "name", "number");
return table;
}
/**
* Set the value of all of the checkboxes
* @param itemIdOrIds
* @param value
*/
private void setCheckBoxes(Object itemIdOrIds, boolean value) {
if (itemIdOrIds instanceof Collection) {
Collection ids = (Collection) itemIdOrIds;
for (Object id : ids) {
setCheckBox(id, value);
}
} else {
setCheckBox(itemIdOrIds, value);
}
}
/**
* Set the value of the given checkbox
* @param id
* @param value
*/
private void setCheckBox(Object id, boolean value) {
CheckBox checkBox = itemIdToCheckbox.get(id);
if (checkBox != null) {
checkBox.setValue(value);
}
}
/**
* Is the given itemId selected ?
* @param select
* @param itemId
* @return
*/
protected boolean isItemIdSelected(AbstractSelect select, Object itemId) {
Object value = select.getValue();
if (itemId == null || value == null) {
return false;
}
if (select.isMultiSelect()) {
return ((Collection) value).contains(itemId);
}
return itemId.equals(value);
}
private IndexedContainer createContainer() {
IndexedContainer container = new IndexedContainer();
container.addContainerProperty("name", String.class, null);
container.addContainerProperty("number", Integer.class, null);
// Generate lots of rows, to check performance isn't too bad
for (int i = 0; i < 1000; i++) {
addItem(container, "Bob", 10);
addItem(container, "Harry", 1);
addItem(container, "Margaret", 0);
addItem(container, "Glenda", 22);
addItem(container, "Boris", 0);
addItem(container, "Jessica", 24);
}
return container;
}
private void addItem(IndexedContainer container, String name, int number) {
Object itemId = container.addItem();
container.getItem(itemId).getItemProperty("name").setValue(name);
container.getItem(itemId).getItemProperty("number").setValue(number);
}
}
@zkendall
Copy link

zkendall commented Nov 7, 2014

I have an implementation of this but noticed it breaks when the table is updated (redrawn?). Such as adding a new item or sorting columns. The returned at CheckBox checkBox = itemIdToCheckbox.get(id); will be null, so the checkboxes will no longer be toggled.

This is because the attach listener is called first, but since the itemId's are already in the map, they aren't added further. Then the remove listener is called and the itemIds are removed.

@zkendall
Copy link

zkendall commented Nov 7, 2014

I solved this by removing the detatch listener but keeping the attach listener and adding: (Note the cast depends on your table's underlying datasource...May not be available for all.)

        // This handles removing the checkbox we're tracking when an item is removed.
        ((BeanItemContainer<?>)table.getContainerDataSource()).addItemSetChangeListener(new ItemSetChangeListener() {
            private static final long serialVersionUID = 1L;
            @Override
            public void containerItemSetChange(ItemSetChangeEvent event) {
               Collection<?> existing = event.getContainer().getItemIds();
               Iterator<?> it = itemIdToCheckbox.keySet().iterator();
               while(it.hasNext()) {
                  Object key = it.next();
                  if(!existing.contains(key)) {
                      it.remove();
                  }
               }
            }
        });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment