Skip to content

Commit

Permalink
Limit dnd-reorder of columns inside spanned headers. (#16643)
Browse files Browse the repository at this point in the history
Doesn't take footers into account, yet.

Change-Id: I9c62ca97bcbdb852f2fe7ad7ea2e7de0f0ed02f8
  • Loading branch information
pleku committed Mar 4, 2015
1 parent 4091622 commit de2172e
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 58 deletions.
23 changes: 14 additions & 9 deletions client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
Expand Up @@ -46,12 +46,15 @@ public class DragAndDropHandler {
*/ */
public interface DragAndDropCallback { 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 * @param startEvent
* the original event that started the drag * 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. * Called on drag.
Expand Down Expand Up @@ -204,13 +207,15 @@ public void onPreviewNativeEvent(NativePreviewEvent event) {


private void startDrag(NativeEvent startEvent, private void startDrag(NativeEvent startEvent,
NativePreviewEvent triggerEvent, DragAndDropCallback callback) { NativePreviewEvent triggerEvent, DragAndDropCallback callback) {
dragging = true; if (callback.onDragStart(startEvent)) {
// just capture something to prevent text selection in IE dragging = true;
Event.setCapture(RootPanel.getBodyElement()); // just capture something to prevent text selection in IE
this.callback = callback; Event.setCapture(RootPanel.getBodyElement());
dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler); this.callback = callback;
callback.onDragStart(startEvent); dragHandlerRegistration = Event
callback.onDragUpdate(triggerEvent); .addNativePreviewHandler(dragHandler);
callback.onDragUpdate(triggerEvent);
}
} }


private void stopDrag() { private void stopDrag() {
Expand Down
138 changes: 89 additions & 49 deletions client/src/com/vaadin/client/widgets/Grid.java
Expand Up @@ -3001,19 +3001,38 @@ private void updateDragDropMarker(final int clientX) {
} }


private void resolveDragElementHorizontalPosition(final int clientX) { private void resolveDragElementHorizontalPosition(final int clientX) {
int left = clientX - table.getAbsoluteLeft(); double left = clientX - table.getAbsoluteLeft();
left = Math.max(0, Math.min(left, table.getClientWidth()));
final double frozenColumnsWidth = getFrozenColumnsWidth(); final double frozenColumnsWidth = getFrozenColumnsWidth();
if (left < frozenColumnsWidth) { if (left < frozenColumnsWidth) {
left = (int) 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; left -= dragElement.getClientWidth() / 2;
dragElement.getStyle().setLeft(left, Unit.PX); dragElement.getStyle().setLeft(left, Unit.PX);
} }


@Override @Override
public void onDragStart(NativeEvent startingEvent) { public boolean onDragStart(NativeEvent startingEvent) {
calculatePossibleDropPositions();

if (possibleDropPositions.isEmpty()) {
return false;
}

initHeaderDragElementDOM(); initHeaderDragElementDOM();
// needs to clone focus and sorting indicators too (UX) // needs to clone focus and sorting indicators too (UX)
dragElement = DOM.clone(eventCell.getElement(), true); dragElement = DOM.clone(eventCell.getElement(), true);
Expand All @@ -3026,12 +3045,11 @@ public void onDragStart(NativeEvent startingEvent) {
// mark the floating cell, for styling & testing // mark the floating cell, for styling & testing
dragElement.addClassName("dragged-column-header"); dragElement.addClassName("dragged-column-header");


calculatePossibleDropPositions();

// start the auto scroll handler // start the auto scroll handler
autoScroller.setScrollAreaPX(60); autoScroller.setScrollAreaPX(60);
autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL,
autoScrollerCallback); autoScrollerCallback);
return true;
} }


@Override @Override
Expand Down Expand Up @@ -3147,55 +3165,82 @@ private int getSelectionAndFrozenColumnCount() {
} }
} }


@SuppressWarnings("boxing")
private void calculatePossibleDropPositions() { private void calculatePossibleDropPositions() {
possibleDropPositions.clear(); possibleDropPositions.clear();


if (!calculatePossibleDropPositionInsideSpannedHeader()) { final int draggedColumnIndex = eventCell.getColumnIndex();
HashMap<Integer, Double> columnIndexToDropPositionMap = new HashMap<Integer, Double>(); HashSet<Integer> unavailableColumnDropIndices = new HashSet<Integer>();


final int frozenColumns = getSelectionAndFrozenColumnCount(); final int frozenColumns = getSelectionAndFrozenColumnCount();
double position = getFrozenColumnsWidth(); /*
// add all columns except frozen columns * If the dragged cell is adjacent to a spanned cell in any other
for (int i = frozenColumns; i < getColumnCount(); i++) { * header or footer row, then the drag is limited inside that
columnIndexToDropPositionMap.put(i, position); * spanned cell. The same rules apply: the cell can't be dropped
position += getColumn(i).getWidthActual(); * inside another spanned cell. The left and right bounds keep track
} * of the edges of the most limiting spanned cell.
// add the right side of the last column as columns.size() */
columnIndexToDropPositionMap.put(getColumnCount(), position); int leftBound = -1;
int rightBound = getColumnCount() + 1;


// can't drop inside a spanned header from outside it for (int r = 0; r < getHeaderRowCount(); r++) {
// -> remove all column indexes that are inside a spanned cell HeaderRow headerRow = getHeaderRow(r);
// in any header row if (!headerRow.hasSpannedCells()) {
continue;
}
for (int c = frozenColumns; c < getColumnCount(); c++) { for (int c = frozenColumns; c < getColumnCount(); c++) {
for (int r = 0; r < getHeaderRowCount(); r++) { HeaderCell cell = headerRow.getCell(getColumn(c));
HeaderRow headerRow = getHeaderRow(r); assert cell != null : "Somehow got a null cell for row:cell "
if (headerRow.hasSpannedCells()) { + r + ":" + c;
HeaderCell cell = headerRow.getCell(getColumn(c)); int colspan = cell.getColspan();
assert cell != null : "Somehow got a null cell for row:cell " if (colspan <= 1) {
+ r + ":" + c; continue;
int colspan = cell.getColspan(); }
while (colspan > 1) { final int spannedCellRightEdgeIndex = c + colspan;
c++; if (c <= draggedColumnIndex
colspan--; && spannedCellRightEdgeIndex > draggedColumnIndex) {
columnIndexToDropPositionMap.remove(Integer // the spanned cell overlaps the dragged cell
.valueOf(c)); 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() { if (leftBound == (rightBound - 1)) {
// TODO if the dragged column is inside a spanned header on any row, return;
// then dragging is limited to inside that spanned cell }
return false;
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());
}
} }
}; };


Expand Down Expand Up @@ -5494,12 +5539,7 @@ private boolean handleHeaderCellDragStartEvent(Event event,
if (eventCell.getElement().getColSpan() > 1) { if (eventCell.getElement().getColSpan() > 1) {
return false; 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 if (event.getTypeInt() == Event.ONMOUSEDOWN
&& event.getButton() == NativeEvent.BUTTON_LEFT && event.getButton() == NativeEvent.BUTTON_LEFT
|| event.getTypeInt() == Event.ONTOUCHSTART) { || event.getTypeInt() == Event.ONTOUCHSTART) {
Expand Down
Expand Up @@ -78,6 +78,11 @@ protected void scrollGridVerticallyTo(double px) {
getGridVerticalScrollbar()); getGridVerticalScrollbar());
} }


protected void scrollGridHorizontallyTo(double px) {
executeScript("arguments[0].scrollLeft = " + px,
getGridHorizontalScrollbar());
}

protected int getGridVerticalScrollPos() { protected int getGridVerticalScrollPos() {
return ((Number) executeScript("return arguments[0].scrollTop", return ((Number) executeScript("return arguments[0].scrollTop",
getGridVerticalScrollbar())).intValue(); getGridVerticalScrollbar())).intValue();
Expand Down Expand Up @@ -126,6 +131,12 @@ protected WebElement getGridVerticalScrollbar() {
By.xpath("//div[contains(@class, \"v-grid-scroller-vertical\")]")); 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 * Reloads the page without restartApplication. This occasionally breaks
* stuff. * stuff.
Expand Down
Expand Up @@ -235,6 +235,124 @@ public void testColumnReorder_anotherRowHasColumnHeadersSpanned_cantDropInsideSp
assertColumnHeaderOrder(1, 2, 0, 3, 4); 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() { private void toggleColumnReorder() {
selectMenuPath("Component", "State", "Column Reordering"); selectMenuPath("Component", "State", "Column Reordering");
} }
Expand Down

0 comments on commit de2172e

Please sign in to comment.