Drag & Drop TwinColSelect

Hi,
This is my first post (but not my first visit!) in the forum. I was looking for a Drag & Drop TwinColSelect, but didnt manage to find one. What I found was a recommendation to write one based on two tables. And that is what I did. I would like to share here the code (it took me a while to make this work) so if anyone else is looking for it, he/she can save some time. I didnt wrote a addon because I dont know how to do it and because Im sure my implementation lacks of maaaaany things. But it serves my purpouse!
If someone is willing to help me to write the addon (or will one to write it himself), I would be glad to do it! Please let me know.

Here is my DragDropTwinCol:

import com.vaadin.data.Container;
import com.vaadin.data.Property;
import com.vaadin.data.util.BeanItem;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.event.DataBoundTransferable;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.event.dd.acceptcriteria.And;
import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
import com.vaadin.event.dd.acceptcriteria.SourceIs;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomField;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.ColumnHeaderMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 *
 * @author jmrunge
 */
public class DragDropTwinCol extends CustomField<Collection> {
    private String itemProperty;
    private Collection sourceItems;
    private Table source;
    private Table target;
    private Panel panel;
    boolean ignoreChange;
    
    public DragDropTwinCol(Class itemClass, Collection sourceItems, String itemProperty) {
        this.itemProperty = itemProperty;
        this.sourceItems = sourceItems;
        source = new Table();
        target = new Table();
        panel = new Panel();
        ignoreChange = false;

        BeanItemContainer sourceContainer = new BeanItemContainer<>(itemClass);
        sourceItems.stream().forEach((item) -> {
            sourceContainer.addBean(item);
        });
        source.addContainerProperty(itemProperty, String.class, null, "Available", null, null);
        source.setContainerDataSource(sourceContainer);
        source.setVisibleColumns(itemProperty);
        source.setImmediate(true);
        source.setReadOnly(true);
        source.setPageLength(sourceItems.size());
        source.setDragMode(Table.TableDragMode.ROW);
        source.setDropHandler(getDropHandler(source, new SourceIs(target)));

        target.addContainerProperty(itemProperty, String.class, null, "Assigned", null, null);
        BeanItemContainer targetContainer = new BeanItemContainer<>(itemClass);
        target.setContainerDataSource(targetContainer);
        target.setVisibleColumns(itemProperty);
        target.setImmediate(true);
        target.setReadOnly(true);
        target.setPageLength(sourceItems.size());
        target.setDragMode(Table.TableDragMode.ROW);
        target.setDropHandler(getDropHandler(target, new SourceIs(source)));
        
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        super.setReadOnly(readOnly);
        if (readOnly) {
            source.setDragMode(Table.TableDragMode.NONE);
            target.setDragMode(Table.TableDragMode.NONE);
        } else {
            source.setDragMode(Table.TableDragMode.ROW);
            target.setDragMode(Table.TableDragMode.ROW);
        }
    }
    
    @Override
    protected Component initContent() {
        GridLayout layout = new GridLayout(2, 1);
        layout.setMargin(true);
        layout.setSpacing(true);
        
        layout.addComponent(source);
        layout.addComponent(target);
        panel.setContent(layout);
        return panel;
    }

    @Override
    public Class<? extends Collection> getType() {
        return Collection.class;
    }
    
    public void addSourceListStyleName(String styleName) {
        source.addStyleName(styleName);
    }

    public void addTargetListStyleName(String styleName) {
        target.addStyleName(styleName);
    }
    
    public void addPanelStyleName(String styleName) {
        panel.addStyleName(styleName);
    }
    
    public void removeSourceListStyleName(String styleName) {
        source.removeStyleName(styleName);
    }

    public void removeTargetListStyleName(String styleName) {
        target.removeStyleName(styleName);
    }
    
    public void removePanelStyleName(String styleName) {
        panel.removeStyleName(styleName);
    }
    
    @Override
    public void addStyleName(String styleName) {
        panel.addStyleName(styleName);
        source.addStyleName(styleName);
        target.addStyleName(styleName);
    }
    
    @Override
    public void removeStyleName(String styleName) {
        panel.removeStyleName(styleName);
        source.removeStyleName(styleName);
        target.removeStyleName(styleName);
    }
    
    @Override
    public void setCaption(String caption) {
        panel.setCaption(caption);
    }

