Move a row in a Table?


How do you move an individual row in a Table?

Given a Table like this:

How do I move “Apple” after “Cranberry”, to get this:

Tree has “move” methods, but Table does not.

Table has “removeItem” but no “insertItem” to go with it.

Table has two forms of “addItemAfter”, but both of these
create
a new Item. But I don’t want a new Item – I already have my existing “Apple” item, and want to put it back into the Table at a certain row position.

–Basil Bourque

I guess one solution would be to add an order property in the container that the items in the table are ordered by that can be altered in run time.
I guess you can order items in a container from a property that isn’t displayed in the table, but I’m not sure though…

I’ve meet the same need for sort row by drag & drop, this is the way i’ve solve the problem:


Item it = null;
switch (dropData.getDropLocation())
			{
				case BOTTOM :
					getContainerDataSource().removeItem(sourceItemId);
					it = ((IndexedContainer) getContainerDataSource()).addItemAfter(targetItemId, sourceItemId);
					break;
				case TOP :
					getContainerDataSource().removeItem(sourceItemId);
					it =((IndexedContainer) getContainerDataSource()).addItemAt(indexOfId(targetItemId), sourceItemId);
					break;
			}
// Need to set all properties on the Item.
if(it!=null)
{
                    it.getItemProperty(....).setValue(...);
}

EDIT: Sorry, I just read the end of your message… this will recreate another Item, but haven’t found another way to accomplish this behaviour.

Thanks for the replies. That gave me the confidence to conclude that indeed the Table class needs to add a “move” feature. I created a
dev Ticket
to request so.

The lack of a “move” feature in Table explains why the Sampler has examples of Tree dragging but not Table dragging. :slight_smile:

When I have some free time I may look at the source code for Table to see how sorting is implemented, to inspect how it moves rows.

It doesn’t move rows. It asks the underlying container to sort, and redisplays.

I have solved this need by creating a SnartBeanContainer to manage row movements. It is derived from BeanContainer and containd methods to move a selection of roes and check if a selection allows moving to enable the 4 buttons I provide (move top, up, down, bottom the list of selected rows as I usually provide in Swing apps.). Here are some examples:

      // enabling - disabling buttons. selection returns the set of selected row IDs!
      SmartBeanContainer bc = (SmartBeanContainer) table.getContainerDataSource();
      int[] span = bc.getMoveSpans(selection);
      btnUp.setEnabled(span[0]
 < 0);
      btnTop.setEnabled(span[0]
 < 0);
      btnDown.setEnabled(span[1]
 > 0);
      btnBottom.setEnabled(span[1]
 > 0);

      // moving selected rows 1 position up
     SmartBeanContainer bc = (SmartBeanContainer) table.getContainerDataSource();
     bc.move(selection, -1) // use +1 to move down!

      // moving selected rows to bottom
     SmartBeanContainer bc = (SmartBeanContainer) table.getContainerDataSource();
     int offset = bc.getMoveSpans(selection)[1]
;
     bc.move(selection, offset)

      // moving selected rows to top
     SmartBeanContainer bc = (SmartBeanContainer) table.getContainerDataSource();
     int offset = bc.getMoveSpans(selection)[0]
;
     bc.move(selection, offset)

And here goes SnartBeanContainer class:

import com.vaadin.data.util.BeanContainer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class SmartBeanContainer<K, V> extends BeanContainer<K, V> {

  public SmartBeanContainer(Class<? super V> type) {
    super(type);
  }
  
  /**
   * Move (up or down) a set of items.
   * @param toMove The IDs of rows to be moved.
   * @param offset The offset to be moved: -1 = 1 row up, +5 = 5 rows down and 
   *   so on.
   */
  public void move(Set<Object> toMove, int offset) {
    System.out.println("move>>>" + offset);
    List<K> ids = getAllItemIds();
    List<K> pending = new ArrayList<K>(ids);
    Object[] newIds = new Object[ids.size()]
;
    for (Object id: toMove) {
      int index = indexOfId(id) + offset;
      if (index >= 0 && index < ids.size()) {
        newIds[index]
 = id;
        pending.remove((K)id);
      }
    }
    int i = 0;
    for (K id: pending) {
      while (newIds[i]
 != null)
        i++;
      newIds[i++]
 = id;
    }
    ids.clear();
    for (Object id: newIds)
      ids.add((K)id);
    if (isFiltered()) {
      filterAll();
    } else {
      fireItemSetChange();
    }
  }

  /**
   * Compute maximum move span. This can be used to enable and disable move
   * buttons and to compute the offset needed to move first selected item
   * to top or last selected item to bottom.
   * @param toMove toMove The IDs of rows to be moved.
   * @return A 2-elements array indicating the maximum negative and positive
   *   move without exceeding table boundaries. Foe example [-5, +2]
 indicates 
   */
  public int[] getMoveSpans(Set<Object> toMove) {
    int l = getAllItemIds().size();
    if (l == 0 || toMove.isEmpty())
      return new int[] {0, 0};
    int first = l - 1;
    int last = 0;
    for (Object o: toMove) {
      int i = indexOfId(o);
      first = Math.min(first , i);
      last = Math.max(last , i);
    }
    return new int[] {-first, l - 1 - last};
  }
}

