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 /client | |
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
Diffstat (limited to 'client')
-rw-r--r-- | client/src/main/java/com/vaadin/client/extensions/DragSourceExtensionConnector.java | 139 | ||||
-rw-r--r-- | client/src/main/java/com/vaadin/client/extensions/DropTargetExtensionConnector.java | 223 |
2 files changed, 362 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(); + } +} |