Tree: Drag and Drop: ClientSideCriterion: TargetInSubtree: (How) does it wo

Hi everyone,
I want to limit Drag and Drop in a Tree to certain nodes and I’m having trouble making it work.


What I want

Consider the following:

My Tree


Root
  |- Branch 1
     |- Leaf 1
     |- Leaf 2
  |- Branch 2
     |- Leaf 3
     |- Leaf 4


My goal is to limit the dragging and dropping of nodes to a certain level in the tree
. So let’s say I want to drag and drop the leaf nodes only, {“Leaf 1”, “Leaf 2”, “Leaf 3”, “Leaf 4”}.
For example, I’d like to drag and drop “Leaf 4” just before “Leaf 3” or between “Leaf 1” and “Leaf 2”. It should also be possible to drop “Leaf 4” onto the nodes “Branch 1” or “Branch 2”, making it their child node. However, it should not be possible to make “Leaf 4” a child node of “Leaf 3”, for example.

I also want achieve my goal using
ClientSideCriterion only
.


What (I think) I should use

The online ‘Book of Vaadin’,
Section 11.12.2, “Dropping Items On a Tree”
,
https://vaadin.com/book/-/page/advanced.dragndrop.html#advanced.dragndrop.treedrop
, states that there are two Tree specific ClientSideCriterion(s).

  • TargetInSubtree
  • TargetItemAllowsChildren


What I’ve tried so far

At first I used the
TargetItemAllowsChildren
and then the composite
new Or (Tree.TargetItemAllowsChildren.get(), new Not(VerticalLocationIs.MIDDLE))
criterion. The later almost achieves my goal. The composed ‘or’ criterion still allows to drag and drop, for example, the node “Leaf 4” to the same tree level as the two nodes “Branch 1” and “Branch 2” (or even to the “Root” level). I fiddled around with different criterion compositions but I was not able to figure one out that does what I want. (This is not to say that it cannot done, just that I could not figure it out).

After that I’ve tried the criterion
TargetInSubtree
. As I understand, when I create a TargetInSubtree(“Branch 2”) I get a criterion that allows dragging and dropping of the nodes “Leaf 3” and “Leaf 4” within the subtree of “Branch 2”. Additionally, the leaf nodes are also draggable onto the “Branch 2” node itself. However, they cannot become each other child nodes, so “Leaf 4” cannot be a child of the “Leaf 3” node. So far so good.
As I want to drag and drop the nodes on the same (leaf) tree level I’d compose an ‘or’ criterion like this:
Or (TargetInSubtree(“Branch 1”), TargetInSubtree(“Branch 2”))
.


Problem

When I use the
TargetInSubtree
criterion and drag and drop a leaf node it freezes the UI in any browser so far (IE10, IE8, Firefox 23.0.1, Chrome 28.0.1500.95 m). See also screenshot in the attachment.

Also, when I drag and drop a leaf node no ‘drop location indication’ is shown (the blue drop indication line).


Question 0: Right approach?

Is there a better approach to get what I want in general? Remember, I want to use a ClientSideCriterion for performance reasons.


Question 1: Am I using TargetInSubtree correctly?

I put together a (working) demo application. In particular, see line 69.

Note: When the
getAcceptCriterion()
method just returns the AcceptAll criterion then there are no problems with dragging and dropping (except for IE10 where dragging and dropping does not work at all, but that’s another story).


package com.example.vaadindemo;

import java.util.Iterator;

import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.event.Transferable;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.ui.dd.VerticalDropLocation;
import com.vaadin.ui.Tree;
import com.vaadin.ui.Tree.TargetInSubtree;
import com.vaadin.ui.Tree.TreeDragMode;
import com.vaadin.ui.Tree.TreeTargetDetails;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

/**
 * Main UI class
 */
@SuppressWarnings("serial")
public class VaadindemoUI extends UI {

    @Override
    protected void init(VaadinRequest request) {
        final VerticalLayout layout = new VerticalLayout();

        layout.setMargin(true);
        setContent(layout);


        final Tree tree = new Tree("My Tree");

        // Create the tree nodes
        tree.addItem("Root");
        tree.addItem("Branch 1");
        tree.addItem("Branch 2");
        tree.addItem("Leaf 1");
        tree.addItem("Leaf 2");
        tree.addItem("Leaf 3");
        tree.addItem("Leaf 4");

        // Set the hierarchy
        tree.setParent("Branch 1", "Root");
        tree.setParent("Branch 2", "Root");
        tree.setParent("Leaf 1", "Branch 1");
        tree.setParent("Leaf 2", "Branch 1");
        tree.setParent("Leaf 3", "Branch 2");
        tree.setParent("Leaf 4", "Branch 2");

        // Leafs may not have any children
        tree.setChildrenAllowed("Leaf 1", false);
        tree.setChildrenAllowed("Leaf 2", false);
        tree.setChildrenAllowed("Leaf 3", false);
        tree.setChildrenAllowed("Leaf 4", false);

        // Expand all items
        for (Iterator<?> it = tree.rootItemIds().iterator(); it.hasNext();)
            tree.expandItemsRecursively(it.next());

        // Set the tree in drag source mode
        tree.setDragMode(TreeDragMode.NODE);

        // Allow the tree to receive drag drops and handle them
        tree.setDropHandler(new DropHandler() {

            final TargetInSubtree tis = tree.new TargetInSubtree("Branch 1");

            @Override
            public AcceptCriterion getAcceptCriterion() {
                return tis;
            }

            @Override
            public void drop(DragAndDropEvent event) {
                // Wrapper for the object that is dragged
                Transferable t = event.getTransferable();

                // Make sure the drag source is the same tree
                if (t.getSourceComponent() != tree)
                    return;

                TreeTargetDetails target = (TreeTargetDetails)
                    event.getTargetDetails();

                // Get ids of the dragged item and the target item
                Object sourceItemId = t.getData("itemId");
                Object targetItemId = target.getItemIdOver();

                // On which side of the target the item was dropped
                VerticalDropLocation location = target.getDropLocation();

                HierarchicalContainer container = (HierarchicalContainer)
                tree.getContainerDataSource();

                // Drop right on an item -> make it a child
                if (location == VerticalDropLocation.MIDDLE)
                    tree.setParent(sourceItemId, targetItemId);

                // Drop at the top of a subtree -> make it previous
                else if (location == VerticalDropLocation.TOP) {
                    Object parentId = container.getParent(targetItemId);
                    container.setParent(sourceItemId, parentId);
                    container.moveAfterSibling(sourceItemId, targetItemId);
                    container.moveAfterSibling(targetItemId, sourceItemId);
                }

                // Drop below another item -> make it next
                else if (location == VerticalDropLocation.BOTTOM) {
                    Object parentId = container.getParent(targetItemId);
                    container.setParent(sourceItemId, parentId);
                    container.moveAfterSibling(sourceItemId, targetItemId);
                }
            }
        });

        layout.addComponent(tree);
    }

}


Question 2: Is the TargetInSubtree criterion just broken?

For reference,

  • I’m using the latest Vaation version 7.1.3.
  • There are no errors on the server-side (no exceptions, no log output (yes, it’s turned on))
  • There are no errors on the client-side (Chrome JavaScript console shows no problems)

Maybe, I’m missing something here?


Question 3: Is a composite criterion possible?

I’ve tried to come up with a composite criterion but without any luck. Maybe someone got an idea on how to compose a client criterion for this scenario?

Thanks
13153.png
13154.java (4.28 KB)

Following-up on this: As I now could identify an client side exception I’ve created a ticket.

See:
Ticket-12534