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.

GridDragSourceConnector.java 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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.connectors.grid;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. import java.util.stream.Collectors;
  20. import com.google.gwt.animation.client.AnimationScheduler;
  21. import com.google.gwt.dom.client.Element;
  22. import com.google.gwt.dom.client.NativeEvent;
  23. import com.google.gwt.dom.client.Style;
  24. import com.google.gwt.dom.client.TableRowElement;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.Window;
  27. import com.vaadin.client.ServerConnector;
  28. import com.vaadin.client.WidgetUtil;
  29. import com.vaadin.client.extensions.DragSourceExtensionConnector;
  30. import com.vaadin.client.widget.escalator.RowContainer;
  31. import com.vaadin.client.widget.grid.selection.SelectionModel;
  32. import com.vaadin.client.widgets.Escalator;
  33. import com.vaadin.client.widgets.Grid;
  34. import com.vaadin.server.Page;
  35. import com.vaadin.shared.Range;
  36. import com.vaadin.shared.ui.Connect;
  37. import com.vaadin.shared.ui.dnd.DropEffect;
  38. import com.vaadin.shared.ui.grid.GridDragSourceRpc;
  39. import com.vaadin.shared.ui.grid.GridDragSourceState;
  40. import com.vaadin.shared.ui.grid.GridState;
  41. import com.vaadin.ui.GridDragSource;
  42. import elemental.events.Event;
  43. import elemental.json.Json;
  44. import elemental.json.JsonArray;
  45. import elemental.json.JsonObject;
  46. /**
  47. * Adds HTML5 drag and drop functionality to a {@link com.vaadin.client.widgets.Grid
  48. * Grid}'s rows. This is the client side counterpart of {@link GridDragSource}.
  49. *
  50. * @author Vaadin Ltd
  51. * @since
  52. */
  53. @Connect(GridDragSource.class)
  54. public class GridDragSourceConnector extends
  55. DragSourceExtensionConnector {
  56. private static final String STYLE_SUFFIX_DRAG_BADGE = "-drag-badge";
  57. private GridConnector gridConnector;
  58. /**
  59. * List of dragged item keys.
  60. */
  61. private List<String> draggedItemKeys;
  62. @Override
  63. protected void extend(ServerConnector target) {
  64. this.gridConnector = (GridConnector) target;
  65. // Set newly added rows draggable
  66. getGridBody().setNewEscalatorRowCallback(
  67. rows -> rows.forEach(this::setDraggable));
  68. // Add drag listeners to body element
  69. addDragListeners(getGridBody().getElement());
  70. }
  71. @Override
  72. protected void onDragStart(Event event) {
  73. // Collect the keys of dragged rows
  74. draggedItemKeys = getDraggedRows(event).stream()
  75. .map(row -> row.getString(GridState.JSONKEY_ROWKEY))
  76. .collect(Collectors.toList());
  77. // Add badge showing the number of dragged columns
  78. if (draggedItemKeys.size() > 1) {
  79. Element draggedRowElement = (Element) event.getTarget();
  80. Element badge = DOM.createSpan();
  81. badge.setClassName(
  82. gridConnector.getWidget().getStylePrimaryName() + "-row"
  83. + STYLE_SUFFIX_DRAG_BADGE);
  84. badge.setInnerHTML(draggedItemKeys.size() + "");
  85. badge.getStyle().setMarginLeft(
  86. getRelativeX(draggedRowElement, (NativeEvent) event) + 10,
  87. Style.Unit.PX);
  88. badge.getStyle().setMarginTop(
  89. getRelativeY(draggedRowElement, (NativeEvent) event)
  90. - draggedRowElement.getOffsetHeight() + 10,
  91. Style.Unit.PX);
  92. draggedRowElement.appendChild(badge);
  93. // Remove badge on the next animation frame. Drag image will still contain the badge.
  94. AnimationScheduler.get().requestAnimationFrame(timestamp -> {
  95. badge.removeFromParent();
  96. }, (Element) event.getTarget());
  97. }
  98. super.onDragStart(event);
  99. }
  100. private int getRelativeY(Element element, NativeEvent event) {
  101. int relativeTop = element.getAbsoluteTop() - Window.getScrollTop();
  102. return WidgetUtil.getTouchOrMouseClientY(event) - relativeTop;
  103. }
  104. private int getRelativeX(Element element, NativeEvent event) {
  105. int relativeLeft = element.getAbsoluteLeft() - Window.getScrollLeft();
  106. return WidgetUtil.getTouchOrMouseClientX(event) - relativeLeft;
  107. }
  108. @Override
  109. protected String createDataTransferText(Event dragStartEvent) {
  110. JsonArray dragData = toJsonArray(
  111. getDraggedRows(dragStartEvent).stream().map(this::getDragData)
  112. .collect(Collectors.toList()));
  113. return dragData.toJson();
  114. }
  115. @Override
  116. protected void sendDragStartEventToServer(Event dragStartEvent) {
  117. // Start server RPC with dragged item keys
  118. getRpcProxy(GridDragSourceRpc.class).dragStart(draggedItemKeys);
  119. }
  120. private List<JsonObject> getDraggedRows(Event dragStartEvent) {
  121. List<JsonObject> draggedRows = new ArrayList<>();
  122. if (dragStartEvent.getTarget() instanceof TableRowElement) {
  123. TableRowElement row = (TableRowElement) dragStartEvent.getTarget();
  124. int rowIndex = ((Escalator.AbstractRowContainer) getGridBody())
  125. .getLogicalRowIndex(row);
  126. JsonObject rowData = gridConnector.getDataSource().getRow(rowIndex);
  127. if (dragMultipleRows(rowData)) {
  128. getSelectedVisibleRows().forEach(draggedRows::add);
  129. } else {
  130. draggedRows.add(rowData);
  131. }
  132. }
  133. return draggedRows;
  134. }
  135. @Override
  136. protected void onDragEnd(Event event) {
  137. super.onDragEnd(event);
  138. // Clear item key list
  139. draggedItemKeys = null;
  140. }
  141. @Override
  142. protected void sendDragEndEventToServer(Event dragEndEvent,
  143. DropEffect dropEffect) {
  144. // Send server RPC with dragged item keys
  145. getRpcProxy(GridDragSourceRpc.class)
  146. .dragEnd(dropEffect, draggedItemKeys);
  147. }
  148. /**
  149. * Tells if multiple rows are dragged. Returns true if multiple selection is
  150. * allowed and a selected row is dragged.
  151. *
  152. * @param draggedRow
  153. * Data of dragged row.
  154. * @return {@code true} if multiple rows are dragged, {@code false}
  155. * otherwise.
  156. */
  157. private boolean dragMultipleRows(JsonObject draggedRow) {
  158. SelectionModel<JsonObject> selectionModel = getGrid()
  159. .getSelectionModel();
  160. return selectionModel.isSelectionAllowed()
  161. && selectionModel instanceof MultiSelectionModelConnector.MultiSelectionModel
  162. && selectionModel.isSelected(draggedRow);
  163. }
  164. /**
  165. * Collects the data of all selected visible rows.
  166. *
  167. * @return List of data of all selected visible rows.
  168. */
  169. private List<JsonObject> getSelectedVisibleRows() {
  170. return getSelectedRowsInRange(getEscalator().getVisibleRowRange());
  171. }
  172. /**
  173. * Get all selected rows from a subset of rows defined by {@code range}.
  174. *
  175. * @param range
  176. * Range of indexes.
  177. * @return List of data of all selected rows in the given range.
  178. */
  179. private List<JsonObject> getSelectedRowsInRange(Range range) {
  180. List<JsonObject> selectedRows = new ArrayList<>();
  181. for (int i = range.getStart(); i < range.getEnd(); i++) {
  182. JsonObject row = gridConnector.getDataSource().getRow(i);
  183. if (SelectionModel.isItemSelected(row)) {
  184. selectedRows.add(row);
  185. }
  186. }
  187. return selectedRows;
  188. }
  189. /**
  190. * Converts a list of {@link JsonObject}s to a {@link JsonArray}.
  191. *
  192. * @param objects
  193. * List of json objects.
  194. * @return Json array containing all json objects.
  195. */
  196. private JsonArray toJsonArray(List<JsonObject> objects) {
  197. JsonArray array = Json.createArray();
  198. for (int i = 0; i < objects.size(); i++) {
  199. array.set(i, objects.get(i));
  200. }
  201. return array;
  202. }
  203. /**
  204. * Gets drag data from the row data if exists or returns complete row data
  205. * otherwise.
  206. *
  207. * @param row
  208. * Row data.
  209. * @return Drag data if present or row data otherwise.
  210. */
  211. private JsonObject getDragData(JsonObject row) {
  212. return row.hasKey(GridDragSourceState.JSONKEY_DRAG_DATA)
  213. ? row.getObject(GridDragSourceState.JSONKEY_DRAG_DATA) : row;
  214. }
  215. @Override
  216. public void onUnregister() {
  217. super.onUnregister();
  218. // Remove draggable from all row elements in the escalator
  219. Range visibleRange = getEscalator().getVisibleRowRange();
  220. for (int i = visibleRange.getStart(); i < visibleRange.getEnd(); i++) {
  221. removeDraggable(getGridBody().getRowElement(i));
  222. }
  223. // Remove drag listeners from body element
  224. removeDragListeners(getGridBody().getElement());
  225. // Remove callback for newly added rows
  226. getGridBody().setNewEscalatorRowCallback(null);
  227. }
  228. private Grid<JsonObject> getGrid() {
  229. return gridConnector.getWidget();
  230. }
  231. private Escalator getEscalator() {
  232. return getGrid().getEscalator();
  233. }
  234. private RowContainer.BodyRowContainer getGridBody() {
  235. return getEscalator().getBody();
  236. }
  237. @Override
  238. public GridDragSourceState getState() {
  239. return (GridDragSourceState) super.getState();
  240. }
  241. }