I am having problems getting your SmartBeanContainer to work.

I suspect the problem is an infinite loop here:

 while (newIds != null)
        i++;

Here is a simple example app I built to try it, “TrySmartBeanContainer” app.
Just copy-paste the 3 classes into a new Vaadin project. Ready to run.

Symptom: When I click any of the 4 buttons, I get Vaadin’s red spinning wheel in the upper right corner. Never ends.

Three simple classes:

  1. Vaadin app
  2. Your
    SmartBeanContainer
  3. A
    Person
    class to populate the table with rows.

Using: Vaadin 6.8.0, Eclipse Indigo, Tomcat 7.0.28, Mac OS X 10.7.4, Java 7 Update 5 (I think, not sure).

package com.example.trysmartbeancontainer;

import java.util.Set;

import com.vaadin.Application;
import com.vaadin.data.Property;
import com.vaadin.ui.*;
import com.vaadin.ui.Button.ClickEvent;

/**
 * <p>
 * My attempt to use the SmartBeanContainer posted in <a href='https://vaadin.com/forum/-/message_boards/view_message/1505114#_19_message_1505114'>this thread of the Vaadin</a> forum.
 * </p>
 * 
 * <p>
 * © 2012 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
 * </p>
 * 
 * @author Basil Bourque.
 * 
 */
public class TrysmartbeancontainerApplication extends Application {

    @SuppressWarnings ( "serial" )
    @Override
    public void init() {
        // Window *****************************************************
        Window mainWindow = new Window( "Trysmartbeancontainer Application" );
        Label label = new Label( "Hello Vaadin user. Now is: " + new java.util.Date() );
        mainWindow.addComponent( label );

        // Container *****************************************************
        SmartBeanContainer container = new SmartBeanContainer( Person.class );
        container.addItem( java.util.UUID.randomUUID(), new Person( "Franco", "Graziosi", "206.555.1234" ) );
        container.addItem( java.util.UUID.randomUUID(), new Person( "Basil", "Bourque", "425.555.5678" ) );
        container.addItem( java.util.UUID.randomUUID(), new Person( "Wendy", "Melvoin", "360.555.7777" ) );
        container.addItem( java.util.UUID.randomUUID(), new Person( "Lisa", "Coleman", "808.555.6655" ) );
        container.addItem( java.util.UUID.randomUUID(), new Person( "Jesse", "Johnson", "707.555.4455" ) );

        // Table *****************************************************
        final Table table = new Table( "People", container );
        table.setImmediate( true );
        table.setSelectable( true );
        table.setMultiSelect( true );
        // Tell selection change.
        table.addListener( new Property.ValueChangeListener() {

            public void valueChange( Property.ValueChangeEvent event ) {
                System.out.println( "Selected: " + table.getValue() + "   " + new java.util.Date() );
                System.out.println( "Selection itself is type: " + table.getValue().getClass().getName() );
            }
        } );
        mainWindow.addComponent( table );

        // Move To The Top button *****************************************************
        Button moveToTopButton = new Button( "Move to top" );
        moveToTopButton.addListener( new Button.ClickListener() {

            @Override
            public void buttonClick( ClickEvent event ) {
                System.out.println( "DEBUG - moveToTopButton button clicked. " + new java.util.Date() );
                // moving selected rows 1 position up
                SmartBeanContainer bc = (SmartBeanContainer)table.getContainerDataSource();
                Set set = (Set)table.getValue();
                int offset = bc.getMoveSpans( set )[0]
;
                bc.move( set, offset );
            }
        } );
        mainWindow.addComponent( moveToTopButton );

        // Up button *****************************************************
        Button moveUpButton = new Button( "↑" );
        moveUpButton.addListener( new Button.ClickListener() {

            @Override
            public void buttonClick( ClickEvent event ) {
                System.out.println( "DEBUG - Up button clicked. " + new java.util.Date() );
                // moving selected rows 1 position up
                SmartBeanContainer bc = (SmartBeanContainer)table.getContainerDataSource();
                Set set = (Set)table.getValue();
                bc.move( set, -1 ); // -1 = up. +1 = Down.
            }
        } );
        mainWindow.addComponent( moveUpButton );

        // Down button *****************************************************
        Button moveDownButton = new Button( "↓" );
        moveDownButton.addListener( new Button.ClickListener() {

            @Override
            public void buttonClick( ClickEvent event ) {
                System.out.println( "DEBUG - Down button clicked. " + new java.util.Date() );
                // moving selected rows 1 position down
                SmartBeanContainer bc = (SmartBeanContainer)table.getContainerDataSource();
                Set set = (Set)table.getValue();
                bc.move( set, 1 ); // -1 = up. +1 = Down.
            }
        } );
        mainWindow.addComponent( moveDownButton );

        // Move To The Bottom button *****************************************************
        Button moveToBottomButton = new Button( "Move to bottom" );
        moveToBottomButton.addListener( new Button.ClickListener() {

            @Override
            public void buttonClick( ClickEvent event ) {
                System.out.println( "DEBUG - moveToBottomButton button clicked. " + new java.util.Date() );
                // moving selected rows 1 position up
                SmartBeanContainer bc = (SmartBeanContainer)table.getContainerDataSource();
                Set set = (Set)table.getValue();
                int offset = bc.getMoveSpans( set )[1]
;
                bc.move( set, offset );
            }
        } );
        mainWindow.addComponent( moveToBottomButton );

        setMainWindow( mainWindow );
    }
}
package com.example.trysmartbeancontainer;

