diff options
author | Adam Wagner <wbadam@users.noreply.github.com> | 2017-02-09 07:52:15 +0100 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2017-02-10 15:59:44 +0200 |
commit | a9c8e66d14899796e786e643d434daf6f16cf062 (patch) | |
tree | 59253d3b06d3223bc2e19b2f8b1b4fd5fb8e3b77 | |
parent | 4445eae397818196ba1818470c73ccf34e2033ce (diff) | |
download | vaadin-framework-a9c8e66d14899796e786e643d434daf6f16cf062.tar.gz vaadin-framework-a9c8e66d14899796e786e643d434daf6f16cf062.zip |
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
19 files changed, 1535 insertions, 0 deletions
diff --git a/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java b/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java new file mode 100644 index 0000000000..25e2912b7a --- /dev/null +++ b/client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java @@ -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(); + } +} diff --git a/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java b/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java new file mode 100644 index 0000000000..5d72a76696 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java @@ -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(); + } +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DragEndEvent.java b/server/src/main/java/com/vaadin/event/dnd/DragEndEvent.java new file mode 100644 index 0000000000..6f094ce9e2 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DragEndEvent.java @@ -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; + } +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DragEndListener.java b/server/src/main/java/com/vaadin/event/dnd/DragEndListener.java new file mode 100644 index 0000000000..00a020353d --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DragEndListener.java @@ -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); +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DragSourceExtension.java b/server/src/main/java/com/vaadin/event/dnd/DragSourceExtension.java new file mode 100644 index 0000000000..d90e1bd8b8 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DragSourceExtension.java @@ -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); + } +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DragStartEvent.java b/server/src/main/java/com/vaadin/event/dnd/DragStartEvent.java new file mode 100644 index 0000000000..591a0e61a7 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DragStartEvent.java @@ -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; + } +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DragStartListener.java b/server/src/main/java/com/vaadin/event/dnd/DragStartListener.java new file mode 100644 index 0000000000..8d691dee6b --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DragStartListener.java @@ -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); +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DropEvent.java b/server/src/main/java/com/vaadin/event/dnd/DropEvent.java new file mode 100644 index 0000000000..382b93bc55 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DropEvent.java @@ -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; + } +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DropListener.java b/server/src/main/java/com/vaadin/event/dnd/DropListener.java new file mode 100644 index 0000000000..8cb96d1852 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DropListener.java @@ -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); +} diff --git a/server/src/main/java/com/vaadin/event/dnd/DropTargetExtension.java b/server/src/main/java/com/vaadin/event/dnd/DropTargetExtension.java new file mode 100644 index 0000000000..20819ec70f --- /dev/null +++ b/server/src/main/java/com/vaadin/event/dnd/DropTargetExtension.java @@ -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); + } +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceRpc.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceRpc.java new file mode 100644 index 0000000000..bbad423044 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceRpc.java @@ -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(); +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java new file mode 100644 index 0000000000..aeb4dd816b --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DragSourceState.java @@ -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<>(); +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DropEffect.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropEffect.java new file mode 100644 index 0000000000..5056ecfd78 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropEffect.java @@ -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 +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetRpc.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetRpc.java new file mode 100644 index 0000000000..dd21278277 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetRpc.java @@ -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); +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java new file mode 100644 index 0000000000..3021c0bd6b --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/DropTargetState.java @@ -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; +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/dnd/EffectAllowed.java b/shared/src/main/java/com/vaadin/shared/ui/dnd/EffectAllowed.java new file mode 100644 index 0000000000..2ff9f21980 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/dnd/EffectAllowed.java @@ -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; + } +} diff --git a/themes/src/main/themes/VAADIN/themes/valo/shared/_global.scss b/themes/src/main/themes/VAADIN/themes/valo/shared/_global.scss index 3c4761ad32..441e073dcf 100644 --- a/themes/src/main/themes/VAADIN/themes/valo/shared/_global.scss +++ b/themes/src/main/themes/VAADIN/themes/valo/shared/_global.scss @@ -345,6 +345,8 @@ $valo-shared-pathPrefix: null; @include valo-drag-element; + @include valo-draggable; + @include valo-tooltip; @include valo-contextmenu; diff --git a/themes/src/main/themes/VAADIN/themes/valo/shared/_overlay.scss b/themes/src/main/themes/VAADIN/themes/valo/shared/_overlay.scss index 1c6f0c6fbc..5b44e94cac 100644 --- a/themes/src/main/themes/VAADIN/themes/valo/shared/_overlay.scss +++ b/themes/src/main/themes/VAADIN/themes/valo/shared/_overlay.scss @@ -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. * diff --git a/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java b/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java new file mode 100644 index 0000000000..24c3940737 --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/dnd/DragAndDropCardShuffle.java @@ -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"; + } +} |