Drag and Drop using AbsoluteLayout

Is it possible in some easy :grin: way to have, for example, an AbsoluteLayout which has two buttons on (lets says Button1 “top:10px; left:10px” and Button2 “top:40px; left:100px”) and be able to drag and drop the button to a new location anywhere on the Absolute layout, so perhaps Button2 would after DnD now be at “top:33px; left:16px”?

You can get the mouse-down event where the drag started from the [tt]
WrapperTransferable
[/tt] object with [tt]
getMouseDownEvent()
[/tt], and for the end point from the [tt]
WrapperTargetDetails
[/tt] with [tt]
getMouseEvent()
[/tt]. The mouse event object has getters for X and Y coordinates (relative to browser window top-left origo).

See
an example
. There appears to be some trouble with the [tt]
AbsoluteLayout
[/tt] as drop target; the accept indicator flickers annoyingly and apparently it doesn’t accept dropping everywhere. You can only make a drop when one of the drop accept indicators are visible and they behave quite strangely. (Might be some user error, but I made a ticket for this -
#4538
)

The details object has also [tt]
getAbsoluteTop()
[/tt] and [tt]
getAbsoluteLeft()
[/tt], but (unlike the book currently falsely states) these return the coordinates of the target wrapper, not the dragged object. You could use those coordinates to determine the relative drop location as well, by subtracting them from the current mouse position. Using just the mouse event coordinates seems nicer though.

Excellent, that seems to be just what I wanted, I’ll try it some more tomorrow… This would also be a good example to have in the sampler…Anyway I’ll bookmark your virtuallypreinstalled site… Thanks!

You can disable all the drop indication styles with these three style names:

layout.addStyleName("no-vertical-drag-hints");
layout.addStyleName("no-horizontal-drag-hints");
layout.addStyleName("no-box-drag-hints");

They should be made available on the API level, IMO, e.g.
DragWrapper.setDragHints(DragHints.NONE);
or something like that.

it looks like my strange problems were because the DragAndDropWrapper has 100% width by default instead of wrapping tightly around the wrapped components. This causes a bit strange behavior for components in an AbsoluteLayout. Just set all component wrappers to be setSizeUndefined() and there should not be problems.


// Have a component to drag
final Button button = new Button("An Absolute Button");
        
// Put the component in a D&D wrapper and allow dragging it
final DragAndDropWrapper buttonWrap = new DragAndDropWrapper(button);
buttonWrap.setDragStartMode(DragStartMode.COMPONENT);

// Set the wrapper to wrap tightly around the component
buttonWrap.setSizeUndefined();
        
// Add the wrapper to the layout, not the component
absLayout.addComponent(buttonWrap, "left: 50px; top: 50px;");

Updated
the example
.

Dropping on a position where the mouse pointer is over a component doesn’t work, because the component blocks the drop. I think this could be worked around easily by adding a drop handler to all the components that forwards drops to the parent AbsoluteLayout.

I don’t know why, but setting the no-*-drag-hints styles did not work for me. Setting the v-absolutelayout background to white worked though.

Oh, yes, those styles need to be set for the parent of the wrapper, not for the contained layout. I forgot to say that. IE6 doesn’t play nicely otherwise.

Some brief testing later…If I add a TextField, I get some odd behaviour. I can drag it OK, but it doesnt display its caption, also there doesnt seem to be anyway to enter data in the TextField… I have just added this in the example code.

    	// Have a component to drag
    	final TextField tf = new TextField("An Absolute TextField");
    	        
    	// Put the component in a D&D wrapper and allow dragging it
    	final DragAndDropWrapper tfWrap = new DragAndDropWrapper(tf);
    	tfWrap.setDragStartMode(DragStartMode.COMPONENT);

    	// Set the wrapper to wrap tightly around the component
    	tfWrap.setSizeUndefined();
    	
    	        
    	// Add the wrapper, not the component, to the layout
    	absLayout.addComponent(tfWrap, "left: 50px; top: 120px;");

Hi,

When testing the example code I get an “UnsupportedOperationException” thrown back:

java.lang.UnsupportedOperationException
	at com.vaadin.ui.CustomComponent.removeComponent(CustomComponent.java:237)
	at com.vaadin.ui.AbstractComponentContainer.addComponent(AbstractComponentContainer.java:203)
	at com.vaadin.ui.AbstractOrderedLayout.addComponent(AbstractOrderedLayout.java:68)

