summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPekka Hyvönen <pekka@vaadin.com>2015-03-03 11:15:06 +0200
committerPekka Hyvönen <pekka@vaadin.com>2015-03-04 09:24:00 +0200
commitde2172eda96ca3f34b2ab4e2e926b13957c113de (patch)
treeddd7e7cddfd47cf33b0837f8486097e92de89481
parent40916228681314c843b32e59fc91bbec18548332 (diff)
downloadvaadin-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
-rw-r--r--client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java23
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java138
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeaturesTest.java11
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnReorderTest.java118
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");
}