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.

GridDragger.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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.ui.components.grid;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.List;
  21. import java.util.Set;
  22. import com.vaadin.data.provider.ListDataProvider;
  23. import com.vaadin.shared.ui.dnd.DropEffect;
  24. import com.vaadin.shared.ui.grid.DropLocation;
  25. import com.vaadin.shared.ui.grid.DropMode;
  26. import com.vaadin.ui.Grid;
  27. /**
  28. * Allows dragging rows for reordering within a Grid and between separate Grids.
  29. * <p>
  30. * When dragging a selected row, all the visible selected rows are dragged. Note
  31. * that ONLY visible rows are taken into account.
  32. * <p>
  33. * <em>NOTE: this helper works only with {@link ListDataProvider} on both grids.
  34. * If you have another data provider, you should customize data provider
  35. * updating on drop with
  36. * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and
  37. * {@link #setTargetGridDropHandler(TargetDataProviderUpdater)}.</em>
  38. *
  39. * @param <T>
  40. * The Grid bean type.
  41. * @author Stephan Knitelius
  42. * @author Vaadin Ltd
  43. * @since
  44. */
  45. public class GridDragger<T> implements Serializable {
  46. private final GridDropTarget<T> gridDropTarget;
  47. private final GridDragSource<T> gridDragSource;
  48. private DropIndexCalculator<T> dropTargetIndexCalculator = null;
  49. private SourceDataProviderUpdater<T> sourceDataProviderUpdater = null;
  50. private TargetDataProviderUpdater<T> targetDataProviderUpdater = null;
  51. /**
  52. * Set of items currently being dragged.
  53. */
  54. private Set<T> draggedItems;
  55. private boolean addItemsToEnd = false;
  56. private boolean removeFromSource = true;
  57. /**
  58. * Enables DnD reordering for the rows in the given grid.
  59. * <p>
  60. * {@link DropMode#BETWEEN} is used.
  61. *
  62. * @param grid
  63. * Grid to be extended.
  64. */
  65. public GridDragger(Grid<T> grid) {
  66. this(grid, grid, DropMode.BETWEEN);
  67. }
  68. /**
  69. * Enables DnD reordering the rows in the given grid with the given drop
  70. * mode.
  71. * <p>
  72. * <em>NOTE: this only works when the grid has a
  73. * {@link ListDataProvider}.</em> Use the custom handlers
  74. * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and
  75. * {@link #setTargetGridDropHandler(TargetDataProviderUpdater)} for other
  76. * data providers.
  77. *
  78. * @param grid
  79. * the grid to enable row DnD reordering on
  80. * @param dropMode
  81. * DropMode to be used.
  82. */
  83. public GridDragger(Grid<T> grid, DropMode dropMode) {
  84. this(grid, grid, dropMode);
  85. }
  86. /**
  87. * Enables DnD moving of rows from the source grid to the target grid.
  88. * <p>
  89. * {@link DropMode#BETWEEN} is used.
  90. * <p>
  91. * <em>NOTE: this only works when the grids have a
  92. * {@link ListDataProvider}.</em> Use the custom handlers
  93. * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} and
  94. * {@link #setTargetGridDropHandler(TargetDataProviderUpdater)} for other
  95. * data providers.
  96. *
  97. * @param source
  98. * the source grid dragged from.
  99. * @param target
  100. * the target grid dropped to.
  101. */
  102. public GridDragger(Grid<T> source, Grid<T> target) {
  103. this(source, target, DropMode.BETWEEN);
  104. }
  105. /**
  106. * Enables DnD moving of rows from the source grid to the target grid with
  107. * the custom data provider updaters.
  108. * <p>
  109. * {@link DropMode#BETWEEN} is used.
  110. *
  111. * @param source
  112. * grid dragged from
  113. * @param target
  114. * grid dragged to
  115. * @param targetDataProviderUpdater
  116. * handler for updating target grid data provider
  117. * @param sourceDataProviderUpdater
  118. * handler for updating source grid data provider
  119. */
  120. public GridDragger(Grid<T> source, Grid<T> target,
  121. TargetDataProviderUpdater<T> targetDataProviderUpdater,
  122. SourceDataProviderUpdater<T> sourceDataProviderUpdater) {
  123. this(source, target, DropMode.BETWEEN);
  124. this.targetDataProviderUpdater = targetDataProviderUpdater;
  125. this.sourceDataProviderUpdater = sourceDataProviderUpdater;
  126. }
  127. /**
  128. * Enables DnD moving of rows from the source grid to the target grid with
  129. * the given drop mode.
  130. * <p>
  131. * <em>NOTE: this only works when the grids have a
  132. * {@link ListDataProvider}.</em> Use the other constructors or custom
  133. * handlers {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)}
  134. * and {@link #setTargetGridDropHandler(TargetDataProviderUpdater)} for
  135. * other data providers.
  136. *
  137. * @param source
  138. * the drag source grid
  139. * @param target
  140. * the drop target grid
  141. * @param dropMode
  142. * the drop mode to use
  143. */
  144. public GridDragger(Grid<T> source, Grid<T> target, DropMode dropMode) {
  145. gridDragSource = new GridDragSource<>(source);
  146. gridDropTarget = new GridDropTarget<>(target, dropMode);
  147. gridDragSource.addGridDragStartListener(event -> {
  148. draggedItems = event.getDraggedItems();
  149. });
  150. gridDropTarget.addGridDropListener(event -> {
  151. if (removeFromSource) {
  152. handleSourceGridDrop(draggedItems);
  153. }
  154. int index = calculateDropIndex(event);
  155. handleTargetGridDrop(index, draggedItems);
  156. });
  157. }
  158. /**
  159. * Sets the target data provider updater, which handles adding the dropped
  160. * items to the target grid.
  161. * <p>
  162. * By default, items are added to the index where they were dropped on for
  163. * any {@link ListDataProvider}. If another type of data provider is used,
  164. * this updater should be set to handle updating instead.
  165. *
  166. * @param targetDataProviderUpdater
  167. * the target drop handler to set, or {@code null} to remove
  168. */
  169. public void setTargetGridDropHandler(
  170. TargetDataProviderUpdater<T> targetDataProviderUpdater) {
  171. this.targetDataProviderUpdater = targetDataProviderUpdater;
  172. }
  173. /**
  174. * Returns the target grid data provider updater.
  175. *
  176. * @return target grid drop handler
  177. */
  178. public TargetDataProviderUpdater<T> getTargetGridDropHandler() {
  179. return targetDataProviderUpdater;
  180. }
  181. /**
  182. * Sets the source data provider updater, which handles removing items from
  183. * the drag source grid.
  184. * <p>
  185. * By default the items are removed from any {@link ListDataProvider}. If
  186. * another type of data provider is used, this updater should be set to
  187. * handle updating instead.
  188. * <p>
  189. * <em>NOTE: this is not triggered when
  190. * {@link #setRemoveItemsFromSource(boolean)} has been set to
  191. * {@code false}</em>
  192. *
  193. * @param sourceDataProviderUpdater
  194. * the drag source data provider updater to set, or {@code null}
  195. * to remove
  196. */
  197. public void setSourceDataProviderUpdater(
  198. SourceDataProviderUpdater<T> sourceDataProviderUpdater) {
  199. this.sourceDataProviderUpdater = sourceDataProviderUpdater;
  200. }
  201. /**
  202. * Returns the source grid data provider updater.
  203. * <p>
  204. * Default is {@code null} and the items are just removed from the source
  205. * grid, which only works for {@link ListDataProvider}.
  206. *
  207. * @return the source grid drop handler
  208. */
  209. public SourceDataProviderUpdater<T> getSourceGridDropHandler() {
  210. return sourceDataProviderUpdater;
  211. }
  212. /**
  213. * Sets the drop index calculator for the target grid. With this callback
  214. * you can have a custom drop location instead of the actual one.
  215. * <p>
  216. * By default, items are placed on the index they are dropped into in the
  217. * target grid.
  218. * <p>
  219. * <em>NOTE: this will override {@link #setAddItemsToEnd(boolean)}.</em>
  220. *
  221. * @param dropIndexCalculator
  222. * the drop index calculator
  223. */
  224. public void setDropIndexCalculator(
  225. DropIndexCalculator<T> dropIndexCalculator) {
  226. this.dropTargetIndexCalculator = dropIndexCalculator;
  227. }
  228. /**
  229. * Gets the drop index calculator.
  230. * <p>
  231. * Default is {@code null} and the dropped items are placed on the drop
  232. * location.
  233. *
  234. * @return the drop index calculator
  235. */
  236. public DropIndexCalculator<T> getDropIndexCalculator() {
  237. return dropTargetIndexCalculator;
  238. }
  239. /**
  240. * Returns the drop target grid to allow performing customizations such as
  241. * altering {@link DropEffect}.
  242. *
  243. * @return the drop target grid
  244. */
  245. public GridDropTarget<T> getGridDropTarget() {
  246. return gridDropTarget;
  247. }
  248. /**
  249. * Returns the drag source grid, exposing it for customizations.
  250. *
  251. * @return the drag source grid
  252. */
  253. public GridDragSource<T> getGridDragSource() {
  254. return gridDragSource;
  255. }
  256. /**
  257. * Sets whether the items should be always added to the end instead of the
  258. * dropped position in target grid.
  259. * <p>
  260. * The default is {@code false} (added to dropped position).
  261. * <p>
  262. * <em>NOTE: this applies only when no custom index calculator is set with
  263. * {@link #setDropIndexCalculator(DropIndexCalculator)}.</em>
  264. *
  265. * @param addItemsToEnd
  266. * {@code true} for adding items to the end of the grid,
  267. * {@code false} for adding to the dropped position
  268. */
  269. public void setAddItemsToEnd(boolean addItemsToEnd) {
  270. this.addItemsToEnd = addItemsToEnd;
  271. }
  272. /**
  273. * Returns whether items are added to end instead of selected position.
  274. * <p>
  275. * <em>NOTE: this applies only when no custom index calculator is set with
  276. * {@link #setDropIndexCalculator(DropIndexCalculator)}.</em>
  277. *
  278. * @return return whether items are added to end or to the dropped position
  279. */
  280. public boolean isAddItemsToEnd() {
  281. return addItemsToEnd;
  282. }
  283. /**
  284. * Sets whether the items should be removed from the source grid or not.
  285. * <p>
  286. * Default value is {@code true} and the dropped items are removed from the
  287. * source grid.
  288. * <p>
  289. * <em>NOTE: when this is set to {@code false}, any custom handler with
  290. * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} is not
  291. * triggered on drop.</em>
  292. *
  293. * @param removeFromSource
  294. * {@code true} to remove dropped items, {@code false} to not
  295. * remove.
  296. */
  297. public void setRemoveItemsFromSource(boolean removeFromSource) {
  298. this.removeFromSource = removeFromSource;
  299. }
  300. /**
  301. * Returns whether dropped items are removed from the source grid or not.
  302. * <p>
  303. * Default value is {@code true} and the dropped items are removed from the
  304. * source grid.
  305. * <p>
  306. * <em>NOTE: when this is set to {@code false}, any custom handler with
  307. * {@link #setSourceDataProviderUpdater(SourceDataProviderUpdater)} is not
  308. * triggered on drop.</em>
  309. *
  310. * @return {@code true} to remove dropped items, {@code false} to not
  311. * remove.
  312. */
  313. public boolean isRemoveItemsFromSource() {
  314. return removeFromSource;
  315. }
  316. private void handleSourceGridDrop(final Collection<T> droppedItems) {
  317. Grid<T> source = getGridDragSource().getGrid();
  318. if (getSourceGridDropHandler() == null) {
  319. if (!(source.getDataProvider() instanceof ListDataProvider)) {
  320. throwIllegalStateExceptionForUnsupportedDataProvider(true);
  321. }
  322. ListDataProvider<T> listDataProvider = (ListDataProvider<T>) source
  323. .getDataProvider();
  324. List<T> sourceItems = new ArrayList<>(listDataProvider.getItems());
  325. sourceItems.removeAll(droppedItems);
  326. source.setItems(sourceItems);
  327. } else {
  328. getSourceGridDropHandler().removeItems(source.getDataProvider(),
  329. droppedItems);
  330. }
  331. }
  332. private void handleTargetGridDrop(final int index,
  333. Collection<T> droppedItems) {
  334. Grid<T> target = getGridDropTarget().getGrid();
  335. if (targetDataProviderUpdater == null) {
  336. if (!(target.getDataProvider() instanceof ListDataProvider)) {
  337. throwIllegalStateExceptionForUnsupportedDataProvider(false);
  338. }
  339. ListDataProvider<T> listDataProvider = (ListDataProvider<T>) target
  340. .getDataProvider();
  341. List<T> targetItems = new ArrayList<>(listDataProvider.getItems());
  342. targetItems.addAll(index, droppedItems);
  343. target.setItems(targetItems);
  344. } else {
  345. getTargetGridDropHandler().onDrop(target.getDataProvider(), index,
  346. droppedItems);
  347. }
  348. }
  349. private int calculateDropIndex(GridDropEvent<T> event) {
  350. if (getDropIndexCalculator() == null) {
  351. if (!addItemsToEnd) {
  352. ListDataProvider<T> targetDataProvider = (ListDataProvider<T>) getGridDropTarget()
  353. .getGrid().getDataProvider();
  354. List<T> items = new ArrayList<>(targetDataProvider.getItems());
  355. int index = items.size();
  356. if (event.getDropTargetRow().isPresent()) {
  357. index = items.indexOf(event.getDropTargetRow().get())
  358. + (event.getDropLocation() == DropLocation.BELOW ? 1
  359. : 0);
  360. }
  361. return index;
  362. }
  363. return Integer.MAX_VALUE;
  364. } else {
  365. return getDropIndexCalculator().calculateDropIndex(event);
  366. }
  367. }
  368. private static void throwIllegalStateExceptionForUnsupportedDataProvider(boolean sourceGrid) {
  369. throw new IllegalStateException(
  370. new StringBuilder().append(sourceGrid ? "Source " : "Target ")
  371. .append("grid does not have a ListDataProvider, cannot automatically ")
  372. .append(sourceGrid ? "remove " : "add ")
  373. .append("items. Use GridDragger.set")
  374. .append(sourceGrid ? "Source" : "Target")
  375. .append("GridDropHandler(...) to customize how to handle updating the data provider.")
  376. .toString());
  377. }
  378. }