Is there a way to get the items of a TreeGrid without access to the list of items that were used to create the dataProvider?
It doesn’t matter to me right now whether such a list would only contain only top-level items, or if it contains all their children too. I can’t find a way for getting either, but I havent worked with TreeGrids before so it’s possible I may have overlooked something.
For Grids, I can use ((ListDataProvider<Foo>) fooGrid.getDataProvider()).getItems();, but the TreeDataProvider that the TreeGrid uses does not have getItems().
Edit:
I have just now found the way to get all top-level items: Collection<Foo> sourceItems = ((TreeDataProvider<Foo>) fooTreeGrid.getDataProvider()).getTreeData().getRootItems();.
Turns out the getRootItems() returns an unmodifiable Collection. Is there a reason for this? I now need to create a new ArrayList using the unmodifiable list from the TreeDataProvider, so I can remove one item from the TreeGrid.
Context for why I need this: I want to implement drag&drop between two TreeGrids of the same Type (Foo), and remove the dragged item from the source treegrid, using the same approach as in the [code examples for Grid]
(https://vaadin.com/components/vaadin-grid/java-examples/drag-and-drop), second example: in the definition of the dropListener, (because there are no drag&drop examples for TreeGrid)
except that I don’t have access to that list directly in my case (just like in the code example, linked below). I need to fetch that list from the dataProvider again. That list is then unmodifiable, so I have to do this:
I have my TreeGrid-adaptation of the [second drag&drop example for Grid]
(https://vaadin.com/components/vaadin-grid/java-examples/drag-and-drop) now almost ready and will probably post it here for completeness (and in hopes that someday the code examples for the TreeGrid component will be updated with a drag&drop example.)
As promised, here is my implementation of drag-n-drop between two TreeGrids. I’m aware that I have sidetracked a little from the original question of the Thread, but I don’t want to open a new Thread just to post my code. I think people can find this well enough when googling for it. Feel free to use any or all code.
It bases on the previously linked code example for drag-n-drop for Grid, but some additions needed to be made - for example checking if the dragged item or any of its descendants are already in the target treegrid. There is also a new complication when removing the dragged item from the source grid - removing root-items is easy but removing a child does need some more implementation on the actual Bean class (bidirectional relation, and a removeChild() method). Adding the dragged item as a child of the dropTarget (depending on GridDropMode) also needs to be adressed.
@Route("treegrid-dnd")
public class TreeGridDNDView extends VerticalLayout {
private Foo draggedItem = null;
private Grid<Foo> dragSource = null;
private int globalFooSeq = 0;
public TreeGridDNDView(){
add(new H1("TreeGrid Drag&Drop View"));
SplitLayout splitLayout = new SplitLayout();
splitLayout.setSizeFull();
Set<Foo> primaryRootItems = mockTreeGridData();
splitLayout.addToPrimary(prepareTreeGrid(primaryRootItems, GridDropMode.ON_GRID, true));
Set<Foo> secondaryRootItems = mockTreeGridData();
splitLayout.addToSecondary(prepareTreeGrid(secondaryRootItems, GridDropMode.ON_TOP, false));
add(splitLayout);
}
private TreeGrid<Foo> prepareTreeGrid(Set<Foo> rootItems, GridDropMode gridDropMode, boolean removeFromSouce) {
TreeGrid<Foo> treeGrid = new TreeGrid<>();
treeGrid.setItems(rootItems, Foo::getChildren);
Grid.Column<Foo> hierarchyColumn = treeGrid.addHierarchyColumn(Foo::getName).setHeader("Name");
treeGrid.addColumn(Foo::getChildrenCount).setHeader("Direct Children Count");
treeGrid.addColumn(Foo::getChildrenCountRecursive).setHeader("Recursive Children Count");
treeGrid.sort(Collections.singletonList(new GridSortOrder<>(hierarchyColumn, SortDirection.ASCENDING)));
treeGrid.setHeight("800px");
prepareDragNDrop(treeGrid, gridDropMode, removeFromSouce);
return treeGrid;
}
private void prepareDragNDrop(TreeGrid<Foo> treeGrid, GridDropMode gridDropMode, boolean removeFromSource) {
treeGrid.setRowsDraggable(true);
treeGrid.setDropMode(gridDropMode);
treeGrid.addDragStartListener(event -> {
draggedItem = event.getDraggedItems().get(0);
dragSource = event.getSource();
});
treeGrid.addDragEndListener(event -> {
draggedItem = null;
dragSource = null;
});
treeGrid.addDropListener(event -> {
// check if the draggedItem or its descendants is already in the target treegrid
// get all items of the target-treegrid recursively (not only root-items)
List<Foo> targetRootItemsUnmodifiable = ((TreeDataProvider<Foo>) treeGrid.getDataProvider()).getTreeData().getRootItems();
Set<Foo> targetRootItems = new HashSet<>(targetRootItemsUnmodifiable);
Set<Foo> targetItemsWithDescendants = getAllItemsDescendants(targetRootItems);
// get all descendants of the dragged item recursivley
Set<Foo> draggedItemDescendants = new HashSet<>();
draggedItemDescendants.add(draggedItem);
draggedItemDescendants = getAllItemsDescendants(draggedItemDescendants);
// compare descendants from draggedItem with all items from target-treegrid
boolean alreadyPresent = false;
for (Foo draggedItemDescendant : draggedItemDescendants) {
if(targetItemsWithDescendants.contains(draggedItemDescendant)){
alreadyPresent = true;
}
}
if(alreadyPresent){
Notification.show("This item (or a descendant thereof) is already in the target grid. Nothing is going to happen.");
} else {
// add draggedItem in target treegrid
// depending on gridDropMode, it will be added as root-item, or as child of the dropTarget
boolean mustHaveDropTargetItem = !gridDropMode.equals(GridDropMode.ON_GRID);
if(mustHaveDropTargetItem){
// add draggedItem as child of dropTarget
Optional<Foo> dropTarget = event.getDropTargetItem();
if(dropTarget.isPresent()){
dropTarget.get().addChild(draggedItem);
}
} else {
// add draggedItem as root-item to target treegrid
targetRootItems.add(draggedItem);
}
// refresh target treegrid
treeGrid.setItems(targetRootItems, Foo::getChildren);
treeGrid.getDataProvider().refreshAll();
// remove dragged item from source-treegrid
if(removeFromSource && dragSource instanceof TreeGrid) {
List<Foo> sourceRootItemsUnmodifiable = ((TreeDataProvider<Foo>) dragSource.getDataProvider()).getTreeData().getRootItems();
List<Foo> sourceRootItems = new ArrayList<>(sourceRootItemsUnmodifiable);
// only if dragged item is a root item in the source-treegrid
// removing non-root-items is feasible, but depends on specific bean implementations
if(sourceRootItems.contains(draggedItem)) {
sourceRootItems.remove(draggedItem);
} else {
// remove from parent bean
draggedItem.getParent().removeChild(draggedItem);
}
// refresh source treegrid
((TreeGrid<Foo>)dragSource).setItems(sourceRootItems, Foo::getChildren);
dragSource.getDataProvider().refreshAll();
}
}
});
}
private Set<Foo> getAllItemsDescendants(Set<Foo> viewEntries) {
Set<Foo> allItemsRecursive = new HashSet<>();
viewEntries.forEach(entity -> {
allItemsRecursive.add(entity);
allItemsRecursive.addAll(getAllItemsDescendants(entity.getChildren()));
});
return allItemsRecursive;
}
//creates a list of 10 root-items, with each 2 children, which again have 5 children
private Set<Foo> mockTreeGridData() {
Set<Foo> viewEntries = new HashSet<>();
for (int i = 1; i <= 10; i++) {
globalFooSeq++;
Foo rootFoo = new Foo(String.format("Foo - %d", globalFooSeq));
for (int j = 1; j <= 2; j++) {
String secondLevelFooName = String.format("Foo - %d.%d", globalFooSeq, j);
Foo secondLevelFoo = new Foo(secondLevelFooName);
for (int k = 1; k <= 5; k++) {
String thirdLevelName = String.format("Foo - %d.%d.%d", globalFooSeq, j, k);
Foo thirdLevelFoo = new Foo(thirdLevelName);
secondLevelFoo.addChild(thirdLevelFoo);
}
rootFoo.addChild(secondLevelFoo);
}
viewEntries.add(rootFoo);
}
return viewEntries;
}
public class Foo {
private String name;
private Set<Foo> children = new HashSet<>();
private Foo parent = null;
public Foo(String name){
this.name = name;
}
public String getName(){
return name;
}
public Set<Foo> getChildren(){
return children;
}
public void addChild(Foo child){
child.setParent(this);
children.add(child);
}
public void removeChild(Foo child){
child.setParent(null);
children.remove(child);
}
public void setParent(Foo parent){
this.parent = parent;
}
public Foo getParent(){
return parent;
}
public int getChildrenCount(){
return getChildren().size();
}
public int getChildrenCountRecursive(){
int count = 0;
for (Foo child : getChildren()) {
count++;
count += child.getChildrenCountRecursive();
}
return count;
}
}
}