import com.vaadin.data.util.BeanContainer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @author Franco Graziosi
 * 
 * @param <K>
 * @param <V>
 */
@SuppressWarnings ( "serial" )
public class SmartBeanContainer< K, V > extends BeanContainer< K, V > {

    public SmartBeanContainer( Class< ? super V > type ) {
        super( type );
    }

    /**
     * Move (up or down) a set of items.
     * 
     * @param toMove
     *            The IDs of rows to be moved.
     * @param offset
     *            The offset to be moved: -1 = 1 row up, +5 = 5 rows down and
     *            so on.
     */
    public void move( Set< Object > toMove, int offset ) {
        System.out.println( "move>>>" + offset );
        List< K > ids = getAllItemIds();
        List< K > pending = new ArrayList< K >( ids );
        Object[] newIds = new Object[ids.size()]
;
        for ( Object id : toMove ) {
            int index = indexOfId( id ) + offset;
            if ( index >= 0 && index < ids.size() ) {
                newIds[index]
 = id;
                pending.remove( (K)id );
            }
        }
        int i = 0;
        for ( K id : pending ) {
            while ( newIds != null )
                i++;
            newIds[i++]
 = id;
        }
        ids.clear();
        for ( Object id : newIds )
            ids.add( (K)id );
        if ( isFiltered() ) {
            filterAll();
        } else {
            fireItemSetChange();
        }
    }

    /**
     * Compute maximum move span. This can be used to enable and disable move
     * buttons and to compute the offset needed to move first selected item
     * to top or last selected item to bottom.
     * 
     * @param toMove
     *            toMove The IDs of rows to be moved.
     * @return A 2-elements array indicating the maximum negative and positive
     *         move without exceeding table boundaries. Foe example [-5, +2]
 indicates
     */
    public int[] getMoveSpans( Set< Object > toMove ) {
        int l = getAllItemIds().size();
        if ( l == 0 || toMove.isEmpty() )
            return new int[] { 0, 0 };
        int first = l - 1;
        int last = 0;
        for ( Object o : toMove ) {
            int i = indexOfId( o );
            first = Math.min( first, i );
            last = Math.max( last, i );
        }
        return new int[] { -first, l - 1 - last };
    }
}
package com.example.trysmartbeancontainer;

/**
 * <p>
 * © 2012 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
 * </p>
 * 
 * @author basilbourque
 * 
 */
public class Person {

    private String firstName;
    private String lastName;
    private String phoneNumber;

    /**
     * @return the firstName
     */
    public String getFirstName() {
        return firstName;
    }

    /**
     * @param firstName
     *            the firstName to set
     */
    public void setFirstName( String firstName ) {
        this.firstName = firstName;
    }

    /**
     * @return the lastName
     */
    public String getLastName() {
        return lastName;
    }

    /**
     * @param lastName
     *            the lastName to set
     */
    public void setLastName( String lastName ) {
        this.lastName = lastName;
    }

    /**
     * @return the phoneNumber
     */
    public String getPhoneNumber() {
        return phoneNumber;
    }

    /**
     * @param firstName
     * @param lastName
     * @param phoneNumber
     */
    public Person( String firstName, String lastName, String phoneNumber ) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
    }

}

–Basil Bourque

Hi Oliv,

I tried your code and it works perfectly, thanks! May I ask why you need to set all the properties for the item you just added? I tried to remove that part (code below) and it works anyway so I’m curious about why it’s needed.

// Need to set all properties on the Item.
if(it!=null)
{
                    it.getItemProperty(....).setValue(...);
}

Thanks

I solved the problem by using a TreeTable and setting “children allowed” to false. Kludgy, perhaps, but quick and effective.

				this.setChildrenAllowed(itemId, false);

				container.moveAfterSibling(sourceItemId, targetItemId);