diff options
author | Pekka Hyvönen <pekka@vaadin.com> | 2015-03-03 11:15:06 +0200 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2015-03-04 09:24:00 +0200 |
commit | de2172eda96ca3f34b2ab4e2e926b13957c113de (patch) | |
tree | ddd7e7cddfd47cf33b0837f8486097e92de89481 | |
parent | 40916228681314c843b32e59fc91bbec18548332 (diff) | |
download | vaadin-framework-de2172eda96ca3f34b2ab4e2e926b13957c113de.tar.gz vaadin-framework-de2172eda96ca3f34b2ab4e2e926b13957c113de.zip |
Limit dnd-reorder of columns inside spanned headers. (#16643)
Doesn't take footers into account, yet.
Change-Id: I9c62ca97bcbdb852f2fe7ad7ea2e7de0f0ed02f8
4 files changed, 232 insertions, 58 deletions
diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java index eec52d5def..0710606818 100644 --- a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java +++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java @@ -46,12 +46,15 @@ public class DragAndDropHandler { */ public interface DragAndDropCallback { /** - * Called when the drag has started. + * Called when the drag has started. The drag can be canceled by + * returning {@code false}. * * @param startEvent * the original event that started the drag + * @return {@code true} if the drag is OK to start, {@code false} to + * cancel */ - void onDragStart(NativeEvent startEvent); + boolean onDragStart(NativeEvent startEvent); /** * Called on drag. @@ -204,13 +207,15 @@ public class DragAndDropHandler { private void startDrag(NativeEvent startEvent, NativePreviewEvent triggerEvent, DragAndDropCallback callback) { - dragging = true; - // just capture something to prevent text selection in IE - Event.setCapture(RootPanel.getBodyElement()); - this.callback = callback; - dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler); - callback.onDragStart(startEvent); - callback.onDragUpdate(triggerEvent); + if (callback.onDragStart(startEvent)) { + dragging = true; + // just capture something to prevent text selection in IE + Event.setCapture(RootPanel.getBodyElement()); + this.callback = callback; + dragHandlerRegistration = Event + .addNativePreviewHandler(dragHandler); + callback.onDragUpdate(triggerEvent); + } } private void stopDrag() { diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index c237a7ba2f..26e7ba4650 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3001,19 +3001,38 @@ public class Grid<T> extends ResizeComposite implements } private void resolveDragElementHorizontalPosition(final int clientX) { - int left = clientX - table.getAbsoluteLeft(); - left = Math.max(0, Math.min(left, table.getClientWidth())); + double left = clientX - table.getAbsoluteLeft(); final double frozenColumnsWidth = getFrozenColumnsWidth(); if (left < frozenColumnsWidth) { left = (int) frozenColumnsWidth; } + // do not show the drag element beyond a spanned header cell + // limitation + final Double leftBound = possibleDropPositions.firstKey(); + final Double rightBound = possibleDropPositions.lastKey(); + double scrollLeft = getScrollLeft(); + if (left + scrollLeft < leftBound) { + left = leftBound - scrollLeft + autoScrollX; + } else if (left + scrollLeft > rightBound) { + left = rightBound - scrollLeft + autoScrollX; + } + + // do not show the drag element beyond the grid + left = Math.max(0, Math.min(left, table.getClientWidth())); + left -= dragElement.getClientWidth() / 2; dragElement.getStyle().setLeft(left, Unit.PX); } @Override - public void onDragStart(NativeEvent startingEvent) { + public boolean onDragStart(NativeEvent startingEvent) { + calculatePossibleDropPositions(); + + if (possibleDropPositions.isEmpty()) { + return false; + } + initHeaderDragElementDOM(); // needs to clone focus and sorting indicators too (UX) dragElement = DOM.clone(eventCell.getElement(), true); @@ -3026,12 +3045,11 @@ public class Grid<T> extends ResizeComposite implements // mark the floating cell, for styling & testing dragElement.addClassName("dragged-column-header"); - calculatePossibleDropPositions(); - // start the auto scroll handler autoScroller.setScrollAreaPX(60); autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, autoScrollerCallback); + return true; } @Override @@ -3147,55 +3165,82 @@ public class Grid<T> extends ResizeComposite implements } } + @SuppressWarnings("boxing") private void calculatePossibleDropPositions() { possibleDropPositions.clear(); - if (!calculatePossibleDropPositionInsideSpannedHeader()) { - HashMap<Integer, Double> columnIndexToDropPositionMap = new HashMap<Integer, Double>(); + final int draggedColumnIndex = eventCell.getColumnIndex(); + HashSet<Integer> unavailableColumnDropIndices = new HashSet<Integer>(); - final int frozenColumns = getSelectionAndFrozenColumnCount(); - double position = getFrozenColumnsWidth(); - // add all columns except frozen columns - for (int i = frozenColumns; i < getColumnCount(); i++) { - columnIndexToDropPositionMap.put(i, position); - position += getColumn(i).getWidthActual(); - } - // add the right side of the last column as columns.size() - columnIndexToDropPositionMap.put(getColumnCount(), position); + final int frozenColumns = getSelectionAndFrozenColumnCount(); + /* + * If the dragged cell is adjacent to a spanned cell in any other + * header or footer row, then the drag is limited inside that + * spanned cell. The same rules apply: the cell can't be dropped + * inside another spanned cell. The left and right bounds keep track + * of the edges of the most limiting spanned cell. + */ + int leftBound = -1; + int rightBound = getColumnCount() + 1; - // can't drop inside a spanned header from outside it - // -> remove all column indexes that are inside a spanned cell - // in any header row + for (int r = 0; r < getHeaderRowCount(); r++) { + HeaderRow headerRow = getHeaderRow(r); + if (!headerRow.hasSpannedCells()) { + continue; + } for (int c = frozenColumns; c < getColumnCount(); c++) { - for (int r = 0; r < getHeaderRowCount(); r++) { - HeaderRow headerRow = getHeaderRow(r); - if (headerRow.hasSpannedCells()) { - HeaderCell cell = headerRow.getCell(getColumn(c)); - assert cell != null : "Somehow got a null cell for row:cell " - + r + ":" + c; - int colspan = cell.getColspan(); - while (colspan > 1) { - c++; - colspan--; - columnIndexToDropPositionMap.remove(Integer - .valueOf(c)); - } + HeaderCell cell = headerRow.getCell(getColumn(c)); + assert cell != null : "Somehow got a null cell for row:cell " + + r + ":" + c; + int colspan = cell.getColspan(); + if (colspan <= 1) { + continue; + } + final int spannedCellRightEdgeIndex = c + colspan; + if (c <= draggedColumnIndex + && spannedCellRightEdgeIndex > draggedColumnIndex) { + // the spanned cell overlaps the dragged cell + if (c <= draggedColumnIndex && c > leftBound) { + leftBound = c; + } + if (spannedCellRightEdgeIndex < rightBound) { + rightBound = spannedCellRightEdgeIndex; + } + c = spannedCellRightEdgeIndex - 1; + } + + else { // can't drop inside a spanned cell + while (colspan > 1) { + c++; + colspan--; + unavailableColumnDropIndices.add(c); } } - } - // finally lets flip the map, because while dragging we want the - // column index matching the X-coordinate - for (Entry<Integer, Double> entry : columnIndexToDropPositionMap - .entrySet()) { - possibleDropPositions.put(entry.getValue(), entry.getKey()); } } - } - private boolean calculatePossibleDropPositionInsideSpannedHeader() { - // TODO if the dragged column is inside a spanned header on any row, - // then dragging is limited to inside that spanned cell - return false; + if (leftBound == (rightBound - 1)) { + return; + } + + double position = getFrozenColumnsWidth(); + // iterate column indices and add possible drop positions + for (int i = frozenColumns; i < getColumnCount(); i++) { + if (!unavailableColumnDropIndices.contains(i)) { + if (leftBound != -1) { + if (i >= leftBound && i <= rightBound) { + possibleDropPositions.put(position, i); + } + } else { + possibleDropPositions.put(position, i); + } + } + position += getColumn(i).getWidthActual(); + } + if (leftBound == -1) { + // add the right side of the last column as columns.size() + possibleDropPositions.put(position, getColumnCount()); + } } }; @@ -5494,12 +5539,7 @@ public class Grid<T> extends ResizeComposite implements if (eventCell.getElement().getColSpan() > 1) { return false; } - // for now prevent dragging of columns belonging to a spanned cell - for (int r = 0; r < getHeaderRowCount(); r++) { - if (getHeaderRow(r).getCell(eventCell.getColumn()).getColspan() > 1) { - return false; - } - } + if (event.getTypeInt() == Event.ONMOUSEDOWN && event.getButton() == NativeEvent.BUTTON_LEFT || event.getTypeInt() == Event.ONTOUCHSTART) { diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java index 13f32fd2a2..0e4f0272dd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java @@ -78,6 +78,11 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { getGridVerticalScrollbar()); } + protected void scrollGridHorizontallyTo(double px) { + executeScript("arguments[0].scrollLeft = " + px, + getGridHorizontalScrollbar()); + } + protected int getGridVerticalScrollPos() { return ((Number) executeScript("return arguments[0].scrollTop", getGridVerticalScrollbar())).intValue(); @@ -126,6 +131,12 @@ public abstract class GridBasicFeaturesTest extends MultiBrowserTest { By.xpath("//div[contains(@class, \"v-grid-scroller-vertical\")]")); } + protected WebElement getGridHorizontalScrollbar() { + return getDriver() + .findElement( + By.xpath("//div[contains(@class, \"v-grid-scroller-horizontal\")]")); + } + /** * Reloads the page without restartApplication. This occasionally breaks * stuff. diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java index 6e5eda43f1..39c6082bed 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java @@ -235,6 +235,124 @@ public class GridColumnReorderTest extends GridBasicClientFeaturesTest { assertColumnHeaderOrder(1, 2, 0, 3, 4); } + @Test + public void testColumnReorder_cellInsideASpannedHeader_cantBeDroppedOutsideSpannedArea() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 1, 2"); + assertColumnHeaderOrder(0, 1, 2, 3, 4); + + // when + dragAndDropColumnHeader(0, 2, 0, 20); + + // then + assertColumnHeaderOrder(0, 2, 1, 3, 4); + } + + @Test + public void testColumnReorder_cellInsideTwoCrossingSpanningHeaders_cantTouchThis() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join column cells 0, 1"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + dragAndDropColumnHeader(0, 3, 0, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + // when + dragAndDropColumnHeader(0, 2, 0, 10); + + // then + assertColumnHeaderOrder(3, 0, 1, 2, 4); + } + + @Test + public void testColumnReorder_cellsInsideSpannedHeaderAndBlockedByOtherSpannedCells_cantTouchThose() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join column cells 0, 1"); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + dragAndDropColumnHeader(0, 3, 0, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + // when then + dragAndDropColumnHeader(0, 1, 3, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + dragAndDropColumnHeader(1, 2, 1, 10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + + dragAndDropColumnHeader(2, 1, 3, -10); + assertColumnHeaderOrder(3, 0, 1, 2, 4); + } + + @Test + public void testColumnReorder_cellsInsideSpannedHeaderAndBlockedByOtherSpannedCells_reorderingLimited() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(0, 0, 4, 100); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + // when then + dragAndDropColumnHeader(0, 1, 4, 10); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(0, 2, 4, 10); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(0, 3, 4, 80); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 5, 4); + + dragAndDropColumnHeader(0, 4, 2, 100); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + + dragAndDropColumnHeader(2, 3, 4, 80); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 5, 4); + + dragAndDropColumnHeader(2, 4, 2, 100); + scrollGridHorizontallyTo(0); + assertColumnHeaderOrder(1, 2, 3, 4, 5); + } + + @Test + public void testColumnReorder_cellsInsideTwoAdjacentSpannedHeaders_reorderingLimited() { + // given + toggleColumnReorder(); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Append row"); + selectMenuPath("Component", "Header", "Row 2", "Join columns 3, 4, 5"); + dragAndDropColumnHeader(0, 0, 4, 100); + scrollGridHorizontallyTo(0); + dragAndDropColumnHeader(0, 1, 4, 80); + scrollGridHorizontallyTo(0); + selectMenuPath("Component", "Header", "Row 3", "Join columns 1, 2"); + assertColumnHeaderOrder(1, 3, 4, 5, 2); + + // when then + dragAndDropColumnHeader(0, 1, 3, 80); + assertColumnHeaderOrder(1, 4, 3, 5, 2); + + dragAndDropColumnHeader(0, 2, 4, 10); + assertColumnHeaderOrder(1, 4, 3, 5, 2); + + dragAndDropColumnHeader(0, 2, 0, 10); + assertColumnHeaderOrder(1, 3, 4, 5, 2); + } + private void toggleColumnReorder() { selectMenuPath("Component", "State", "Column Reordering"); } |