Loading...
Important Notice - Forums is archived

To simplify things and help our users to be more productive, we have archived the current forum and focus our efforts on helping developers on Stack Overflow. You can post new questions on Stack Overflow or join our Discord channel.

Product icon
TUTORIAL

Vaadin lets you build secure, UX-first PWAs entirely in Java.
Free ebook & tutorial.

Table example: shift- and ctrl-selection

Marc Englund
1 decade ago Oct 09, 2009 1:38pm

Hi,

There have been several questions about how to make a Table behave more like a desktop application, with shift- and ctrl-selection. I was convinced to put an example here.

The trick is to turn off regular selection in the Table, and use the ItemClickListener to select things programmatically (this keeps selection code in one place, and generally avoids trouble).

Of course I found a bug while doing this; here's the example, complete with workaround for #3500:

public class TableSelectionExample extends com.vaadin.Application {

    Table table;
    Object mostSelected; // last item clicked, essentially

    @Override
    public void init() {

        final Window main = new Window("Hello window");
        setMainWindow(main);

        // create table & configure
        table = new Table();
        main.addComponent(table);

        // fillerup
        table.addContainerProperty("Thing", String.class, null);
        for (int i = 0; i < 50; i++) {
            Item item = table.addItem("Number " + i);
            item.getItemProperty("Thing").setValue("Number " + i);
        }

        // this is the main magic + selectRange() and toggleSelected()
        table.addListener(new ItemClickListener() {

            public void itemClick(ItemClickEvent event) {
                Object id = event.getItemId();
                if (event.isCtrlKey()) {
                    if (toggleSelected(id)) {
                        mostSelected = id;
                    }
                } else if (event.isShiftKey()) {
                    if (mostSelected == null) {
                        mostSelected = id;
                    }
                    selectRange(mostSelected, id);
                    mostSelected = id;
                } else {
                    mostSelected = id;
                    HashSet<Object> selected = new HashSet<Object>();
                    selected.add(id);
                    table.setValue(selected);
                }
            }

        });

        // configure
        table.setImmediate(true);
        table.setMultiSelect(true);
        table.setSelectable(false);
        table.setNullSelectionAllowed(true);

        // Sorry, there is a bug, adding an ActionHandler as workaround
        // see http://dev.vaadin.com/ticket/3500
        table.addActionHandler(new Action.Handler() {
            public Action[] getActions(Object target, Object sender) {
                return null;
            }

            public void handleAction(Action action, Object sender, Object target) {
            }
        });

    }

    private void selectRange(Object startId, Object endId) {

        Container.Indexed container = (Container.Indexed) table
                .getContainerDataSource();
        HashSet<Object> range = new HashSet<Object>();

        int idx = container.indexOfId(startId);
        int end = container.indexOfId(endId);
        if (idx > end) {
            int t = idx;
            idx = end;
            end = t;
            range.add(endId);
        } else {
            range.add(startId);
        }

        while (idx < end) {
            Object id = container.getIdByIndex(++idx);
            range.add(id);
        }

        Set<Object> selected = (Set<Object>) table.getValue();
        if (selected != null && selected.containsAll(range)) {
            // unselect
            HashSet<Object> newSelection = new HashSet<Object>();
            newSelection.addAll(selected);
            newSelection.removeAll(range);
            table.setValue(newSelection);
        } else {
            table.setValue(range);
        }
    }

    private boolean toggleSelected(Object id) {

        HashSet<Object> select = new HashSet<Object>();
        Set<Object> selected = (Set<Object>) table.getValue();
        if (selected != null) {
            for (Object i : selected) {
                if (i != id) {
                    select.add(i);
                }
            }
        }
        boolean retval = false;
        if (!table.isSelected(id)) {
            select.add(id);
            retval = true;
        }
        table.setValue(select);
        return retval;
    }

}

I'll try to remember to update this once the workaround is no longer needed :-\
(this could probably be simplified a little as well, as it was made in a hurry - might update at some point)

Hint: You might want to wrap this up by extending Table and moving the functionality there.

