Browse Source

HTML5 Drag and Drop Support (#8264)

* Add DragSource Extension (#8169)

* Add DropTarget Extension (#8170)

* Add DragStart Event to DragSource Extension (#8171)

* Make DataTransfer.dropEffect configurable (#8174)

* Make DragSource.dataTransfer data configurable (#8172)

* Add server-side Event for drop (#8177)

* Added license headers

* Extract handler methods, move DropEvent and DropListener to new file, move enums to top

* Replaced LinkedHashMap with Map and added List to preserve order of data

* Add API for adding a JS acceptance criteria for dragover and drop (#8178, #8179)

* Make DragSource Extension extendable (#8175)

* Make DropTarget Extension extendable (#8176)

* Added javadoc to protected methods

* Moved EffectAllowed to shared so that it could be used in shared state directly

* Moved DropEffect to separate file, some review fixes and javadoc

* Added list to DropTargetRpc to preserve order of data

* Remove event listeners on unregister

* Changed method names set/getData() to more descriptive set/getTransferData()

* Add server side dragStart event (#8171)

* Add style to prevent text selection to allow drag

* Remove target indicator style on drop

* Add client side dragend event listener for drag source

* Add server side dragend listener.
Attach client side listener only when server side listener added.

* Add drag source information to server side dragstart and dragend events.

* Fixed some issues addressed in review

* Trigger server side dragstart only when there is a listener attached

* Criteria script can be set as null to clear

* Use Js Interop instead of JSNI for handling event listeners

* Use elemental package instead of Js Interop for handling event listeners

* Add missing javadoc for public methods

* Add default value "uninitialized" to effectAllowed parameter

* Simple test UI for HTML5 DnD functionality (#8395)

* Add javadoc and other minor changes
tags/8.1.0.alpha1
Adam Wagner 7 years ago
parent
commit
a9c8e66d14

+ 139
- 0
client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java View File

@@ -0,0 +1,139 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.client.extensions;

import java.util.List;
import java.util.Map;

import com.google.gwt.dom.client.DataTransfer;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ServerConnector;
import com.vaadin.event.dnd.DragSourceExtension;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.dnd.DragSourceRpc;
import com.vaadin.shared.ui.dnd.DragSourceState;

import elemental.events.Event;
import elemental.events.EventListener;
import elemental.events.EventTarget;

/**
* Extension to add drag source functionality to a widget for using HTML5 drag
* and drop. Client side counterpart of {@link DragSourceExtension}.
*/
@Connect(DragSourceExtension.class)
public class DragSourceExtensionConnector extends AbstractExtensionConnector {

private static final String CLASS_DRAGGABLE = "v-draggable";

// Create event listeners
private final EventListener dragStartListener = this::onDragStart;
private final EventListener dragEndListener = this::onDragEnd;

@Override
protected void extend(ServerConnector target) {
Element dragSourceElement = getDraggableElement();

dragSourceElement.setDraggable(Element.DRAGGABLE_TRUE);
dragSourceElement.addClassName(CLASS_DRAGGABLE);

EventTarget dragSource = dragSourceElement.cast();

// dragstart
dragSource.addEventListener(Event.DRAGSTART, dragStartListener);

// dragend
dragSource.addEventListener(Event.DRAGEND, dragEndListener);
}

@Override
public void onUnregister() {
super.onUnregister();

EventTarget dragSource = (EventTarget) getDraggableElement();

// Remove listeners
dragSource.removeEventListener(Event.DRAGSTART, dragStartListener);
dragSource.removeEventListener(Event.DRAGEND, dragEndListener);
}

/**
* Event handler for the {@code dragstart} event. Called when {@code
* dragstart} event occurs.
*
* @param event
* browser event to be handled
*/
protected void onDragStart(Event event) {
// Convert elemental event to have access to dataTransfer
NativeEvent nativeEvent = (NativeEvent) event;

// Set effectAllowed parameter
if (getState().effectAllowed != null) {
setEffectAllowed(nativeEvent.getDataTransfer(),
getState().effectAllowed.getValue());
}

// Set data parameter
List<String> types = getState().types;
Map<String, String> data = getState().data;
for (String format : types) {
nativeEvent.getDataTransfer().setData(format, data.get(format));
}

// Initiate firing server side dragstart event when there is a
// DragStartListener attached on the server side
if (hasEventListener(DragSourceState.EVENT_DRAGSTART)) {
getRpcProxy(DragSourceRpc.class).dragStart();
}
}

/**
* Event handler for the {@code dragend} event. Called when {@code dragend}
* event occurs.
*
* @param event
*/
protected void onDragEnd(Event event) {
// Initiate server start dragend event when there is a DragEndListener
// attached on the server side
if (hasEventListener(DragSourceState.EVENT_DRAGEND)) {
getRpcProxy(DragSourceRpc.class).dragEnd();
}
}

/**
* Finds the draggable element within the widget. By default, returns the
* topmost element.
*
* @return the draggable element in the parent widget.
*/
protected Element getDraggableElement() {
return ((ComponentConnector) getParent()).getWidget().getElement();
}

private native void setEffectAllowed(DataTransfer dataTransfer,
String effectAllowed)/*-{
dataTransfer.effectAllowed = effectAllowed;
}-*/;

@Override
public DragSourceState getState() {
return (DragSourceState) super.getState();
}
}

+ 223
- 0
client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java View File

@@ -0,0 +1,223 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.client.extensions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.DataTransfer;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ServerConnector;
import com.vaadin.event.dnd.DropTargetExtension;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.dnd.DropTargetRpc;
import com.vaadin.shared.ui.dnd.DropTargetState;

import elemental.events.Event;
import elemental.events.EventListener;
import elemental.events.EventTarget;

/**
* Extension to add drop target functionality to a widget for using HTML5 drag
* and drop. Client side counterpart of {@link DropTargetExtension}.
*/
@Connect(DropTargetExtension.class)
public class DropTargetExtensionConnector extends AbstractExtensionConnector {

private static final String CLASS_DRAG_OVER = "v-drag-over";

// Create event listeners
private final EventListener dragEnterListener = this::onDragEnter;
private final EventListener dragOverListener = this::onDragOver;
private final EventListener dragLeaveListener = this::onDragLeave;
private final EventListener dropListener = this::onDrop;

@Override
protected void extend(ServerConnector target) {
EventTarget dropTarget = getDropTargetElement().cast();

// dragenter event
dropTarget.addEventListener(BrowserEvents.DRAGENTER, dragEnterListener);

// dragover event
dropTarget.addEventListener(BrowserEvents.DRAGOVER, dragOverListener);

// dragleave event
dropTarget.addEventListener(BrowserEvents.DRAGLEAVE, dragLeaveListener);

// drop event
dropTarget.addEventListener(BrowserEvents.DROP, dropListener);
}

@Override
public void onUnregister() {
super.onUnregister();

EventTarget dropTarget = getDropTargetElement().cast();

// Remove listeners
dropTarget.removeEventListener(BrowserEvents.DRAGENTER,
dragEnterListener);
dropTarget.removeEventListener(BrowserEvents.DRAGOVER,
dragOverListener);
dropTarget.removeEventListener(BrowserEvents.DRAGLEAVE,
dragLeaveListener);
dropTarget.removeEventListener(BrowserEvents.DROP, dropListener);
}

/**
* Finds the drop target element within the widget. By default, returns the
* topmost element.
*
* @return the drop target element in the parent widget.
*/
protected Element getDropTargetElement() {
return ((ComponentConnector) getParent()).getWidget().getElement();
}

/**
* Event handler for the {@code dragenter} event.
*
* @param event
* browser event to be handled
*/
protected void onDragEnter(Event event) {
addTargetIndicator(getDropTargetElement());
}

/**
* Event handler for the {@code dragover} event.
*
* @param event
* browser event to be handled
*/
protected void onDragOver(Event event) {
NativeEvent nativeEvent = (NativeEvent) event;
if (isDragOverAllowed(nativeEvent)) {
// Set dropEffect parameter
if (getState().dropEffect != null) {
nativeEvent.getDataTransfer().setDropEffect(
DataTransfer.DropEffect
.valueOf(getState().dropEffect.name()));
}

// Prevent default to allow drop
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
} else {
// Remove drop effect
nativeEvent.getDataTransfer()
.setDropEffect(DataTransfer.DropEffect.NONE);

// Remove drop target indicator
removeTargetIndicator(getDropTargetElement());
}
}

/**
* Determines if dragover event is allowed on this drop target according to
* the dragover criteria.
*
* @param event
* Native dragover event.
* @return {@code true} if dragover is allowed, {@code false} otherwise.
* @see DropTargetExtension#setDragOverCriteria(String)
*/
protected boolean isDragOverAllowed(NativeEvent event) {
if (getState().dragOverCriteria != null) {
return executeScript(event, getState().dragOverCriteria);
}

// Allow when criteria not set
return true;
}

/**
* Event handler for the {@code dragleave} event.
*
* @param event
* browser event to be handled
*/
protected void onDragLeave(Event event) {
removeTargetIndicator(getDropTargetElement());
}

/**
* Event handler for the {@code drop} event.
*
* @param event
* browser event to be handled
*/
protected void onDrop(Event event) {
NativeEvent nativeEvent = (NativeEvent) event;
if (dropAllowed(nativeEvent)) {
nativeEvent.preventDefault();
nativeEvent.stopPropagation();

// Initiate firing server side drop event
JsArrayString typesJsArray = getTypes(
nativeEvent.getDataTransfer());
List<String> types = new ArrayList<>();
Map<String, String> data = new HashMap<>();
for (int i = 0; i < typesJsArray.length(); i++) {
types.add(typesJsArray.get(i));
data.put(typesJsArray.get(i), nativeEvent.getDataTransfer()
.getData(typesJsArray.get(i)));
}

getRpcProxy(DropTargetRpc.class)
.drop(types, data, getState().dropEffect);
}

removeTargetIndicator(getDropTargetElement());
}

private boolean dropAllowed(NativeEvent event) {
if (getState().dropCriteria != null) {
return executeScript(event, getState().dropCriteria);
}

// Allow when criteria not set
return true;
}

private void addTargetIndicator(Element element) {
element.addClassName(CLASS_DRAG_OVER);
}

private void removeTargetIndicator(Element element) {
element.removeClassName(CLASS_DRAG_OVER);
}

private native boolean executeScript(NativeEvent event, String script)/*-{
return new Function('event', script)(event);
}-*/;

private native JsArrayString getTypes(DataTransfer dataTransfer)/*-{
return dataTransfer.types;
}-*/;

@Override
public DropTargetState getState() {
return (DropTargetState) super.getState();
}
}

+ 77
- 0
server/src/main/java/com/vaadin/event/dnd/DragEndEvent.java View File

@@ -0,0 +1,77 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.ui.Component;

/**
* Server side dragend event. Fired when an HTML5 dragend happens.
*
* @see DragSourceExtension#addDragEndListener(DragEndListener)
*/
public class DragEndEvent extends Component.Event {
private final Map<String, String> data;
private final EffectAllowed effectAllowed;

/**
* Creates a new server side dragend event.
*
* @param source
* Draggable component.
* @param types
* List of data types from {@code DataTransfer.types}.
* @param data
* Map of all data from {@code DataTransfer}.
* @param effectAllowed
* Parameter from {@code DataTransfer.effectAllowed}.
*/
public DragEndEvent(Component source, List<String> types,
Map<String, String> data, EffectAllowed effectAllowed) {
super(source);

// Create a linked map that preserves the order of types
this.data = new LinkedHashMap<>();
types.forEach(type -> this.data.put(type, data.get(type)));

this.effectAllowed = effectAllowed;
}

/**
* Get data from the client side {@code DataTransfer} object.
*
* @param format
* Data format, e.g. {@code text/plain} or {@code text/uri-list}.
* @return Data for the given format if exists in the client side {@code
* DataTransfer}, otherwise {@code null}.
*/
public String getTransferData(String format) {
return data != null ? data.get(format) : null;
}

/**
* Returns the {@code effectAllowed} parameter of this event.
*
* @return This event's {@code effectAllowed} parameter.
*/
public EffectAllowed getEffectAllowed() {
return effectAllowed;
}
}

+ 40
- 0
server/src/main/java/com/vaadin/event/dnd/DragEndListener.java View File

@@ -0,0 +1,40 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.lang.reflect.Method;

import com.vaadin.event.ConnectorEventListener;

/**
* Interface to be implemented when creating a dragend listener on a drag
* source for HTML5 drag and drop.
*
* @see DragSourceExtension#addDragEndListener(DragEndListener)
*/
@FunctionalInterface
public interface DragEndListener extends ConnectorEventListener {
static final Method DRAGEND_METHOD = DragEndListener.class
.getDeclaredMethods()[0];

/**
* Called when a server side dragend event is fired.
*
* @param event
* The dragend event that is fired.
*/
void dragEnd(DragEndEvent event);
}

+ 226
- 0
server/src/main/java/com/vaadin/event/dnd/DragSourceExtension.java View File

@@ -0,0 +1,226 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.dnd.DragSourceRpc;
import com.vaadin.shared.ui.dnd.DragSourceState;
import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.ui.AbstractComponent;

/**
* Extension to add drag source functionality to a component for using HTML5
* drag and drop.
*/
public class DragSourceExtension extends AbstractExtension {

/**
* Constructor for {@link DragSourceExtension}
*/
public DragSourceExtension() {
registerRpc(new DragSourceRpc() {
@Override
public void dragStart() {
DragStartEvent event = new DragStartEvent(
(AbstractComponent) getParent(), getState(false).types,
getState(false).data, getState(false).effectAllowed);
fireEvent(event);
}

@Override
public void dragEnd() {
DragEndEvent event = new DragEndEvent(
(AbstractComponent) getParent(), getState(false).types,
getState(false).data, getState(false).effectAllowed);
fireEvent(event);
}
});
}

/**
* Makes {@code target} component a drag source.
*
* @param target
* Component to be extended.
*/
public void extend(AbstractComponent target) {
super.extend(target);
}

/**
* Sets the allowed effects for the current drag source element. Used for
* setting client side {@code DataTransfer.effectAllowed} parameter for the
* drag event.
* <p>
* By default the value is {@link EffectAllowed#UNINITIALIZED} which is
* equivalent to {@link EffectAllowed#ALL}.
*
* @param effect
* Effects to allow for this draggable element. Cannot be {@code
* null}.
*/
public void setEffectAllowed(EffectAllowed effect) {
if (effect == null) {
throw new IllegalArgumentException("Allowed effect cannot be null");
}
if (!Objects.equals(getState(false).effectAllowed, effect)) {
getState().effectAllowed = effect;
}
}

/**
* Returns the allowed effects for the current drag source element. Used to
* set client side {@code DataTransfer.effectAllowed} parameter for the drag
* event.
*
* @return Effects that are allowed for this draggable element.
*/
public EffectAllowed getEffectAllowed() {
return getState(false).effectAllowed;
}

/**
* Sets the data for this drag source element. Used to set data for client
* side drag element using {@code DataTransfer.setData()}. To be used as a
* map, key-value pairs are stored. Order of entries are preserved.
* <p>
* Note that by HTML specification, the browser will change data type
* "{@code text}" to "{@code text/plain}" and "{@code url}" to "{@code
* text/uri-list}" during client side drag event.
*
* @param format
* Data type to store, e.g. {@code text/plain} or {@code
* text/uri-list}. Cannot be {@code null}.
* @param data
* Data to store for the data type. Cannot be {@code null}.
*/
public void setTransferData(String format, String data) {
if (format == null) {
throw new IllegalArgumentException("Data type cannot be null");
}

if (data == null) {
throw new IllegalArgumentException("Data cannot be null");
}

if (!getState(false).types.contains(format)) {
getState().types.add(format);
}
getState().data.put(format, data);
}

/**
* Returns the data stored for {@code format} type in this drag source
* element.
*
* @param format
* Data type of the requested data, e.g. {@code text/plain} or
* {@code text/uri-list}.
* @return Data that is stored for {@code format} data type.
*/
public String getTransferData(String format) {
return getState(false).data.get(format);
}

/**
* Returns the map of data stored in this drag source element. The returned
* map preserves the order of storage and is unmodifiable.
*
* @return Unmodifiable copy of the map of data in the order the data was
* stored.
*/
public Map<String, String> getTransferData() {
Map<String, String> data = getState(false).data;

// Create a map of data that preserves the order of types
LinkedHashMap<String, String> orderedData = new LinkedHashMap<>(
data.size());
getState(false).types
.forEach(type -> orderedData.put(type, data.get(type)));

return Collections.unmodifiableMap(orderedData);
}

/**
* Clears data with the given type for this drag source element when
* present.
*
* @param format
* Type of data to be cleared. Cannot be {@code null}.
*/
public void clearTransferData(String format) {
if (format == null) {
throw new IllegalArgumentException("Data type cannot be null");
}

getState().types.remove(format);
getState().data.remove(format);
}

/**
* Clears all data for this drag source element.
*/
public void clearTransferData() {
getState().types.clear();
getState().data.clear();
}

/**
* Attaches dragstart listener for the current drag source. {@link
* DragStartListener#dragStart(DragStartEvent)} is called when dragstart
* event happens on the client side.
*
* @param listener
* Listener to handle dragstart event.
* @return Handle to be used to remove this listener.
*/
public Registration addDragStartListener(DragStartListener listener) {
return addListener(DragSourceState.EVENT_DRAGSTART,
DragStartEvent.class, listener,
DragStartListener.DRAGSTART_METHOD);
}

/**
* Attaches dragend listener for the current drag source. {@link
* DragEndListener#dragEnd(DragEndEvent)} is called when dragend
* event happens on the client side.
*
* @param listener
* Listener to handle dragend event.
* @return Handle to be used to remove this listener.
*/
public Registration addDragEndListener(DragEndListener listener) {
return addListener(DragSourceState.EVENT_DRAGEND, DragEndEvent.class,
listener, DragEndListener.DRAGEND_METHOD);
}

@Override
protected DragSourceState getState() {
return (DragSourceState) super.getState();
}

@Override
protected DragSourceState getState(boolean markAsDirty) {
return (DragSourceState) super.getState(markAsDirty);
}
}

+ 77
- 0
server/src/main/java/com/vaadin/event/dnd/DragStartEvent.java View File

@@ -0,0 +1,77 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.vaadin.shared.ui.dnd.EffectAllowed;
import com.vaadin.ui.Component;

/**
* Server side dragstart event. Fired when an HTML5 dragstart happens.
*
* @see DragSourceExtension#addDragStartListener(DragStartListener)
*/
public class DragStartEvent extends Component.Event {
private final Map<String, String> data;
private final EffectAllowed effectAllowed;

/**
* Creates a new server side dragend event.
*
* @param source
* Draggable component.
* @param types
* List of data types from {@code DataTransfer.types}.
* @param data
* Map of all data from {@code DataTransfer}.
* @param effectAllowed
* Parameter from {@code DataTransfer.effectAllowed}.
*/
public DragStartEvent(Component source, List<String> types,
Map<String, String> data, EffectAllowed effectAllowed) {
super(source);

// Create a linked map that preserves the order of types
this.data = new LinkedHashMap<>();
types.forEach(type -> this.data.put(type, data.get(type)));

this.effectAllowed = effectAllowed;
}

/**
* Get data from the client side {@code DataTransfer} object.
*
* @param format
* Data format, e.g. {@code text/plain} or {@code text/uri-list}.
* @return Data for the given format if exists in the client side {@code
* DataTransfer}, otherwise {@code null}.
*/
public String getTransferData(String format) {
return data != null ? data.get(format) : null;
}

/**
* Returns the {@code effectAllowed} parameter of this event.
*
* @return This event's {@code effectAllowed} parameter.
*/
public EffectAllowed getEffectAllowed() {
return effectAllowed;
}
}

+ 40
- 0
server/src/main/java/com/vaadin/event/dnd/DragStartListener.java View File

@@ -0,0 +1,40 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.lang.reflect.Method;

import com.vaadin.event.ConnectorEventListener;

/**
* Interface to be implemented when creating a dragstart listener on a drag
* source for HTML5 drag and drop.
*
* @see DragSourceExtension#addDragStartListener(DragStartListener)
*/
@FunctionalInterface
public interface DragStartListener extends ConnectorEventListener {
static final Method DRAGSTART_METHOD = DragStartListener.class
.getDeclaredMethods()[0];

/**
* Called when a server side dragstart event is fired.
*
* @param event
* The dragstart event that is fired.
*/
void dragStart(DragStartEvent event);
}

+ 77
- 0
server/src/main/java/com/vaadin/event/dnd/DropEvent.java View File

@@ -0,0 +1,77 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.ui.Component;

/**
* Server side drop event. Fired when an HTML5 drop happens.
*
* @see DropTargetExtension#addDropListener(DropListener)
*/
public class DropEvent extends Component.Event {
private final Map<String, String> data;
private final DropEffect dropEffect;

/**
* Creates a new server side drop event.
*
* @param source
* Drop target component.
* @param types
* List of data types from {@code DataTransfer.types}.
* @param data
* Map of all data from {@code DataTransfer}.
* @param dropEffect
* Parameter from {@code DataTransfer.dropEffect}.
*/
public DropEvent(Component source, List<String> types,
Map<String, String> data, DropEffect dropEffect) {
super(source);

// Create a linked map that preserves the order of types
this.data = new LinkedHashMap<>();
types.forEach(type -> this.data.put(type, data.get(type)));

this.dropEffect = dropEffect;
}

/**
* Get data from the client side {@code DataTransfer} object.
*
* @param format
* Data format, e.g. {@code text/plain} or {@code text/uri-list}.
* @return Data for the given format if exists in the client side {@code
* DataTransfer}, otherwise {@code null}.
*/
public String getTransferData(String format) {
return data != null ? data.get(format) : null;
}

/**
* Get drop effect set for the current drop target.
*
* @return {@code dropEffect} parameter set for the current drop target.
*/
public DropEffect getDropEffect() {
return dropEffect;
}
}

+ 38
- 0
server/src/main/java/com/vaadin/event/dnd/DropListener.java View File

@@ -0,0 +1,38 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.lang.reflect.Method;

import com.vaadin.event.ConnectorEventListener;

/**
* Interface to be implemented when creating a drop listener on a drop target
* for HTML5 drag and drop. See {@link DropTargetExtension#addDropListener(DropListener)}.
*/
@FunctionalInterface
public interface DropListener extends ConnectorEventListener {
static final Method DROP_METHOD = DropListener.class
.getDeclaredMethods()[0];

/**
* Called when a server side drop event is fired.
*
* @param event
* The drop event that is fired.
*/
void drop(DropEvent event);
}

+ 145
- 0
server/src/main/java/com/vaadin/event/dnd/DropTargetExtension.java View File

@@ -0,0 +1,145 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event.dnd;

import java.util.Objects;

import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.dnd.DropEffect;
import com.vaadin.shared.ui.dnd.DropTargetRpc;
import com.vaadin.shared.ui.dnd.DropTargetState;
import com.vaadin.ui.AbstractComponent;

/**
* Extension to add drop target functionality to a widget for using HTML5 drag
* and drop.
*/
public class DropTargetExtension extends AbstractExtension {

/**
* Constructor for {@link DropTargetExtension}.
*/
public DropTargetExtension() {
registerRpc((DropTargetRpc) (types, data, dropEffect) -> {
DropEvent event = new DropEvent((AbstractComponent) getParent(),
types, data, dropEffect);

fireEvent(event);
});
}

/**
* Makes {@code target} component a drop target.
*
* @param target
* Component to be extended.
*/
public void extend(AbstractComponent target) {
super.extend(target);
}

/**
* Sets the drop effect for the current drop target. Used for the client
* side {@code DataTransfer.dropEffect} parameter.
* <p>
* Default value is browser dependent and can depend on e.g. modifier keys.
*
* @param dropEffect
* The drop effect to be set. Cannot be {@code null}.
*/
public void setDropEffect(DropEffect dropEffect) {
if (dropEffect == null) {
throw new IllegalArgumentException("Drop effect cannot be null.");
}

if (!Objects.equals(getState(false).dropEffect, dropEffect)) {
getState().dropEffect = dropEffect;
}
}

/**
* Returns the drop effect for the current drop target.
*
* @return The drop effect of this drop target.
*/
public DropEffect getDropEffect() {
return getState(false).dropEffect;
}

/**
* Sets criteria to allow dragover event on the current drop target. The
* script executes when dragover event happens and stops the event in case
* the script returns {@code false}.
*
* @param criteriaScript
* JavaScript to be executed when dragover event happens or {@code
* null} to clear.
*/
public void setDragOverCriteria(String criteriaScript) {
if (!Objects.equals(getState(false).dragOverCriteria, criteriaScript)) {
getState().dragOverCriteria = criteriaScript;
}
}

/**
* Sets criteria to allow drop event on the current drop target. The script
* executes when drop event happens and stops the event in case the script
* returns {@code false}.
*
* @param criteriaScript
* JavaScript to be executed when drop event happens or {@code null}
* to clear.
*/
public void setDropCriteria(String criteriaScript) {
if (!Objects.equals(getState(false).dropCriteria, criteriaScript)) {
getState().dropCriteria = criteriaScript;
}
}

/**
* Returns the criteria for allowing drop event on the current drop target.
*
* @return JavaScript that executes when drop event happens.
*/
public String getDropCriteria() {
return getState(false).dropCriteria;
}

/**
* Attaches drop listener for the current drop target. {@link
* DropListener#drop(DropEvent)} is called when drop event happens on the
* client side.
*
* @param listener
* Listener to handle drop event.
* @return Handle to be used to remove this listener.
*/
public Registration addDropListener(DropListener listener) {
return addListener(DropEvent.class, listener, DropListener.DROP_METHOD);
}

@Override
protected DropTargetState getState() {
return (DropTargetState) super.getState();
}

@Override
protected DropTargetState getState(boolean markAsDirty) {
return (DropTargetState) super.getState(markAsDirty);
}
}

+ 35
- 0
shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceRpc.java View File

@@ -0,0 +1,35 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.dnd;

import com.vaadin.shared.communication.ServerRpc;

/**
* RPC for firing server side event when client side dragstart event happens on
* drag source.
*/
public interface DragSourceRpc extends ServerRpc {

/**
* Called when dragsource event happens on client side.
*/
public void dragStart();

/**
* Called when dragend event happens on client side.
*/
public void dragEnd();
}

+ 55
- 0
shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java View File

@@ -0,0 +1,55 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.dnd;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.vaadin.shared.communication.SharedState;

/**
* State class containing parameters for DragSourceExtension.
*/
public class DragSourceState extends SharedState {

/**
* Event identifier for dragend event.
*/
public static final String EVENT_DRAGEND = "dragend";

/**
* Event identifier for dragstart event.
*/
public static final String EVENT_DRAGSTART = "dragstart";

/**
* {@code DataTransfer.effectAllowed} parameter for the drag event.
*/
public EffectAllowed effectAllowed = EffectAllowed.UNINITIALIZED;

/**
* {@code DataTransfer.types} parameter. Used to keep track of data formats
* set for the drag event.
*/
public List<String> types = new ArrayList<>();

/**
* Used to store data in the {@code DataTransfer} object for the drag event.
*/
public Map<String, String> data = new HashMap<>();
}

+ 41
- 0
shared/src/main/java/com/vaadin/shared/ui/dnd/DropEffect.java View File

@@ -0,0 +1,41 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.dnd;

/**
* Used to specify the drop effect to use on dragenter or dragover events.
*/
public enum DropEffect {
/**
* A copy of the source item is made at the new location.
*/
COPY,

/**
* An item is moved to a new location.
*/
MOVE,

/**
* A link is established to the source at the new location.
*/
LINK,

/**
* The item may not be dropped.
*/
NONE
}

+ 42
- 0
shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetRpc.java View File

@@ -0,0 +1,42 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.dnd;

import java.util.List;
import java.util.Map;

import com.vaadin.shared.communication.ServerRpc;

/**
* RPC for firing server side drop event when client side drop event happens on
* drop target.
*/
public interface DropTargetRpc extends ServerRpc {

/**
* Called when drop event happens on client side.
*
* @param types
* Data types that are present in {@code data} map in the same order
* as found in {@code DataTransfer.types}.
* @param data
* Contains data from {@code DataTransfer} object.
* @param dropEffect
* Drop effect set for the drop target where drop happened.
*/
public void drop(List<String> types, Map<String, String> data,
DropEffect dropEffect);
}

+ 38
- 0
shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java View File

@@ -0,0 +1,38 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.dnd;

import com.vaadin.shared.communication.SharedState;

/**
* State class containing parameters for DropTargetExtension.
*/
public class DropTargetState extends SharedState {
/**
* {@code DataTransfer.dropEffect} parameter for the drag event
*/
public DropEffect dropEffect;

/**
* Criteria script to allow dragOver event on the element
*/
public String dragOverCriteria;

/**
* Criteria script to allow drop event on the element
*/
public String dropCriteria;
}

+ 81
- 0
shared/src/main/java/com/vaadin/shared/ui/dnd/EffectAllowed.java View File

@@ -0,0 +1,81 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.shared.ui.dnd;

/**
* Used to specify the effect that is allowed for a drag operation.
*/
public enum EffectAllowed {
/**
* The item may not be dropped.
*/
NONE("none"),

/**
* A copy of the source item may be made at the new location.
*/
COPY("copy"),

/**
* An item may be moved to a new location.
*/
MOVE("move"),

/**
* A link may be established to the source at the new location.
*/
LINK("link"),

/**
* A copy or move operation is permitted.
*/
COPY_MOVE("copyMove"),

/**
* A copy or link operation is permitted.
*/
COPY_LINK("copyLink"),

/**
* A link or move operation is permitted.
*/
LINK_MOVE("linkMove"),

/**
* All operations are permitted.
*/
ALL("all"),

/**
* Default state, equivalent to ALL
*/
UNINITIALIZED("uninitialized");

private final String value;

EffectAllowed(String value) {
this.value = value;
}

/**
* Get the string value that is accepted by the client side drag event.
*
* @return String value accepted by the client side drag event.
*/
public String getValue() {
return value;
}
}

+ 2
- 0
themes/src/main/themes/VAADIN/themes/valo/shared/_global.scss View File

@@ -345,6 +345,8 @@ $valo-shared-pathPrefix: null;

@include valo-drag-element;

@include valo-draggable;

@include valo-tooltip;

@include valo-contextmenu;

+ 6
- 0
themes/src/main/themes/VAADIN/themes/valo/shared/_overlay.scss View File

@@ -275,6 +275,12 @@ $v-selection-item-selection-color: $v-selection-color !default;
}
}

@mixin valo-draggable {
.v-draggable {
user-select: none !important;
}
}

/**
* Outputs the styles for generic dragging ghost elements.
*

+ 153
- 0
uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java View File

@@ -0,0 +1,153 @@
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.tests.dnd;

import java.util.Optional;

import com.vaadin.event.dnd.DragSourceExtension;
import com.vaadin.event.dnd.DropTargetExtension;
import com.vaadin.server.Extension;
import com.vaadin.server.Page;
import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUIWithLog;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;

public class DragAndDropCardShuffle extends AbstractTestUIWithLog {

// Data type for storing card position
private static final String DATA_INDEX = "index";

// Create cards
private final Label ace = new Label("A");
private final Label jack = new Label("J");
private final Label queen = new Label("Q");
private final Label king = new Label("K");

// Create desk
private final HorizontalLayout desk = new HorizontalLayout();

@Override
protected void setup(VaadinRequest request) {

// Create UI and add extensions
desk.addComponents(ace, jack, queen, king);

ace.setStyleName("card");
addDragSourceExtension(ace);
addDropTargetExtension(ace);

jack.setStyleName("card");
addDragSourceExtension(jack);
addDropTargetExtension(jack);

queen.setStyleName("card");
addDragSourceExtension(queen);
addDropTargetExtension(queen);

king.setStyleName("card");
addDragSourceExtension(king);
addDropTargetExtension(king);

addComponent(desk);

// Add styling
setStyle();
}

private void addDragSourceExtension(Label source) {
// Create and attach extension
DragSourceExtension dragSource = new DragSourceExtension();
dragSource.extend(source);

// Set component position as transfer data
dragSource.setTransferData(DATA_INDEX,
String.valueOf(desk.getComponentIndex(source)));

// Add listeners
dragSource.addDragStartListener(event -> {
event.getComponent().addStyleName("dragged");
log(((Label) event.getComponent()).getValue() + " dragstart");
});

dragSource.addDragEndListener(event -> {
event.getComponent().removeStyleName("dragged");
log(((Label) event.getComponent()).getValue() + " dragend");
});
}

private void addDropTargetExtension(Label target) {
// Create and attach extension
DropTargetExtension dropTarget = new DropTargetExtension();
dropTarget.extend(target);

// Add listener
dropTarget.addDropListener(event -> {
// Retrieve the source's position
int sourceIndex = Integer
.valueOf(event.getTransferData(DATA_INDEX));

// Find source component
Component source = desk.getComponent(sourceIndex);

// Swap source and target components
desk.replaceComponent(target, source);

// Get DragSource extension for target component and set position data
Optional<Extension> targetExt = target.getExtensions().stream()
.filter(e -> e instanceof DragSourceExtension).findFirst();
targetExt.ifPresent(extension -> {
((DragSourceExtension) extension).setTransferData(DATA_INDEX,
String.valueOf(desk.getComponentIndex(target)));
});

// Get DragSource extension for source component and set position data
Optional<Extension> sourceExt = source.getExtensions().stream()
.filter(e -> e instanceof DragSourceExtension).findFirst();
sourceExt.ifPresent(extension -> {
((DragSourceExtension) extension).setTransferData(DATA_INDEX,
String.valueOf(desk.getComponentIndex(source)));
});

log(((Label) source).getValue() + " dropped onto " + ((Label) event
.getComponent()).getValue());
});
}

private void setStyle() {
Page.Styles styles = Page.getCurrent().getStyles();

styles.add(".card {"
+ "width: 150px;"
+ "height: 200px;"
+ "border: 1px solid black;"
+ "border-radius: 7px;"
+ "padding-left: 10px;"
+ "color: red;"
+ "font-weight: bolder;"
+ "font-size: 25px;"
+ "background-color: gainsboro;"
+ "}");
styles.add(".v-drag-over {border-style: dashed;}");
styles.add(".dragged {opacity: .4;}");
}

@Override
protected String getTestDescription() {
return "Shuffle cards with pure HTML5 drag and drop";
}
}

Loading…
Cancel
Save