The test code is as follows:

public void init() {
		Window mainWindow = new Window("Test Application");
		mainWindow.addComponent(new Label("A label"));
		
		VerticalLayout vl = new VerticalLayout();
		
		vl.addComponent(createDragAndDropOnAbsolutePanel());
		mainWindow.addComponent(vl);
		
		mainWindow.addComponent(new Label("Another Label"));
		setMainWindow(mainWindow);
	}

Where the “createDragAndDropOnAbsolutePanel()” method is a direct copy of the example code and returns an AbsoluteLayout.

The error occurs on the line mainWindow.addComponent(vl); .
It seems that when the AbstractComponentContainer tries to add the component it also tries to remove it’s parent, which causes CustomComponent to throw the exception as per the code:

    public void removeComponent(Component c) {
        throw new UnsupportedOperationException();
    }

What am I doing incorrectly?
Any ideas would be greatly appreciated.

Cheers,
Richard

No idea from that, but here is my code which you can use as some basis…

package com.example.tester;

import com.vaadin.Application;
import com.vaadin.ui.*;

public class TesterApplication extends Application {
	@Override
	public void init() {
		Window mainWindow = new Window("Tester Application");
		//Label label = new Label("Hello Vaadin user");
		//mainWindow.setSizeFull();
		mainWindow.addComponent(new Label("A label"));
		mainWindow.addComponent(new DnDTest());
		mainWindow.addComponent(new Label("Another Label"));
		setMainWindow(mainWindow);
		setTheme("testertheme");
	}

}
package com.example.tester;

import java.util.Iterator;

import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropHandler;
import com.vaadin.event.dd.acceptcriteria.AcceptAll;
import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
import com.vaadin.ui.AbsoluteLayout;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.DragAndDropWrapper;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.AbsoluteLayout.ComponentPosition;
import com.vaadin.ui.DragAndDropWrapper.DragStartMode;
import com.vaadin.ui.DragAndDropWrapper.WrapperTargetDetails;
import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable;

public class DnDTest extends VerticalLayout {

    /**
	 * 
	 */
	private static final long serialVersionUID = 654954439884471733L;