Best Regards,
Marc

Michel Jung
1 decade ago Oct 09, 2009 2:35pm
Jouni Koivuviita
1 decade ago Oct 09, 2009 7:20pm
Michel Jung
1 decade ago Oct 12, 2009 7:13am
Marc Englund
1 decade ago Oct 12, 2009 1:27pm
Michel Jung
1 decade ago Oct 12, 2009 1:57pm
Marc Englund
1 decade ago Oct 13, 2009 9:01am
Michel Jung
1 decade ago Oct 13, 2009 9:53am

There were two small bugs in your code which caused "the selection always been done" (bad english i think) from the last to the second last clicked item (which is incorrect if you want to extend or reduce your selection with the shift modifier).

Here comes the updated code:

package com.example;

import java.util.HashSet;
import java.util.Set;

import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.event.Action;
import com.vaadin.event.ItemClickEvent;
import com.vaadin.event.ItemClickEvent.ItemClickListener;
import com.vaadin.ui.Table;

public class SelectableTable extends Table {

  /**
   * 
   */
  private static final long serialVersionUID = -6232921468057007629L;

  Object startItem; // last item clicked, essentially

  public SelectableTable() {

    // fillerup
    addContainerProperty( "Thing", String.class, null );
    for ( int i = 0; i < 50; i++ ) {
      Item item = addItem( "Number " + i );
      item.getItemProperty( "Thing" ).setValue( "Number " + i );
    }

    // this is the main magic + selectRange() and toggleSelected()
    addListener( new ItemClickListener() {

      public void itemClick( ItemClickEvent event ) {
        Object id = event.getItemId();
        if ( event.isCtrlKey() ) {
          if ( toggleSelected( id ) ) {
            startItem = id;
          }
        } else if ( event.isShiftKey() ) {
          if ( startItem == null ) {
            startItem = id;
          }
          selectRange( startItem, id );
        } else {
          startItem = id;
          HashSet<Object> selected = new HashSet<Object>();
          selected.add( id );
          setValue( selected );
        }
      }
    } );

    // configure
    setImmediate( true );
    setMultiSelect( true );
    setSelectable( false );
    setNullSelectionAllowed( true );

    // Sorry, there is a bug, adding an ActionHandler as workaround
    // see http://dev.vaadin.com/ticket/3500
    addActionHandler( new Action.Handler() {
      public Action[] getActions( Object target, Object sender ) {
        return null;
      }

      public void handleAction( Action action, Object sender, Object target ) {
      }
    } );

  }

  private void selectRange( Object startId, Object endId ) {

    Container.Indexed container = ( Container.Indexed ) getContainerDataSource();
    HashSet<Object> range = new HashSet<Object>();

    int idx = container.indexOfId( startId );
    int end = container.indexOfId( endId );
    if ( idx > end ) {
      int t = idx;
      idx = end;
      end = t;
      range.add( endId );
    } else {
      range.add( startId );
    }

    while ( idx < end ) {
      Object id = container.getIdByIndex( ++idx );
      range.add( id );
    }

    Set<Object> selected = ( Set<Object> ) getValue();
    if ( ( selected != null ) && selected.containsAll( range ) ) {
      // unselect
      HashSet<Object> newSelection = new HashSet<Object>();
      newSelection.removeAll( selected );
      newSelection.addAll( range );
      setValue( newSelection );
    } else {
      setValue( range );
    }
  }

  private boolean toggleSelected( Object id ) {

    HashSet<Object> select = new HashSet<Object>();
    Set<Object> selected = ( Set<Object> ) getValue();
    if ( selected != null ) {
      for ( Object i : selected ) {
        if ( i != id ) {
          select.add( i );
        }
      }
    }
    boolean retval = false;
    if ( !isSelected( id ) ) {
      select.add( id );
      retval = true;
    }
    setValue( select );
    return retval;
  }

}
Last updated on Oct, 13th 2009
Jonatan Kronqvist
1 decade ago Oct 28, 2009 7:51am