Drag and drop rows in a Table example

I have a simple one (perhaps two) column Table in which the columns are not sortable, but I’d like the rows to drag and drop so the user can set the order.

Is there a code example that shows this for a Table (all within the same Table and not involving a Tree)?

Thanks for any tips…

Does

table.setColumnReorderingAllowed(true);
table.setSortDisabled(true);

do what you want, or am I missing something?

The question was about dragging rows, not columns.

The
Sampler tree - table drag and drop example
is the closest one I have right now, I’m sure someone has a better one. The drop handler could be simplified a lot, you can use SourceIsTarget in your criteria. Be careful with indices when moving an item up.

Thanks, Henri. Yes, I basically have a list of items that I’d like the user to be able to sort/re-order as they see fit. I saw that Tree/Table sample, but was hoping for a simpler just-Table example to avoid figuring out the “simplification” part. That said, I’m sure I can figure it out, though was hoping for something easier to start with to avoid looking up stuff that’s not needed when just a single Table is involved: drag and drop within the same table.

Since I am using a Form, in a perfect world, there would be some sort of re-ordering multi-select drop down box so that whatever order was done would be set on my BeanItem on Form.commit().

But barring that, I figured a simple Table would suffice, showing something like:

Page Name
1 Welcome page
2 Personal information
3 Education
4 Employment
5 Authorization

Then, if the user wanted to move the “Employment” page before “Education” page, they’d just drag it up, and I’d then display the table as:

Page Name
1 Welcome page
2 Personal information
3 Employment
4 Education
5 Authorization

The “page” is not a real field of my data (I just have a list of names), but as things are moved, I’d like to then scan my Table and update the Page field to show it back in order. I may do this reorder after each drop, or I may only do it on Form.commit() so the user can see the original position until they click Save.

In my testing of dragging rows in a Table (Vaadin 6.4.4 with Chameleon Theme), I ran into a couple of issues. I basically created a Table with 3 rows in its container. I have set the width to 100%, but no height is set. The Table is put in a GridLayout of a Form.

If I call table.setPageLength(3) so that all 3 rows are visible, when I click on a row, scollbars will automatically appear which causes the last row to be covered up. I found that I have to setPageLength(container.size()+1) in order to ensure there’s a blank row so that when I click, no scrollbars appear. Is this a bug? I did a quick test in FF 3.6 and IE 8 and it both did this.

Secondly, I was looking to support multiple selected rows drag and drop using:

table.setDragMode(TableDragMode.MULTIROW);
table.setMultiSelect(true);

It appears that I still only get a single ‘drop’ callback with a single source and target, so how do I determine the other selected rows in the table? Is this part of DnD or part of the Table interface?

This is going to make user-defined ordering much more convenient, so DnD is a great addition! Thanks!

This is probably a theme issue in Chameleon - do you also see this with other themes?

The DnD API does allow you to let the user drag multiple rows, but only the one of them is in the Transferable. You need to ask the drag source for the full selection - I cannot recall if there is a good example of this somewhere.

This should be simple when the drag source and target are the same, you just need to be careful with indices if transferring multiple items that might come from both above and below the drop location, in any order.

I believe a DnD-orderable list or table component would also be of interest to others if you would publish it as an add-on in the directory.

You were right, the problem of scrollbars appearing when you click one of the 3 rows when the Table.setPageLength(3) is in place occurs only with Chameleon. Once you click outside of the Table, the scrollbars go away (no doubt as the Table loses focus).

When I changed our Theme to inherit from Reindeer instead, the bug does not appear as soon as you select a row, but will occur if you then try to drag any row below the last row – that is, the scrollbars will appear and then cover up the last row. This also appears to be the case with Runo. I believe this could be related to the fact that when scrolling, it has the “blue insertion bar” telling me when I’m at a location to drop, which perhaps is just too big and thus causes the scrollbars to appear.

And I think others have reported that you can’t drag to a location that would require scrolling to get to. You can only drag among the items listed in the current page. This isn’t yet an issue for me as my lists tend to be short and I can just set the page length to match the number of items in my container so they are all visible.

RE-EDIT UPDATE:

From another forum post regarding the Table shift, which may very well be what’s responsible, though it’s possible the other themes have it when dragging to the last item in the table:

“Chameleon Theme - Table problem”
This has been reported earlier by another user, and a ticker has been created: #5604.

Per Henri’s request, here’s the code we are using now. We’re not currently attempting to make this a stand-alone generic component suitable for the Directory, nor has it been tested rigorously.

Note that PageInfo is the simple bean we use in our BeanItemContainer supplied as the source for the Table, having just two properties ‘name’ and ‘pageNumber’.

Here’s all we did:

pageInfoTable = new Table();
pageInfoTable.setWidth(100, UNITS_PERCENTAGE);
pageInfoTable.setSortDisabled(true);
pageInfoTable.setNullSelectionAllowed(true);
pageInfoTable.setDragMode(TableDragMode.ROW);
pageInfoTable.setDropHandler(new DropHandler() {
        public void drop(DragAndDropEvent dropEvent) {
                DataBoundTransferable t = (DataBoundTransferable)dropEvent.getTransferable();
                PageInfo sourceItemId = (PageInfo)t.getItemId(); // returns our Bean

                AbstractSelectTargetDetails dropData = ((AbstractSelectTargetDetails) dropEvent.getTargetDetails());
                PageInfo targetItemId = (PageInfo)dropData.getItemIdOver(); // returns our Bean
                
                // No move if source and target are the same, or there is no target
                if ( sourceItemId == targetItemId || targetItemId == null)
                	return;
                
                // Let's remove the source of the drag so we can add it back where requested...
                pageInfoContainer.removeItem(sourceItemId);

            	if ( dropData.getDropLocation() == VerticalDropLocation.BOTTOM ) {
                	pageInfoContainer.addItemAfter(targetItemId,sourceItemId);
            	} else {
                    Object prevItemId = pageInfoContainer.prevItemId(targetItemId);
                    pageInfoContainer.addItemAfter(prevItemId, sourceItemId);
            	}
        }

        public AcceptCriterion getAcceptCriterion() {
                return new And(new SourceIs(pageInfoTable), AcceptItem.ALL);
         }
});

There are some considerations for your needs:

  1. What do you do if they drag an item and drop it on the same item (row)? In our case, since we have Beans in the container, we just check if they are the same bean and ignore: sourceItemId == targetItemId

  2. What do you do if there is no target (targetItemId == null) for the drop? The sampler put it at the end, but I found I got no target if I dragged to the Table’s column headers, too, so putting it at the end would be odd. I suspect you can do the same by dragging to the bottom of a table (especially if it has a footer).

  3. What to do with a drop location of MIDDLE? If the drop location is TOP, it makes sense you want to move the item above the target. If the drop location is BOTTOM, it makes sense you want to move the it above the target. But when it’s MIDDLE (like you drop it right on another item/row), do you put it above or below the target? We chose “before” but are not really happy since we’re not sure what would be considered “standard” for such a thing.

Also, if you drag to the first row in our code, there will be no “prevItemId”, but it appears that the addItemAfter will put it on the top if it’s null.

Also, since our Table is in a Form that can be in EDIT mode or READONLY mode, we have special code for our setReadOnly(boolean readOnly) method so that it works since it’s not an input field like other Form fields, so we only allow it to be selected and/or dragged when in EDIT mode:

pageInfoTable.setReadOnly(readOnly);
pageInfoTable.setSelectable(!readOnly);
pageInfoTable.setDragMode( readOnly ? TableDragMode.NONE : TableDragMode.ROW);

And lastly, when a row/item is selected from our view so that it will then appear in the Form for editing/details, our Form gets called to set a new data source, which we use to then update our pageInfoTable and pageInfoContainer as appropriate. Because there’s no PageInfo beans in our BeanItemContainer (PageInfoContainer) until our Form’s data source is set, we do the following code to set up our page info table accordingly as it otherwise has no properties:

		try {
			if ( pageInfoContainer != null ) {
				pageInfoContainer.removeAllItems();
			}
			pageInfoContainer = new PageInfoContainer(bean.documentVersion());
			pageInfoTablepageInfoTable.setContainerDataSource(pageInfoContainer);
			pageInfoList.setVisibleColumns( new String[]{"pageName", "pageNumber"} );
			pageInfoTable.setColumnHeaders( new String[]{"Page EsfName", "Order"} );
			pageInfoTable.setColumnExpandRatio("pageName", 1);
			pageInfoTable.setPageLength(pageInfoContainer.size()); 
			pageInfoTable.setVisible(true);
		} catch (Exception e) {
		}

