summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WebContent/VAADIN/themes/base/grid/grid.scss21
-rw-r--r--client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java240
-rw-r--r--client/src/com/vaadin/client/widgets/Grid.java209
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java9
4 files changed, 478 insertions, 1 deletions
diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss
index e4a4a1d920..d0bae911db 100644
--- a/WebContent/VAADIN/themes/base/grid/grid.scss
+++ b/WebContent/VAADIN/themes/base/grid/grid.scss
@@ -52,6 +52,27 @@ $v-grid-editor-background-color: $v-grid-row-background-color !default;
border: $v-grid-border;
}
+ .#{$primaryStyleName} .header-drag-table {
+ border-spacing: 0;
+ table-layout: fixed;
+ width: inherit; // a decent default fallback
+
+ .#{$primaryStyleName}-header {
+
+ > .#{$primaryStyleName}-cell {
+ border: $v-grid-border;
+ opacity: 0.9;
+ filter: alpha(opacity=90); // IE8
+ }
+
+ > .#{$primaryStyleName}-drop-marker {
+ background-color: #197de1;
+ position: absolute;
+ width: 3px;
+ }
+ }
+ }
+
// Common cell styles
.#{$primaryStyleName}-cell {
diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
new file mode 100644
index 0000000000..c8b651bd4e
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.dd;
+
+import java.util.logging.Logger;
+
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.vaadin.client.WidgetUtil;
+import com.vaadin.client.widgets.Grid;
+
+/**
+ * A simple event handler for elements that can be drag and dropped. Loosely
+ * based on {@link VDragAndDropManager}, but without any Vaadin related stuff.
+ * Properly handles drag start, cancel and end. For example, used in
+ * {@link Grid} column header reordering.
+ * <p>
+ * The showing of the dragged element, drag hints and reacting to drop/cancel is
+ * delegated to {@link DragAndDropCallback} implementation.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class DragAndDropHandler {
+
+ /**
+ * Callback interface for drag and drop.
+ */
+ public interface DragAndDropCallback {
+ /**
+ * Called when the drag has started.
+ */
+ void showDragElement();
+
+ /**
+ * Called on drag.
+ *
+ * @param event
+ * the event related to the drag
+ */
+ void updateDragElement(NativePreviewEvent event);
+
+ /**
+ * Called when the has ended on a drop or cancel.
+ */
+ void removeDragElement();
+
+ /**
+ * Called when the drag has ended on a drop.
+ */
+ void onDrop();
+
+ /**
+ * Called when the drag has been canceled before drop.
+ */
+ void onDragCancel();
+ }
+
+ private HandlerRegistration dragStartNativePreviewHandlerRegistration;
+ private HandlerRegistration dragHandlerRegistration;
+
+ private boolean dragging;
+
+ private DragAndDropCallback callback;
+
+ private final NativePreviewHandler dragHandler = new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (dragging) {
+ final int typeInt = event.getTypeInt();
+ switch (typeInt) {
+ case Event.ONKEYDOWN:
+ int keyCode = event.getNativeEvent().getKeyCode();
+ if (keyCode == KeyCodes.KEY_ESCAPE) {
+ // end drag if ESC is hit
+ cancelDrag(event);
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ callback.updateDragElement(event);
+ // prevent text selection on IE
+ event.getNativeEvent().preventDefault();
+ break;
+ case Event.ONTOUCHCANCEL:
+ cancelDrag(event);
+ break;
+ case Event.ONTOUCHEND:
+ /* Avoid simulated event on drag end */
+ event.getNativeEvent().preventDefault();
+ //$FALL-THROUGH$
+ case Event.ONMOUSEUP:
+ callback.updateDragElement(event);
+ callback.onDrop();
+ stopDrag();
+ event.cancel();
+ break;
+ default:
+ break;
+ }
+ } else {
+ stopDrag();
+ }
+ }
+
+ private void cancelDrag(NativePreviewEvent event) {
+ callback.onDragCancel();
+ callback.removeDragElement();
+ stopDrag();
+ event.cancel();
+ event.getNativeEvent().preventDefault();
+ }
+
+ };
+
+ private static Logger getLogger() {
+ return Logger.getLogger(DragAndDropHandler.class.getName());
+ }
+
+ /**
+ * This method can be called to trigger drag and drop on any grid element
+ * that can be dragged and dropped.
+ *
+ * @param dragStartingEvent
+ * the drag triggering event, usually a {@link Event#ONMOUSEDOWN}
+ * or {@link Event#ONTOUCHSTART} event on the draggable element
+ *
+ * @param callback
+ * the callback that will handle actual drag and drop related
+ * operations
+ */
+ public void onDragStartOnDraggableElement(
+ final NativeEvent dragStartingEvent,
+ final DragAndDropCallback callback) {
+ dragStartNativePreviewHandlerRegistration = Event
+ .addNativePreviewHandler(new NativePreviewHandler() {
+
+ private int startX = WidgetUtil
+ .getTouchOrMouseClientX(dragStartingEvent);
+ private int startY = WidgetUtil
+ .getTouchOrMouseClientY(dragStartingEvent);
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ final int typeInt = event.getTypeInt();
+ if (typeInt == -1
+ && event.getNativeEvent().getType()
+ .toLowerCase().contains("pointer")) {
+ /*
+ * Ignore PointerEvents since IE10 and IE11 send
+ * also MouseEvents for backwards compatibility.
+ */
+ return;
+ }
+ switch (typeInt) {
+ case Event.ONMOUSEOVER:
+ case Event.ONMOUSEOUT:
+ // we don't care
+ break;
+ case Event.ONKEYDOWN:
+ case Event.ONKEYPRESS:
+ case Event.ONKEYUP:
+ case Event.ONBLUR:
+ case Event.ONFOCUS:
+ // don't cancel possible drag start
+ break;
+ case Event.ONMOUSEMOVE:
+ case Event.ONTOUCHMOVE:
+ int currentX = WidgetUtil
+ .getTouchOrMouseClientX(event
+ .getNativeEvent());
+ int currentY = WidgetUtil
+ .getTouchOrMouseClientY(event
+ .getNativeEvent());
+ if (Math.abs(startX - currentX) > 3
+ || Math.abs(startY - currentY) > 3) {
+ removeNativePreviewHandlerRegistration();
+ startDrag(event, callback);
+ }
+ break;
+ default:
+ // on any other events, clean up this preview
+ // listener
+ removeNativePreviewHandlerRegistration();
+ break;
+ }
+ }
+ });
+ }
+
+ private void startDrag(NativePreviewEvent event,
+ 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.showDragElement();
+ callback.updateDragElement(event);
+ }
+
+ private void stopDrag() {
+ dragging = false;
+ if (dragHandlerRegistration != null) {
+ dragHandlerRegistration.removeHandler();
+ dragHandlerRegistration = null;
+ }
+ Event.releaseCapture(RootPanel.getBodyElement());
+ if (callback != null) {
+ callback.removeDragElement();
+ callback = null;
+ }
+ }
+
+ private void removeNativePreviewHandlerRegistration() {
+ if (dragStartNativePreviewHandlerRegistration != null) {
+ dragStartNativePreviewHandlerRegistration.removeHandler();
+ dragStartNativePreviewHandlerRegistration = null;
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java
index 71962d6953..b0e18cc2ab 100644
--- a/client/src/com/vaadin/client/widgets/Grid.java
+++ b/client/src/com/vaadin/client/widgets/Grid.java
@@ -36,6 +36,8 @@ import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
@@ -53,6 +55,8 @@ import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.touch.client.Point;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
@@ -68,6 +72,8 @@ import com.vaadin.client.renderers.ComplexRenderer;
import com.vaadin.client.renderers.Renderer;
import com.vaadin.client.renderers.WidgetRenderer;
import com.vaadin.client.ui.SubPartAware;
+import com.vaadin.client.ui.dd.DragAndDropHandler;
+import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback;
import com.vaadin.client.widget.escalator.Cell;
import com.vaadin.client.widget.escalator.ColumnConfiguration;
import com.vaadin.client.widget.escalator.EscalatorUpdater;
@@ -2853,6 +2859,153 @@ public class Grid<T> extends ResizeComposite implements
private boolean enabled = true;
+ private boolean columnReorderingAllowed;
+
+ private DragAndDropHandler dndHandler = new DragAndDropHandler();
+
+ private DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() {
+
+ /**
+ * Elements for displaying the dragged column(s) and drop marker
+ * properly
+ */
+ private Element table;
+ private Element tableHeader;
+ /** Marks the column drop location */
+ private Element dropMarker;
+ /** A copy of the dragged column(s), moves with cursor. */
+ private Element dragElement;
+ /** Tracks index of the column whose left side the drop would occur */
+ private int latestColumnDropIndex;
+ /**
+ * Makes sure that drag cancel doesn't cause anything unwanted like sort
+ */
+ private HandlerRegistration columnSortPreventRegistration;
+
+ private void initHeaderDragElementDOM() {
+ if (table == null) {
+ tableHeader = DOM.createTHead();
+ dropMarker = DOM.createDiv();
+ tableHeader.appendChild(dropMarker);
+ table = DOM.createTable();
+ table.appendChild(tableHeader);
+ table.setClassName("header-drag-table");
+ }
+ // update the style names on each run in case primary name has been
+ // modified
+ tableHeader.setClassName(escalator.getHeader().getElement()
+ .getClassName());
+ dropMarker.setClassName(getStylePrimaryName() + "-drop-marker");
+ getElement().appendChild(table);
+ }
+
+ @Override
+ public void updateDragElement(NativePreviewEvent event) {
+ int clientX = WidgetUtil.getTouchOrMouseClientX(event
+ .getNativeEvent());
+ resolveDragElementHorizontalPosition(clientX);
+ updateDragDropMarker(clientX);
+ }
+
+ private void updateDragDropMarker(final int clientX) {
+ RowContainer header = escalator.getHeader();
+ NodeList<TableCellElement> cells = header.getRowElement(
+ eventCell.getRowIndex()).getCells();
+ double dropMarkerLeft = 0 - escalator.getScrollLeft();
+ latestColumnDropIndex = 0;
+ for (int i = 0; i < cells.getLength(); i++, latestColumnDropIndex++) {
+ TableCellElement cellElement = cells.getItem(i);
+ int cellX = cellElement.getAbsoluteLeft();
+ int cellWidth = cellElement.getOffsetWidth();
+ if (clientX < cellX || clientX < cellX + (cellWidth / 2)) {
+ break;
+ } else {
+ dropMarkerLeft += cellWidth;
+ }
+ }
+ if (dropMarkerLeft > header.getElement().getOffsetWidth()
+ || dropMarkerLeft < 0) {
+ dropMarkerLeft = -10000000;
+ }
+ dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX);
+ }
+
+ private void resolveDragElementVerticalPosition() {
+ dragElement.getStyle().setTop(-10, Unit.PX);
+ }
+
+ private void resolveDragElementHorizontalPosition(final int clientX) {
+ int left = clientX - table.getAbsoluteLeft();
+ left = Math.max(0, Math.min(left, table.getClientWidth()));
+ left -= dragElement.getClientWidth() / 2;
+ dragElement.getStyle().setLeft(left, Unit.PX);
+ }
+
+ @Override
+ public void showDragElement() {
+ initHeaderDragElementDOM();
+ // TODO this clones also some unwanted style names, should confirm
+ // with UX what we want to show (focus/sort indicator)
+ dragElement = DOM.clone(eventCell.getElement(), true);
+ dragElement.getStyle().clearWidth();
+ dropMarker.getStyle().setProperty("height",
+ dragElement.getStyle().getHeight());
+ tableHeader.appendChild(dragElement);
+ // might need to change this on fly once sorting with multiple
+ // header rows is possible
+ resolveDragElementVerticalPosition();
+ }
+
+ @Override
+ public void removeDragElement() {
+ table.removeFromParent();
+ dragElement.removeFromParent();
+ }
+
+ @Override
+ public void onDrop() {
+ final int draggedColumnIndex = eventCell.getColumnIndex();
+ if (latestColumnDropIndex != draggedColumnIndex
+ && latestColumnDropIndex != (draggedColumnIndex + 1)) {
+ List<Column<?, T>> columns = getColumns();
+ List<Column<?, T>> reordered = new ArrayList<Column<?, T>>(
+ columns);
+ Column<?, T> moved = reordered.remove(draggedColumnIndex);
+ if (draggedColumnIndex < latestColumnDropIndex) {
+ latestColumnDropIndex--;
+ }
+ reordered.add(latestColumnDropIndex, moved);
+ @SuppressWarnings("unchecked")
+ Column<?, T>[] array = reordered.toArray(new Column[reordered
+ .size()]);
+ setColumnOrder(array);
+ } // else no reordering
+ }
+
+ @Override
+ public void onDragCancel() {
+ // cancel next click so that we may prevent column sorting if
+ // mouse was released on top of the dragged cell
+ if (columnSortPreventRegistration == null) {
+ columnSortPreventRegistration = Event
+ .addNativePreviewHandler(new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(
+ NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONCLICK) {
+ event.cancel();
+ event.getNativeEvent().preventDefault();
+ columnSortPreventRegistration
+ .removeHandler();
+ columnSortPreventRegistration = null;
+ }
+ }
+ });
+ }
+ }
+ };
+
/**
* Enumeration for easy setting of selection mode.
*/
@@ -4649,7 +4802,7 @@ public class Grid<T> extends ResizeComposite implements
+ getColumnCount() + ")");
}
- this.frozenColumnCount = numberOfColumns;
+ frozenColumnCount = numberOfColumns;
updateFrozenColumns();
}
@@ -4949,6 +5102,10 @@ public class Grid<T> extends ResizeComposite implements
if (!isElementInChildWidget(e)) {
+ if (handleHeaderCellDragStartEvent(event, container)) {
+ return;
+ }
+
// Sorting through header Click / KeyUp
if (handleHeaderDefaultRowEvent(event, container)) {
return;
@@ -5097,6 +5254,35 @@ public class Grid<T> extends ResizeComposite implements
return true;
}
+ private boolean handleHeaderCellDragStartEvent(Event event,
+ RowContainer container) {
+ if (!columnReorderingAllowed) {
+ return false;
+ }
+ if (container != escalator.getHeader()) {
+ return false;
+ }
+ // for now only support reordering of default row as the only row
+ if (!getHeader().getRow(eventCell.getRowIndex()).isDefault()
+ || getHeader().getRowCount() != 1) {
+ return false;
+ }
+ if (eventCell.getColumnIndex() < escalator.getColumnConfiguration()
+ .getFrozenColumnCount()) {
+ return false;
+ }
+ if (event.getTypeInt() == Event.ONMOUSEDOWN
+ && event.getButton() == NativeEvent.BUTTON_LEFT
+ || event.getTypeInt() == Event.ONTOUCHSTART) {
+ dndHandler.onDragStartOnDraggableElement(event,
+ headerCellDndCallback);
+ event.preventDefault();
+ event.stopPropagation();
+ return true;
+ }
+ return false;
+ }
+
private Point rowEventTouchStartingPoint;
private CellStyleGenerator<T> cellStyleGenerator;
private RowStyleGenerator<T> rowStyleGenerator;
@@ -5906,6 +6092,27 @@ public class Grid<T> extends ResizeComposite implements
}
/**
+ * Returns whether columns can be reordered with drag and drop.
+ *
+ * @since
+ * @return <code>true</code> if columns can be reordered, false otherwise
+ */
+ public boolean isColumnReorderingAllowed() {
+ return columnReorderingAllowed;
+ }
+
+ /**
+ * Sets whether column reordering with drag and drop is allowed or not.
+ *
+ * @since
+ * @param columnReorderingAllowed
+ * specifies whether column reordering is allowed
+ */
+ public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
+ this.columnReorderingAllowed = columnReorderingAllowed;
+ }
+
+ /**
* Sets a new column order for the grid. All columns which are not ordered
* here will remain in the order they were before as the last columns of
* grid.
diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java
index 0452aa65d1..bfe8b3cf4d 100644
--- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java
+++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridBasicClientFeaturesWidget.java
@@ -649,6 +649,15 @@ public class GridBasicClientFeaturesWidget extends
grid.setEnabled(!grid.isEnabled());
}
}, "Component", "State");
+
+ addMenuCommand("Column Reordering", new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ grid.setColumnReorderingAllowed(!grid
+ .isColumnReorderingAllowed());
+ }
+ }, "Component", "State");
}
private void createColumnsMenu() {