Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

DragSourceExtensionConnector.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.extensions;
  17. import com.google.gwt.animation.client.AnimationScheduler;
  18. import com.google.gwt.dom.client.DataTransfer;
  19. import com.google.gwt.dom.client.Element;
  20. import com.google.gwt.dom.client.NativeEvent;
  21. import com.google.gwt.dom.client.Style;
  22. import com.google.gwt.dom.client.Style.Position;
  23. import com.google.gwt.user.client.ui.Image;
  24. import com.google.gwt.user.client.ui.Widget;
  25. import com.vaadin.client.BrowserInfo;
  26. import com.vaadin.client.ComponentConnector;
  27. import com.vaadin.client.ServerConnector;
  28. import com.vaadin.client.annotations.OnStateChange;
  29. import com.vaadin.client.ui.AbstractComponentConnector;
  30. import com.vaadin.shared.ui.Connect;
  31. import com.vaadin.shared.ui.dnd.DragSourceRpc;
  32. import com.vaadin.shared.ui.dnd.DragSourceState;
  33. import com.vaadin.shared.ui.dnd.DropEffect;
  34. import com.vaadin.ui.dnd.DragSourceExtension;
  35. import elemental.events.Event;
  36. import elemental.events.EventListener;
  37. import elemental.events.EventTarget;
  38. /**
  39. * Extension to add drag source functionality to a widget for using HTML5 drag
  40. * and drop. Client side counterpart of {@link DragSourceExtension}.
  41. *
  42. * @author Vaadin Ltd
  43. * @since 8.1
  44. */
  45. @Connect(DragSourceExtension.class)
  46. public class DragSourceExtensionConnector extends AbstractExtensionConnector {
  47. /**
  48. * Style suffix for indicating that the element is a drag source.
  49. */
  50. protected static final String STYLE_SUFFIX_DRAGSOURCE = "-dragsource";
  51. private static final String STYLE_NAME_DRAGGABLE = "v-draggable";
  52. // Create event listeners
  53. private final EventListener dragStartListener = this::onDragStart;
  54. private final EventListener dragEndListener = this::onDragEnd;
  55. /**
  56. * Widget of the drag source component.
  57. */
  58. private Widget dragSourceWidget;
  59. @Override
  60. protected void extend(ServerConnector target) {
  61. dragSourceWidget = ((ComponentConnector) target).getWidget();
  62. // HTML5 DnD is by default not enabled for mobile devices
  63. if (BrowserInfo.get().isTouchDevice() && !getConnection()
  64. .getUIConnector().isMobileHTML5DndEnabled()) {
  65. return;
  66. }
  67. addDraggable(getDraggableElement());
  68. addDragListeners(getDraggableElement());
  69. ((AbstractComponentConnector) target).onDragSourceAttached();
  70. }
  71. /**
  72. * Makes the given element draggable and adds class name.
  73. *
  74. * @param element
  75. * Element to be set draggable.
  76. */
  77. protected void addDraggable(Element element) {
  78. element.setDraggable(Element.DRAGGABLE_TRUE);
  79. element.addClassName(
  80. getStylePrimaryName(element) + STYLE_SUFFIX_DRAGSOURCE);
  81. element.addClassName(STYLE_NAME_DRAGGABLE);
  82. }
  83. /**
  84. * Removes draggable and class name from the given element.
  85. *
  86. * @param element
  87. * Element to remove draggable from.
  88. */
  89. protected void removeDraggable(Element element) {
  90. element.setDraggable(Element.DRAGGABLE_FALSE);
  91. element.removeClassName(
  92. getStylePrimaryName(element) + STYLE_SUFFIX_DRAGSOURCE);
  93. element.removeClassName(STYLE_NAME_DRAGGABLE);
  94. }
  95. /**
  96. * Adds dragstart and dragend event listeners to the given DOM element.
  97. *
  98. * @param element
  99. * DOM element to attach event listeners to.
  100. */
  101. protected void addDragListeners(Element element) {
  102. EventTarget target = element.cast();
  103. target.addEventListener(Event.DRAGSTART, dragStartListener);
  104. target.addEventListener(Event.DRAGEND, dragEndListener);
  105. }
  106. /**
  107. * Removes dragstart and dragend event listeners from the given DOM element.
  108. *
  109. * @param element
  110. * DOM element to remove event listeners from.
  111. */
  112. protected void removeDragListeners(Element element) {
  113. EventTarget target = element.cast();
  114. target.removeEventListener(Event.DRAGSTART, dragStartListener);
  115. target.removeEventListener(Event.DRAGEND, dragEndListener);
  116. }
  117. @Override
  118. public void onUnregister() {
  119. super.onUnregister();
  120. Element dragSource = getDraggableElement();
  121. removeDraggable(dragSource);
  122. removeDragListeners(dragSource);
  123. ((AbstractComponentConnector) getParent()).onDragSourceDetached();
  124. }
  125. @OnStateChange("resources")
  126. private void prefetchDragImage() {
  127. String dragImageUrl = getResourceUrl(
  128. DragSourceState.RESOURCE_DRAG_IMAGE);
  129. if (dragImageUrl != null && !dragImageUrl.isEmpty()) {
  130. Image.prefetch(getConnection().translateVaadinUri(dragImageUrl));
  131. }
  132. }
  133. /**
  134. * Event handler for the {@code dragstart} event. Called when {@code
  135. * dragstart} event occurs.
  136. *
  137. * @param event
  138. * browser event to be handled
  139. */
  140. protected void onDragStart(Event event) {
  141. // Convert elemental event to have access to dataTransfer
  142. NativeEvent nativeEvent = (NativeEvent) event;
  143. // Do not allow drag starts from native Android Chrome, since it doesn't
  144. // work properly (doesn't fire dragend reliably)
  145. if (isAndoidChrome() && isNativeDragEvent(nativeEvent)) {
  146. event.preventDefault();
  147. event.stopPropagation();
  148. return;
  149. }
  150. // Set effectAllowed parameter
  151. if (getState().effectAllowed != null) {
  152. setEffectAllowed(nativeEvent.getDataTransfer(),
  153. getState().effectAllowed.getValue());
  154. }
  155. // Set drag image
  156. setDragImage(nativeEvent);
  157. // Set text data parameter
  158. String dataTransferText = createDataTransferText(nativeEvent);
  159. // Always set something as the text data, or DnD won't work in FF !
  160. if (dataTransferText == null) {
  161. dataTransferText = "";
  162. }
  163. nativeEvent.getDataTransfer().setData(DragSourceState.DATA_TYPE_TEXT,
  164. dataTransferText);
  165. // Initiate firing server side dragstart event when there is a
  166. // DragStartListener attached on the server side
  167. if (hasEventListener(DragSourceState.EVENT_DRAGSTART)) {
  168. sendDragStartEventToServer(nativeEvent);
  169. }
  170. // Stop event bubbling
  171. nativeEvent.stopPropagation();
  172. }
  173. /**
  174. * Fixes missing drag image for Safari by making the dragged element
  175. * position to relative if needed. Safari won't show drag image unless the
  176. * dragged element position is relative or absolute / fixed, but not with
  177. * display block for the latter.
  178. * <p>
  179. * This method is a NOOP for non-safari browser.
  180. * <p>
  181. * This fix is not needed if a custom drag image is used on Safari.
  182. *
  183. * @param draggedElement
  184. * the element that forms the drag image
  185. */
  186. protected void fixDragImageForSafari(Element draggedElement) {
  187. if (!BrowserInfo.get().isSafari()) {
  188. return;
  189. }
  190. final Style style = draggedElement.getStyle();
  191. final String position = style.getPosition();
  192. // relative works always
  193. if ("relative".equalsIgnoreCase(position)) {
  194. return;
  195. }
  196. // absolute & fixed don't work when there is offset used
  197. if ("absolute".equalsIgnoreCase(position)
  198. || "fixed".equalsIgnoreCase(position)) {
  199. // FIXME #9261 need to figure out how to get absolute and fixed to
  200. // position work when there is offset involved, like in Grid.
  201. // The following hack with setting position to relative did not
  202. // work, nor did clearing top/right/bottom/left.
  203. }
  204. // for all other positions, set the position to relative and revert it
  205. // in an animation frame
  206. draggedElement.getStyle().setPosition(Position.RELATIVE);
  207. AnimationScheduler.get().requestAnimationFrame(timestamp -> {
  208. draggedElement.getStyle().setProperty("position", position);
  209. }, draggedElement);
  210. }
  211. /**
  212. * Creates data of type {@code "text"} for the {@code DataTransfer} object
  213. * of the given event.
  214. *
  215. * @param dragStartEvent
  216. * Event to set the data for.
  217. * @return Textual data to be set for the event or {@literal null}.
  218. */
  219. protected String createDataTransferText(NativeEvent dragStartEvent) {
  220. return getState().dataTransferText;
  221. }
  222. /**
  223. * Initiates a server RPC for the drag start event.
  224. * <p>
  225. * This method is called only if there is a server side drag start event
  226. * handler attached.
  227. *
  228. * @param dragStartEvent
  229. * Client side dragstart event.
  230. */
  231. protected void sendDragStartEventToServer(NativeEvent dragStartEvent) {
  232. getRpcProxy(DragSourceRpc.class).dragStart();
  233. }
  234. /**
  235. * Sets the drag image to be displayed.
  236. * <p>
  237. * Override this method in case you need custom drag image setting. Called
  238. * from {@link #onDragStart(Event)}.
  239. *
  240. * @param dragStartEvent
  241. * The drag start event.
  242. */
  243. protected void setDragImage(NativeEvent dragStartEvent) {
  244. String imageUrl = getResourceUrl(DragSourceState.RESOURCE_DRAG_IMAGE);
  245. if (imageUrl != null && !imageUrl.isEmpty()) {
  246. Image dragImage = new Image(
  247. getConnection().translateVaadinUri(imageUrl));
  248. dragStartEvent.getDataTransfer()
  249. .setDragImage(dragImage.getElement(), 0, 0);
  250. } else {
  251. fixDragImageForSafari(
  252. (Element) dragStartEvent.getCurrentEventTarget().cast());
  253. }
  254. }
  255. /**
  256. * Event handler for the {@code dragend} event. Called when {@code dragend}
  257. * event occurs.
  258. *
  259. * @param event
  260. * browser event to be handled
  261. */
  262. protected void onDragEnd(Event event) {
  263. NativeEvent nativeEvent = (NativeEvent) event;
  264. // for android chrome we use the polyfill, in case browser fires a
  265. // native dragend event after the polyfill dragend, we need to ignore
  266. // that one
  267. if (isAndoidChrome() && isNativeDragEvent((nativeEvent))) {
  268. event.preventDefault();
  269. event.stopPropagation();
  270. return;
  271. }
  272. // Initiate server start dragend event when there is a DragEndListener
  273. // attached on the server side
  274. if (hasEventListener(DragSourceState.EVENT_DRAGEND)) {
  275. String dropEffect = getDropEffect(nativeEvent.getDataTransfer());
  276. assert dropEffect != null : "Drop effect should never be null";
  277. sendDragEndEventToServer(nativeEvent,
  278. DropEffect.valueOf(dropEffect.toUpperCase()));
  279. }
  280. }
  281. /**
  282. * Initiates a server RPC for the drag end event.
  283. *
  284. * @param dragEndEvent
  285. * Client side dragend event.
  286. * @param dropEffect
  287. * Drop effect of the dragend event, extracted from {@code
  288. * DataTransfer.dropEffect} parameter.
  289. */
  290. protected void sendDragEndEventToServer(NativeEvent dragEndEvent,
  291. DropEffect dropEffect) {
  292. getRpcProxy(DragSourceRpc.class).dragEnd(dropEffect);
  293. }
  294. /**
  295. * Finds the draggable element within the widget. By default, returns the
  296. * topmost element.
  297. * <p>
  298. * Override this method to make some other than the root element draggable
  299. * instead.
  300. * <p>
  301. * In case you need to make more than whan element draggable, override
  302. * {@link #extend(ServerConnector)} instead.
  303. *
  304. * @return the draggable element in the parent widget.
  305. */
  306. protected Element getDraggableElement() {
  307. return dragSourceWidget.getElement();
  308. }
  309. /**
  310. * Returns whether the given event is a native (android) drag start/end
  311. * event, and not produced by the drag-drop-polyfill.
  312. *
  313. * @param nativeEvent
  314. * the event to test
  315. * @return {@code true} if native event, {@code false} if not (polyfill
  316. * event)
  317. */
  318. protected boolean isNativeDragEvent(NativeEvent nativeEvent) {
  319. return isTrusted(nativeEvent) || isComposed(nativeEvent);
  320. }
  321. /**
  322. * Returns whether the current browser is Android Chrome.
  323. *
  324. * @return {@code true} if Android Chrome, {@code false} if not
  325. *
  326. */
  327. protected boolean isAndoidChrome() {
  328. BrowserInfo browserInfo = BrowserInfo.get();
  329. return browserInfo.isAndroid() && browserInfo.isChrome();
  330. }
  331. private native boolean isTrusted(NativeEvent event)
  332. /*-{
  333. return event.isTrusted;
  334. }-*/;
  335. private native boolean isComposed(NativeEvent event)
  336. /*-{
  337. return event.isComposed;
  338. }-*/;
  339. private native void setEffectAllowed(DataTransfer dataTransfer,
  340. String effectAllowed)
  341. /*-{
  342. dataTransfer.effectAllowed = effectAllowed;
  343. }-*/;
  344. /**
  345. * Returns the dropEffect for the given data transfer.
  346. *
  347. * @param dataTransfer
  348. * the data transfer with drop effect
  349. * @return the currently set drop effect
  350. */
  351. protected static native String getDropEffect(DataTransfer dataTransfer)
  352. /*-{
  353. return dataTransfer.dropEffect;
  354. }-*/;
  355. @Override
  356. public DragSourceState getState() {
  357. return (DragSourceState) super.getState();
  358. }
  359. private native boolean getStylePrimaryName(Element element)
  360. /*-{
  361. return @com.google.gwt.user.client.ui.UIObject::getStylePrimaryName(Lcom/google/gwt/dom/client/Element;)(element);
  362. }-*/;
  363. }