Hope this helps someone…or helps us if we’re going about it the wrong way! Thanks…

Your example is helping me now as I too try to make a drag-to-sort-rows Table. Thanks!

I have a question: Where is your “pageInfoContainer”?

Is that a final member variable of the outer class you created to give your “DropHandler” inner class access to the target’s container?

Is there no way to get to the container of the target?

We can get the source’s container this way:

com.vaadin.data.Container sourceContainer = transferable.getSourceContainer();

Is there an equivalent way to get the target’s container?

By the way, FYI for others reading this thread, the Book of Vaadin
documents
a “TableTargetDetails” class which does not exist. The Tree.java class defines a nested “TreeTargetDetails” class. But the Table.java class does not do the same; there is no “TableTargetDetails”. Instead, Table.java directly instantiates the “AbstractSelectTargetDetails” class (which is not actually marked with the Java keyword ‘abstract’). This explains the “AbstractSelectTargetDetails” cast in David Hall’s code above.

Yes, that behavior is documented in
Container.Ordered
.

So I am confused by David Hall’s example code.

He removes the item being dragged. I understand that – we are going to put the item back into the Table (a Container), but in a new location.

He then calls “addItemAfter” method. But that method
creates
a new Item, rather than insert our existing Item. I do not see where he inserts the object we removed a moment earlier.

The answer Apple chose for Mac OS X behavior is to have no MIDDLE, only TOP or BOTTOM. A couple examples:
• System Preferences > Language & Text > Language (tab), the user can drag any of the many languages in order of preference.
• System Preferences > Network > Advanced (button) > DNS (tab) > DNS Servers, the user can drag to re-arrange any of multiple DNS Server addresses.
The insertion point is always above or below an item, but never is the item itself a drop location. The dragged-over item is never highlighted.

I stumbled on a way to enable this behavior in Vaadin when I found some source code in a
dev Ticket
. In your DropHandler, create a AcceptCriterion something like this:

table.setDropHandler(new DropHandler() {

    public AcceptCriterion getAcceptCriterion() {
        return [color=#00ac16]
new Not(AbstractSelect.VerticalLocationIs.MIDDLE)
[/color]; // User drops only between items.
		// return AcceptAll.get();
    }
	
	@Override
	public void drop( DragAndDropEvent dropEvent ) {
		…
	}
	
}

The dragged-over item will no longer be highlighted.

I
requested
this be added to the Book of Vaadin.

Hey, that’s a great discovery, Basil. I hope it is added to the Book of Vaadin since this is probably a pretty common interest for re-ordering items. I know there are times when dropping right on the object is great, too, but this makes it much more intuitive for re-ordering.

+1

So poking around makes me think that there is no way to “move” an existing item in a container. The only workaround I’ve seen so far was to remove the item and recreate it at the appropriate location (in case of indexed container). Other implementation may fix this issue by changing the way to itemIds are ordered in the collection I believe.

Is this a fair statement? If so, I find it weird that it’s not more explicit. When I see the tree sorting in the sampler, that makes me thing that table is obviously supported as well (sounds “simpler”).

Many containers display data where it is logically not possible to change the order, so adding this as a generic feature would not make sense. Some containers are read-only, many sort their data themselves, etc. In fact, I personally find that Tree and Table already provide too much pass-through API for operations that should only be in containers - maybe even only some specific kinds of containers.

The sample uses a container where moving items is possible at least to some extent. IndexedContainer could technically have a move method, but I believe it doesn’t at the moment. Implementing it in a reasonable way might be somewhat complicated in a filtered/sorted container but doable.

Hi,
[b]
i have multiple selected table row and i am able to drag component from one table to another tabel.
but my question is
While draging the table row can we have option to change the backgorund color of that drag row.

Thanks

[/b]

Hi,

i am using drag and drop row in my table. i want drag my last row to first possition in table . if i having more data then scrolling is coming for that how i need to be drop to first poiision.

please help