	public DnDTest() {
		
		setSizeUndefined();
		
    	// A layout that allows moving its contained components
    	// by dragging and dropping them
    	final AbsoluteLayout absLayout = new AbsoluteLayout();
    	
    	absLayout.setWidth("600px");
    	absLayout.setHeight("400px");
    	
    	        
    	// Have a component to drag
    	final Button button = new Button("An Absolute Button");
    	        
    	// Put the component in a D&D wrapper and allow dragging it
    	final DragAndDropWrapper buttWrap = new DragAndDropWrapper(button);
    	buttWrap.setDragStartMode(DragStartMode.COMPONENT);

    	// Set the wrapper to wrap tightly around the component
    	buttWrap.setSizeUndefined();
    	
    	        
    	// Add the wrapper, not the component, to the layout
    	absLayout.addComponent(buttWrap, "left: 50px; top: 50px;");
    	
    	// Have a component to drag
    	final Label label = new Label("An Absolute Label");
    	        
    	// Put the component in a D&D wrapper and allow dragging it
    	final DragAndDropWrapper labWrap = new DragAndDropWrapper(label);
    	labWrap.setDragStartMode(DragStartMode.COMPONENT);

    	// Set the wrapper to wrap tightly around the component
    	labWrap.setSizeUndefined();
    	
    	        
    	// Add the wrapper, not the component, to the layout
    	absLayout.addComponent(labWrap, "left: 50px; top: 90px;");
    	
    	// Have a component to drag
    	final TextField tf = new TextField("An Absolute TextField");
    	        
    	// Put the component in a D&D wrapper and allow dragging it
    	final DragAndDropWrapper tfWrap = new DragAndDropWrapper(tf);
    	tfWrap.setDragStartMode(DragStartMode.COMPONENT);

    	// Set the wrapper to wrap tightly around the component
    	tfWrap.setSizeUndefined();
    	
    	        
    	// Add the wrapper, not the component, to the layout
    	absLayout.addComponent(tfWrap, "left: 50px; top: 120px;");
    	        
    	// Have another component to drag
    	final Button button2 = new Button("Another Button");
    	final DragAndDropWrapper button2Wrap = new DragAndDropWrapper(button2);
    	button2Wrap.setDragStartMode(DragStartMode.WRAPPER);
    	button2Wrap.setSizeUndefined();
    	absLayout.addComponent(button2Wrap, "left: 200px; top: 75px;");

    	// Another component to drag
    	final Panel panel = new Panel("Panel to Drag");
    	panel.setWidth("200px");
    	panel.addComponent(new Label(
    	        "<h1>Draggable Panel</h1>"+
    	        "<p>You can drag this panel.</p>",
    	        Label.CONTENT_XHTML));
    	((Layout) panel.getContent()).setMargin(false);
    	final DragAndDropWrapper panelWrap = new DragAndDropWrapper(panel);
    	panelWrap.setDragStartMode(DragStartMode.COMPONENT);
    	panelWrap.setSizeUndefined();
    	absLayout.addComponent(panelWrap, "left: 100px; top: 100px;");

    	// Wrap the layout to allow handling drops
    	DragAndDropWrapper layoutWrapper =
    	        new DragAndDropWrapper(absLayout);
    	layoutWrapper.addStyleName("no-vertical-drag-hints");
    	layoutWrapper.addStyleName("no-horizontal-drag-hints");
    	layoutWrapper.addStyleName("no-box-drag-hints");

    	// Handles drops both on an AbsoluteLayout and
    	// on components contained within it
    	class MoveHandler implements DropHandler {
    	    public AcceptCriterion getAcceptCriterion() {
    	        return AcceptAll.get();
    	    }

    	    public void drop(DragAndDropEvent event) {
    	        WrapperTransferable t =
    	            (WrapperTransferable) event.getTransferable();
    	        WrapperTargetDetails details =
    	            (WrapperTargetDetails) event.getTargetDetails();
    	        
    	        // Calculate the drag coordinate difference
    	        int xChange = details.getMouseEvent().getClientX()
    	                      - t.getMouseDownEvent().getClientX();
    	        int yChange = details.getMouseEvent().getClientY()
    	                      - t.getMouseDownEvent().getClientY();

    	        // Move the component in the absolute layout
    	        ComponentPosition pos =
    	            absLayout.getPosition(t.getSourceComponent());
    	        pos.setLeftValue(pos.getLeftValue() + xChange);
    	        pos.setTopValue(pos.getTopValue() + yChange);
    	    }
    	}        

    	// Handle moving components within the AbsoluteLayout
    	layoutWrapper.setDropHandler(new MoveHandler());
    	        
    	// Handle cases where a component is dropped on another
    	// component
    	for (Iterator<Component> i =
    	         absLayout.getComponentIterator(); i.hasNext();)
    	    ((DragAndDropWrapper)i.next()).setDropHandler(new MoveHandler());
    	
    	addComponent(layoutWrapper);
    	
    	
    }

}

Brilliant Phil!! - that worked for me, thank you for the code (also now I can see what I’m doing wrong).

Thanks again
(nice sheep pic BTW)

The caption of a TextField and most other components is managed by the containing layout, and in this case the layout sees only the wrapper, not the wrapped component. The wrapper doesn’t automatically get the caption from the component (I wonder if it should?), so you have to set the caption for the wrapper, not the component.

final TextField text = new TextField("Meaningless Caption");
final DragAndDropWrapper textWrap = new DragAndDropWrapper(text);
textWrap.setCaption("I Has a Caption!");
textWrap.setDragStartMode(DragStartMode.COMPONENT);
textWrap.setSizeUndefined();
absLayout.addComponent(textWrap, "left: 250px; top: 80px;");

Updated
the example
.

And yes, mouse click focus does not seem to work for a wrapped TextField (and probably for many other components as well). Thank you, you have just found a bug,
#4546
.

I am trying to imagine why you wouldnt want to get the info from the component, in the example the Panel caption does not have to be set on the wrapper…Setting it on the wrapper seems counter intuitive to me and doesnt allow you to expect the same consistent behaviour from, in this example, a TextField.

What happens if you have more than 1 TextField wrapped in a wrapper do that both lose their caption? OK I can try that :dry:

No problem, glad to help… Nice Dog pic… we seem to have some sort of theme here :lol:

Yes, because Panel (and Button and some others) “contain” the caption within themselves and therefore manage it themselves. If the wrapper would get its caption from the component, it could lead to some problems, such as a Panel wrapper having a duplicate caption even though the Panel already has one.

In that case, you would put the TextFields in a layout, which would manage their captions. This is a good solution for just one TextField as well.

// A text field wrapped in a layout that manages its caption
final TextField tf2 = new TextField("Layout Managed Caption");
final VerticalLayout tf2Layout = new VerticalLayout();
tf2Layout.addComponent(tf2);

// Then wrap the drag and drop wrapper around the layout
final DragAndDropWrapper tf2Wrap = new DragAndDropWrapper(tf2Layout);
tf2Wrap.setDragStartMode(DragStartMode.WRAPPER);
tf2Wrap.setSizeUndefined();
absLayout.addComponent(tf2Wrap, "left: 300px; top: 300px;");

This solution lets the user drag the TextField also from the caption, which is not the case if the caption is set for the wrapper. This is important for TextField, which will have very little grab area once the focus bug is fixed, just the border.

Be sure to set DragStartMode.WRAPPER or otherwise (if you set COMPONENT) dragging will drag just the TextField component and not its caption.

Updated
the example
. Please observe the behaviour of the two TextFields when you try to drag them from their captions.

The fix should not change the current behavior, where you can drag the textfield from its whole area. It should work just like buttons: you can either drag them, or simply click to perform some other action (click/focus).

Edit: oh, I see the problem now: text-selection… bummer.

In that case, I wonder if it will disable text selection with mouse. I suppose it’s a bit rare to select text with mouse from a draggable TextField.

(Uh, I accidentally hit Edit instead of Reply when replying - something a forum admin shouldn’t do… don’t worry, fixed your post.)

I wonder what is the real use case for draggable text field anyway? WYSIWYG GUI editor is the only one that comes to my mind. There will always be some major issues with drag and drop and non read only components. That is something I think we can’t completely hide on Vaadin level.

Illustrator/Freehand/Indesing might be good apps to mimic. “Drag only starts with ALT key down” or different tools that affect whether components are dragged/moved/edited/…

Hi Marco!

I tried your example, but I get an error after i drop component:


java.lang.RuntimeException: java.lang.NullPointerException
        at com.vaadin.terminal.gwt.server.AbstractCommunicationManager.handleVariables(AbstractCommunicationManager.java:1105)
        at com.vaadin.terminal.gwt.server.AbstractCommunicationManager.doHandleUidlRequest(AbstractCommunicationManager.java:587)
        at com.vaadin.terminal.gwt.server.CommunicationManager.handleUidlRequest(CommunicationManager.java:265)
        at com.vaadin.terminal.gwt.server.AbstractApplicationServlet.service(AbstractApplicationServlet.java:482)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.NullPointerException
        at com.vaadin.ui.AbsoluteLayout$ComponentPosition.getTopValue(AbsoluteLayout.java:353)
        at my.copmany.dnd.dndtest$1MoveHandler.drop(dndtest.java:128)
        at com.vaadin.terminal.gwt.server.DragAndDropService.handleDropRequest(DragAndDropService.java:109)
        at com.vaadin.terminal.gwt.server.DragAndDropService.changeVariables(DragAndDropService.java:74)
        at com.vaadin.terminal.gwt.server.AbstractCommunicationManager.handleVariables(AbstractCommunicationManager.java:1087)
        ... 16 more

dndtest.java:128


pos.setTopValue(pos.getTopValue() + yChange);

After i found in AbsoluteLayout.java (vaadin-6.3.3.jar):


/**
         * Gets the 'top' CSS-attributes value in specified units.
         * 
         * @return The value of the 'top' CSS-attribute
         */
        public float getTopValue() {
            return topValue == null ? 0 : rightValue.floatValue();
        }

:open_mouth:

There was a bug (
#5152
) in Vaadin 6.3.3 that getTopValue() returned wrong value. The problem is corrected in the latest nightlies.

Hello everybody,
I’m lucky that’s a good exemple here, exactelly what I was looking for.
To complicate things a little more I would like to draw lines between the D&D components, on the fly if possible but that’s not a must. The best approach?

PS: For backwards compatibility with IE6 I’m avoiding canvas tags… what else remains?

Thx for your inputs,
Dani