You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VDragAndDropManager.java 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui.dd;
  5. import com.google.gwt.core.client.GWT;
  6. import com.google.gwt.core.client.Scheduler;
  7. import com.google.gwt.core.client.Scheduler.RepeatingCommand;
  8. import com.google.gwt.dom.client.Element;
  9. import com.google.gwt.dom.client.EventTarget;
  10. import com.google.gwt.dom.client.NativeEvent;
  11. import com.google.gwt.dom.client.Node;
  12. import com.google.gwt.dom.client.Style;
  13. import com.google.gwt.dom.client.Style.Display;
  14. import com.google.gwt.dom.client.Style.Unit;
  15. import com.google.gwt.event.dom.client.KeyCodes;
  16. import com.google.gwt.event.shared.HandlerRegistration;
  17. import com.google.gwt.user.client.Command;
  18. import com.google.gwt.user.client.Event;
  19. import com.google.gwt.user.client.Event.NativePreviewEvent;
  20. import com.google.gwt.user.client.Event.NativePreviewHandler;
  21. import com.google.gwt.user.client.Timer;
  22. import com.google.gwt.user.client.ui.RootPanel;
  23. import com.google.gwt.user.client.ui.Widget;
  24. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  25. import com.vaadin.terminal.gwt.client.ComponentConnector;
  26. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  27. import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder;
  28. import com.vaadin.terminal.gwt.client.UIDL;
  29. import com.vaadin.terminal.gwt.client.Util;
  30. import com.vaadin.terminal.gwt.client.ValueMap;
  31. /**
  32. * Helper class to manage the state of drag and drop event on Vaadin client
  33. * side. Can be used to implement most of the drag and drop operation
  34. * automatically via cross-browser event preview method or just as a helper when
  35. * implementing own low level drag and drop operation (like with HTML5 api).
  36. * <p>
  37. * Singleton. Only one drag and drop operation can be active anyways. Use
  38. * {@link #get()} to get instance.
  39. *
  40. * TODO cancel drag and drop if more than one touches !?
  41. */
  42. public class VDragAndDropManager {
  43. public static final String ACTIVE_DRAG_SOURCE_STYLENAME = "v-active-drag-source";
  44. private final class DefaultDragAndDropEventHandler implements
  45. NativePreviewHandler {
  46. public void onPreviewNativeEvent(NativePreviewEvent event) {
  47. NativeEvent nativeEvent = event.getNativeEvent();
  48. int typeInt = event.getTypeInt();
  49. if (typeInt == Event.ONKEYDOWN) {
  50. int keyCode = event.getNativeEvent().getKeyCode();
  51. if (keyCode == KeyCodes.KEY_ESCAPE) {
  52. // end drag if ESC is hit
  53. interruptDrag();
  54. event.cancel();
  55. event.getNativeEvent().preventDefault();
  56. }
  57. // no use for handling for any key down event
  58. return;
  59. }
  60. currentDrag.setCurrentGwtEvent(nativeEvent);
  61. updateDragImagePosition();
  62. Element targetElement = Element.as(nativeEvent.getEventTarget());
  63. if (Util.isTouchEvent(nativeEvent)
  64. || (dragElement != null && dragElement
  65. .isOrHasChild(targetElement))) {
  66. // to detect the "real" target, hide dragelement temporary and
  67. // use elementFromPoint
  68. String display = dragElement.getStyle().getDisplay();
  69. dragElement.getStyle().setDisplay(Display.NONE);
  70. try {
  71. int x = Util.getTouchOrMouseClientX(nativeEvent);
  72. int y = Util.getTouchOrMouseClientY(nativeEvent);
  73. // Util.browserDebugger();
  74. targetElement = Util.getElementFromPoint(x, y);
  75. if (targetElement == null) {
  76. // ApplicationConnection.getConsole().log(
  77. // "Event on dragImage, ignored");
  78. event.cancel();
  79. nativeEvent.stopPropagation();
  80. return;
  81. } else {
  82. // ApplicationConnection.getConsole().log(
  83. // "Event on dragImage, target changed");
  84. // special handling for events over dragImage
  85. // pretty much all events are mousemove althout below
  86. // kind of happens mouseover
  87. switch (typeInt) {
  88. case Event.ONMOUSEOVER:
  89. case Event.ONMOUSEOUT:
  90. // ApplicationConnection
  91. // .getConsole()
  92. // .log(
  93. // "IGNORING proxy image event, fired because of hack or not significant");
  94. return;
  95. case Event.ONMOUSEMOVE:
  96. case Event.ONTOUCHMOVE:
  97. VDropHandler findDragTarget = findDragTarget(targetElement);
  98. if (findDragTarget != currentDropHandler) {
  99. // dragleave on old
  100. if (currentDropHandler != null) {
  101. currentDropHandler.dragLeave(currentDrag);
  102. currentDrag.getDropDetails().clear();
  103. serverCallback = null;
  104. }
  105. // dragenter on new
  106. currentDropHandler = findDragTarget;
  107. if (findDragTarget != null) {
  108. // ApplicationConnection.getConsole().log(
  109. // "DropHandler now"
  110. // + currentDropHandler
  111. // .getPaintable());
  112. }
  113. if (currentDropHandler != null) {
  114. currentDrag
  115. .setElementOver((com.google.gwt.user.client.Element) targetElement);
  116. currentDropHandler.dragEnter(currentDrag);
  117. }
  118. } else if (findDragTarget != null) {
  119. currentDrag
  120. .setElementOver((com.google.gwt.user.client.Element) targetElement);
  121. currentDropHandler.dragOver(currentDrag);
  122. }
  123. // prevent text selection on IE
  124. nativeEvent.preventDefault();
  125. return;
  126. default:
  127. // just update element over and let the actual
  128. // handling code do the thing
  129. // ApplicationConnection.getConsole().log(
  130. // "Target just modified on "
  131. // + event.getType());
  132. currentDrag
  133. .setElementOver((com.google.gwt.user.client.Element) targetElement);
  134. break;
  135. }
  136. }
  137. } catch (RuntimeException e) {
  138. // ApplicationConnection.getConsole().log(
  139. // "ERROR during elementFromPoint hack.");
  140. throw e;
  141. } finally {
  142. dragElement.getStyle().setProperty("display", display);
  143. }
  144. }
  145. switch (typeInt) {
  146. case Event.ONMOUSEOVER:
  147. VDropHandler target = findDragTarget(targetElement);
  148. if (target != null && target != currentDropHandler) {
  149. if (currentDropHandler != null) {
  150. currentDropHandler.dragLeave(currentDrag);
  151. currentDrag.getDropDetails().clear();
  152. }
  153. currentDropHandler = target;
  154. // ApplicationConnection.getConsole().log(
  155. // "DropHandler now"
  156. // + currentDropHandler.getPaintable());
  157. currentDrag
  158. .setElementOver((com.google.gwt.user.client.Element) targetElement);
  159. target.dragEnter(currentDrag);
  160. } else if (target == null && currentDropHandler != null) {
  161. // ApplicationConnection.getConsole().log("Invalid state!?");
  162. currentDropHandler.dragLeave(currentDrag);
  163. currentDrag.getDropDetails().clear();
  164. currentDropHandler = null;
  165. }
  166. break;
  167. case Event.ONMOUSEOUT:
  168. Element relatedTarget = Element.as(nativeEvent
  169. .getRelatedEventTarget());
  170. VDropHandler newDragHanler = findDragTarget(relatedTarget);
  171. if (dragElement != null
  172. && dragElement.isOrHasChild(relatedTarget)) {
  173. // ApplicationConnection.getConsole().log(
  174. // "Mouse out of dragImage, ignored");
  175. return;
  176. }
  177. if (currentDropHandler != null
  178. && currentDropHandler != newDragHanler) {
  179. currentDropHandler.dragLeave(currentDrag);
  180. currentDrag.getDropDetails().clear();
  181. currentDropHandler = null;
  182. serverCallback = null;
  183. }
  184. break;
  185. case Event.ONMOUSEMOVE:
  186. case Event.ONTOUCHMOVE:
  187. if (currentDropHandler != null) {
  188. currentDrag
  189. .setElementOver((com.google.gwt.user.client.Element) targetElement);
  190. currentDropHandler.dragOver(currentDrag);
  191. }
  192. nativeEvent.preventDefault();
  193. break;
  194. case Event.ONTOUCHEND:
  195. /* Avoid simulated event on drag end */
  196. event.getNativeEvent().preventDefault();
  197. case Event.ONMOUSEUP:
  198. endDrag();
  199. break;
  200. default:
  201. break;
  202. }
  203. }
  204. }
  205. public enum DragEventType {
  206. ENTER, LEAVE, OVER, DROP
  207. }
  208. public static final String DD_SERVICE = "DD";
  209. private static VDragAndDropManager instance;
  210. private HandlerRegistration handlerRegistration;
  211. private VDragEvent currentDrag;
  212. /**
  213. * If dragging is currently on a drophandler, this field has reference to it
  214. */
  215. private VDropHandler currentDropHandler;
  216. public VDropHandler getCurrentDropHandler() {
  217. return currentDropHandler;
  218. }
  219. /**
  220. * If drag and drop operation is not handled by {@link VDragAndDropManager}s
  221. * internal handler, this can be used to update current {@link VDropHandler}
  222. * .
  223. *
  224. * @param currentDropHandler
  225. */
  226. public void setCurrentDropHandler(VDropHandler currentDropHandler) {
  227. this.currentDropHandler = currentDropHandler;
  228. }
  229. private VDragEventServerCallback serverCallback;
  230. private HandlerRegistration deferredStartRegistration;
  231. public static VDragAndDropManager get() {
  232. if (instance == null) {
  233. instance = GWT.create(VDragAndDropManager.class);
  234. }
  235. return instance;
  236. }
  237. /* Singleton */
  238. private VDragAndDropManager() {
  239. }
  240. private NativePreviewHandler defaultDragAndDropEventHandler = new DefaultDragAndDropEventHandler();
  241. /**
  242. * Flag to indicate if drag operation has really started or not. Null check
  243. * of currentDrag field is not enough as a lazy start may be pending.
  244. */
  245. private boolean isStarted;
  246. /**
  247. * This method is used to start Vaadin client side drag and drop operation.
  248. * Operation may be started by virtually any Widget.
  249. * <p>
  250. * Cancels possible existing drag. TODO figure out if this is always a bug
  251. * if one is active. Maybe a good and cheap lifesaver thought.
  252. * <p>
  253. * If possible, method automatically detects current {@link VDropHandler}
  254. * and fires {@link VDropHandler#dragEnter(VDragEvent)} event on it.
  255. * <p>
  256. * May also be used to control the drag and drop operation. If this option
  257. * is used, {@link VDropHandler} is searched on mouse events and appropriate
  258. * methods on it called automatically.
  259. *
  260. * @param transferable
  261. * @param nativeEvent
  262. * @param handleDragEvents
  263. * if true, {@link VDragAndDropManager} handles the drag and drop
  264. * operation GWT event preview.
  265. * @return
  266. */
  267. public VDragEvent startDrag(VTransferable transferable,
  268. final NativeEvent startEvent, final boolean handleDragEvents) {
  269. interruptDrag();
  270. isStarted = false;
  271. currentDrag = new VDragEvent(transferable, startEvent);
  272. currentDrag.setCurrentGwtEvent(startEvent);
  273. final Command startDrag = new Command() {
  274. public void execute() {
  275. isStarted = true;
  276. addActiveDragSourceStyleName();
  277. VDropHandler dh = null;
  278. if (startEvent != null) {
  279. dh = findDragTarget(Element.as(currentDrag
  280. .getCurrentGwtEvent().getEventTarget()));
  281. }
  282. if (dh != null) {
  283. // drag has started on a DropHandler, kind of drag over
  284. // happens
  285. currentDropHandler = dh;
  286. dh.dragEnter(currentDrag);
  287. }
  288. if (handleDragEvents) {
  289. handlerRegistration = Event
  290. .addNativePreviewHandler(defaultDragAndDropEventHandler);
  291. if (dragElement != null
  292. && dragElement.getParentElement() == null) {
  293. // deferred attaching drag image is on going, we can
  294. // hurry with it now
  295. lazyAttachDragElement.cancel();
  296. lazyAttachDragElement.run();
  297. }
  298. }
  299. // just capture something to prevent text selection in IE
  300. Event.setCapture(RootPanel.getBodyElement());
  301. }
  302. private void addActiveDragSourceStyleName() {
  303. ComponentConnector dragSource = currentDrag.getTransferable()
  304. .getDragSource();
  305. dragSource.getWidget().addStyleName(
  306. ACTIVE_DRAG_SOURCE_STYLENAME);
  307. }
  308. };
  309. final int eventType = Event.as(startEvent).getTypeInt();
  310. if (handleDragEvents
  311. && (eventType == Event.ONMOUSEDOWN || eventType == Event.ONTOUCHSTART)) {
  312. // only really start drag event on mousemove
  313. deferredStartRegistration = Event
  314. .addNativePreviewHandler(new NativePreviewHandler() {
  315. public void onPreviewNativeEvent(
  316. NativePreviewEvent event) {
  317. int typeInt = event.getTypeInt();
  318. switch (typeInt) {
  319. case Event.ONMOUSEOVER:
  320. if (dragElement == null) {
  321. break;
  322. }
  323. EventTarget currentEventTarget = event
  324. .getNativeEvent()
  325. .getCurrentEventTarget();
  326. if (Node.is(currentEventTarget)
  327. && !dragElement.isOrHasChild(Node
  328. .as(currentEventTarget))) {
  329. // drag image appeared below, ignore
  330. break;
  331. }
  332. case Event.ONKEYDOWN:
  333. case Event.ONKEYPRESS:
  334. case Event.ONKEYUP:
  335. case Event.ONBLUR:
  336. case Event.ONFOCUS:
  337. // don't cancel possible drag start
  338. break;
  339. case Event.ONMOUSEOUT:
  340. if (dragElement == null) {
  341. break;
  342. }
  343. EventTarget relatedEventTarget = event
  344. .getNativeEvent()
  345. .getRelatedEventTarget();
  346. if (Node.is(relatedEventTarget)
  347. && !dragElement.isOrHasChild(Node
  348. .as(relatedEventTarget))) {
  349. // drag image appeared below, ignore
  350. break;
  351. }
  352. case Event.ONMOUSEMOVE:
  353. case Event.ONTOUCHMOVE:
  354. if (deferredStartRegistration != null) {
  355. deferredStartRegistration.removeHandler();
  356. deferredStartRegistration = null;
  357. }
  358. currentDrag.setCurrentGwtEvent(event
  359. .getNativeEvent());
  360. startDrag.execute();
  361. break;
  362. default:
  363. // on any other events, clean up the
  364. // deferred drag start
  365. if (deferredStartRegistration != null) {
  366. deferredStartRegistration.removeHandler();
  367. deferredStartRegistration = null;
  368. }
  369. currentDrag = null;
  370. clearDragElement();
  371. break;
  372. }
  373. }
  374. });
  375. } else {
  376. startDrag.execute();
  377. }
  378. return currentDrag;
  379. }
  380. private void updateDragImagePosition() {
  381. if (currentDrag.getCurrentGwtEvent() != null && dragElement != null) {
  382. Style style = dragElement.getStyle();
  383. int clientY = Util.getTouchOrMouseClientY(currentDrag
  384. .getCurrentGwtEvent());
  385. int clientX = Util.getTouchOrMouseClientX(currentDrag
  386. .getCurrentGwtEvent());
  387. style.setTop(clientY, Unit.PX);
  388. style.setLeft(clientX, Unit.PX);
  389. }
  390. }
  391. /**
  392. * First seeks the widget from this element, then iterates widgets until one
  393. * implement HasDropHandler. Returns DropHandler from that.
  394. *
  395. * @param element
  396. * @return
  397. */
  398. private VDropHandler findDragTarget(Element element) {
  399. try {
  400. Widget w = Util.findWidget(
  401. (com.google.gwt.user.client.Element) element, null);
  402. if (w == null) {
  403. return null;
  404. }
  405. while (!(w instanceof VHasDropHandler)) {
  406. w = w.getParent();
  407. if (w == null) {
  408. break;
  409. }
  410. }
  411. if (w == null) {
  412. return null;
  413. } else {
  414. VDropHandler dh = ((VHasDropHandler) w).getDropHandler();
  415. return dh;
  416. }
  417. } catch (Exception e) {
  418. // ApplicationConnection.getConsole().log(
  419. // "FIXME: Exception when detecting drop handler");
  420. // e.printStackTrace();
  421. return null;
  422. }
  423. }
  424. /**
  425. * Drag is ended (drop happened) on current drop handler. Calls drop method
  426. * on current drop handler and does appropriate cleanup.
  427. */
  428. public void endDrag() {
  429. endDrag(true);
  430. }
  431. /**
  432. * The drag and drop operation is ended, but drop did not happen. If
  433. * operation is currently on a drop handler, its dragLeave method is called
  434. * and appropriate cleanup happens.
  435. */
  436. public void interruptDrag() {
  437. endDrag(false);
  438. }
  439. private void endDrag(boolean doDrop) {
  440. if (handlerRegistration != null) {
  441. handlerRegistration.removeHandler();
  442. handlerRegistration = null;
  443. }
  444. boolean sendTransferableToServer = false;
  445. if (currentDropHandler != null) {
  446. if (doDrop) {
  447. // we have dropped on a drop target
  448. sendTransferableToServer = currentDropHandler.drop(currentDrag);
  449. if (sendTransferableToServer) {
  450. doRequest(DragEventType.DROP);
  451. /*
  452. * Clean active source class name deferred until response is
  453. * handled. E.g. hidden on start, removed in drophandler ->
  454. * would flicker in case removed eagerly.
  455. */
  456. final ComponentConnector dragSource = currentDrag
  457. .getTransferable().getDragSource();
  458. final ApplicationConnection client = currentDropHandler
  459. .getApplicationConnection();
  460. Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
  461. public boolean execute() {
  462. if (!client.hasActiveRequest()) {
  463. removeActiveDragSourceStyleName(dragSource);
  464. return false;
  465. }
  466. return true;
  467. }
  468. }, 30);
  469. }
  470. } else {
  471. currentDrag.setCurrentGwtEvent(null);
  472. currentDropHandler.dragLeave(currentDrag);
  473. }
  474. currentDropHandler = null;
  475. serverCallback = null;
  476. visitId = 0; // reset to ignore ongoing server check
  477. }
  478. /*
  479. * Remove class name indicating drag source when server visit is done
  480. * iff server visit was not initiated. Otherwise it will be removed once
  481. * the server visit is done.
  482. */
  483. if (!sendTransferableToServer && currentDrag != null) {
  484. removeActiveDragSourceStyleName(currentDrag.getTransferable()
  485. .getDragSource());
  486. }
  487. currentDrag = null;
  488. clearDragElement();
  489. // release the capture (set to prevent text selection in IE)
  490. Event.releaseCapture(RootPanel.getBodyElement());
  491. }
  492. private void removeActiveDragSourceStyleName(ComponentConnector dragSource) {
  493. dragSource.getWidget().removeStyleName(ACTIVE_DRAG_SOURCE_STYLENAME);
  494. }
  495. private void clearDragElement() {
  496. if (dragElement != null) {
  497. if (dragElement.getParentElement() != null) {
  498. RootPanel.getBodyElement().removeChild(dragElement);
  499. }
  500. dragElement = null;
  501. }
  502. }
  503. private int visitId = 0;
  504. private Element dragElement;
  505. /**
  506. * Visits server during drag and drop procedure. Transferable and event type
  507. * is given to server side counterpart of DropHandler.
  508. *
  509. * If another server visit is started before the current is received, the
  510. * current is just dropped. TODO consider if callback should have
  511. * interrupted() method for cleanup.
  512. *
  513. * @param acceptCallback
  514. */
  515. public void visitServer(VDragEventServerCallback acceptCallback) {
  516. doRequest(DragEventType.ENTER);
  517. serverCallback = acceptCallback;
  518. }
  519. private void doRequest(DragEventType drop) {
  520. if (currentDropHandler == null) {
  521. return;
  522. }
  523. ComponentConnector paintable = currentDropHandler.getConnector();
  524. ApplicationConnection client = currentDropHandler
  525. .getApplicationConnection();
  526. /*
  527. * For drag events we are using special id that are routed to
  528. * "drag service" which then again finds the corresponding DropHandler
  529. * on server side.
  530. *
  531. * TODO add rest of the data in Transferable
  532. *
  533. * TODO implement partial updates to Transferable (currently the whole
  534. * Transferable is sent on each request)
  535. */
  536. visitId++;
  537. client.updateVariable(DD_SERVICE, "visitId", visitId, false);
  538. client.updateVariable(DD_SERVICE, "eventId", currentDrag.getEventId(),
  539. false);
  540. client.updateVariable(DD_SERVICE, "dhowner", paintable, false);
  541. VTransferable transferable = currentDrag.getTransferable();
  542. client.updateVariable(DD_SERVICE, "component",
  543. transferable.getDragSource(), false);
  544. client.updateVariable(DD_SERVICE, "type", drop.ordinal(), false);
  545. if (currentDrag.getCurrentGwtEvent() != null) {
  546. try {
  547. MouseEventDetails mouseEventDetails = MouseEventDetailsBuilder
  548. .buildMouseEventDetails(currentDrag
  549. .getCurrentGwtEvent());
  550. currentDrag.getDropDetails().put("mouseEvent",
  551. mouseEventDetails.serialize());
  552. } catch (Exception e) {
  553. // NOP, (at least oophm on Safari) can't serialize html dd event
  554. // to mouseevent
  555. }
  556. } else {
  557. currentDrag.getDropDetails().put("mouseEvent", null);
  558. }
  559. client.updateVariable(DD_SERVICE, "evt", currentDrag.getDropDetails(),
  560. false);
  561. client.updateVariable(DD_SERVICE, "tra", transferable.getVariableMap(),
  562. true);
  563. }
  564. public void handleServerResponse(ValueMap valueMap) {
  565. if (serverCallback == null) {
  566. return;
  567. }
  568. UIDL uidl = (UIDL) valueMap.cast();
  569. int visitId = uidl.getIntAttribute("visitId");
  570. if (this.visitId == visitId) {
  571. serverCallback.handleResponse(uidl.getBooleanAttribute("accepted"),
  572. uidl);
  573. serverCallback = null;
  574. }
  575. runDeferredCommands();
  576. }
  577. private void runDeferredCommands() {
  578. if (deferredCommand != null) {
  579. Command command = deferredCommand;
  580. deferredCommand = null;
  581. command.execute();
  582. if (!isBusy()) {
  583. runDeferredCommands();
  584. }
  585. }
  586. }
  587. void setDragElement(Element node) {
  588. if (currentDrag != null) {
  589. if (dragElement != null && dragElement != node) {
  590. clearDragElement();
  591. } else if (node == dragElement) {
  592. return;
  593. }
  594. dragElement = node;
  595. dragElement.addClassName("v-drag-element");
  596. updateDragImagePosition();
  597. if (isStarted) {
  598. lazyAttachDragElement.run();
  599. } else {
  600. /*
  601. * To make our default dnd handler as compatible as possible, we
  602. * need to defer the appearance of dragElement. Otherwise events
  603. * that are derived from sequences of other events might not
  604. * fire as domchanged will fire between them or mouse up might
  605. * happen on dragElement.
  606. */
  607. lazyAttachDragElement.schedule(300);
  608. }
  609. }
  610. }
  611. Element getDragElement() {
  612. return dragElement;
  613. }
  614. private final Timer lazyAttachDragElement = new Timer() {
  615. @Override
  616. public void run() {
  617. if (dragElement != null && dragElement.getParentElement() == null) {
  618. RootPanel.getBodyElement().appendChild(dragElement);
  619. }
  620. }
  621. };
  622. private Command deferredCommand;
  623. private boolean isBusy() {
  624. return serverCallback != null;
  625. }
  626. /**
  627. * Method to que tasks until all dd related server visits are done
  628. *
  629. * @param command
  630. */
  631. private void defer(Command command) {
  632. deferredCommand = command;
  633. }
  634. /**
  635. * Method to execute commands when all existing dd related tasks are
  636. * completed (some may require server visit).
  637. * <p>
  638. * Using this method may be handy if criterion that uses lazy initialization
  639. * are used. Check
  640. * <p>
  641. * TODO Optimization: consider if we actually only need to keep the last
  642. * command in queue here.
  643. *
  644. * @param command
  645. */
  646. public void executeWhenReady(Command command) {
  647. if (isBusy()) {
  648. defer(command);
  649. } else {
  650. command.execute();
  651. }
  652. }
  653. }