    public void setSourceListCaption(String caption) {
        source.setColumnHeader(itemProperty, caption);
    }
    
    public void setTargetListCaption(String caption) {
        target.setColumnHeader(itemProperty, caption);
    }
    
    public void setShowListCaptions(boolean show) {
        if (show) {
            source.setColumnHeaderMode(ColumnHeaderMode.EXPLICIT_DEFAULTS_ID);
            target.setColumnHeaderMode(ColumnHeaderMode.EXPLICIT_DEFAULTS_ID);
        } else {
            source.setColumnHeaderMode(ColumnHeaderMode.HIDDEN);
            target.setColumnHeaderMode(ColumnHeaderMode.HIDDEN);
        }
    }
    
    public void setListVisibleRows(int rows) {
        source.setPageLength(rows);
        target.setPageLength(rows);
    }
    
    public void setListColumnWidth(int width) {
        source.setColumnWidth(itemProperty, width);
        target.setColumnWidth(itemProperty, width);
    }
    
    private DropHandler getDropHandler(Table targetTable, ClientSideCriterion acceptCriterion) {
        return new DropHandler() {

            @Override
            public void drop(DragAndDropEvent dropEvent) {
                final DataBoundTransferable t = (DataBoundTransferable) dropEvent.getTransferable();
                if (t.getSourceContainer().equals(targetTable.getContainerDataSource())) {
                    return;
                }
                Container source = t.getSourceContainer();
                Object sourceItemId = t.getItemId();
                Container target = targetTable.getContainerDataSource();
                ((BeanItemContainer)target).addBean(((BeanItem)source.getItem(sourceItemId)).getBean());
                source.removeItem(sourceItemId);
                updateValues();
            }

            @Override
            public AcceptCriterion getAcceptCriterion() {
                return new And(acceptCriterion, AbstractSelect.AcceptItem.ALL);
            }
        };
    }

    @Override
    public void setPropertyDataSource(Property newDataSource) {
        super.setPropertyDataSource(newDataSource);
        resetValues();
        initValues((Collection) newDataSource.getValue());
        super.addValueChangeListener((Property.ValueChangeEvent event) -> {
            if (!ignoreChange) {
                resetValues();
                initValues((Collection) event.getProperty().getValue());
            }
        });
    }
    
    private void resetValues() {
        ((BeanItemContainer)source.getContainerDataSource()).removeAllItems();
        ((BeanItemContainer)target.getContainerDataSource()).removeAllItems();
        sourceItems.stream().forEach((item) -> {
            ((BeanItemContainer)source.getContainerDataSource()).addBean(item);
        });
    }
    
    private void initValues(Collection newValues) {
        if (newValues != null) {
            newValues.stream().forEach((item) -> {
                ((BeanItemContainer)target.getContainerDataSource()).addBean(item);
                ((BeanItemContainer)source.getContainerDataSource()).removeItem(item);
            });
        }
    }
    
    private void updateValues() {
        ignoreChange = true;
        setValue(getValue());
        ignoreChange = false;
    }

    @Override
    public List getValue() {
        List values = new ArrayList();
        ((BeanItemContainer)target.getContainerDataSource()).getItemIds().stream().forEach((id) -> {
            Object item = ((BeanItemContainer)target.getContainerDataSource()).getItem(id).getBean();
            values.add(item);
        });
        return values;
    }
    
    

}

And here is the way I use it:

private Component getTwinCol(FieldGroup binder) {
        DragDropTwinCol twinCol = new DragDropTwinCol(MilongaSystemUser.class, users, "userName");
        twinCol.addStyleName("visible");
        twinCol.setCaption("Usuarios");
        twinCol.setListColumnWidth(143);
        twinCol.setSourceListCaption("Disponibles");
        twinCol.setTargetListCaption("Asignados");
        binder.bind(twinCol, "systemUsers");
        twinCol.setReadOnly(true);
        return twinCol;
    }

Hope this will help someone!

PS: Thanks vaadin guys for making such an amazing framework for us java devs who dont like HTML! :wink:

Hi Juan,

Thank you for sharing your code. It might be a good idea to package and share your component on Vaadin Directory to give it more visibility.

You can find instrcutions in the Book of Vaadin:
https://vaadin.com/book/-/page/gwt.addons.html

Felipe,
Thanks for your advice. Will do it as soon as I finish some enhancements Im doing to it.

Kind Regards,