/*
* Copyright 2000-2016 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.connectors.grid;
import java.util.Objects;
import java.util.Optional;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.user.client.Window;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.extensions.DropTargetExtensionConnector;
import com.vaadin.client.widget.escalator.RowContainer;
import com.vaadin.client.widgets.Escalator;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.grid.DropLocation;
import com.vaadin.shared.ui.grid.DropMode;
import com.vaadin.shared.ui.grid.GridDropTargetRpc;
import com.vaadin.shared.ui.grid.GridDropTargetState;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.ui.components.grid.GridDropTarget;
import elemental.events.Event;
import elemental.json.JsonObject;
/**
* Makes Grid an HTML5 drop target. This is the client side counterpart of
* {@link GridDropTarget}.
*
* @author Vaadin Ltd
* @since 8.1
*/
@Connect(GridDropTarget.class)
public class GridDropTargetConnector extends DropTargetExtensionConnector {
/**
* Current style name
*/
private String currentStyleName;
private GridConnector gridConnector;
/**
* Class name to apply when an element is dragged over a row and the
* location is {@link DropLocation#ON_TOP}.
*/
private String styleDragCenter;
/**
* Class name to apply when an element is dragged over a row and the
* location is {@link DropLocation#ABOVE}.
*/
private String styleDragTop;
/**
* Class name to apply when an element is dragged over a row and the
* location is {@link DropLocation#BELOW}.
*/
private String styleDragBottom;
@Override
protected void extend(ServerConnector target) {
gridConnector = (GridConnector) target;
super.extend(target);
}
@Override
protected void sendDropEventToServer(String dataTransferText,
String dropEffect, NativeEvent dropEvent) {
String rowKey = null;
DropLocation dropLocation = null;
Optional<TableRowElement> targetRow = getTargetRow(
(Element) dropEvent.getEventTarget().cast());
if (targetRow.isPresent()) {
rowKey = getRowData(targetRow.get())
.getString(GridState.JSONKEY_ROWKEY);
dropLocation = getDropLocation(targetRow.get(), dropEvent);
}
getRpcProxy(GridDropTargetRpc.class).drop(dataTransferText, dropEffect,
rowKey, dropLocation);
}
private JsonObject getRowData(TableRowElement row) {
int rowIndex = ((Escalator.AbstractRowContainer) getGridBody())
.getLogicalRowIndex(row);
return gridConnector.getDataSource().getRow(rowIndex);
}
/**
* Returns the location of the event within the row.
*/
private DropLocation getDropLocation(Element target, NativeEvent event) {
if (getState().dropMode == DropMode.BETWEEN) {
if (getRelativeY(target, event) < (target.getOffsetHeight() / 2)) {
return DropLocation.ABOVE;
} else {
return DropLocation.BELOW;
}
} else if (getState().dropMode == DropMode.ON_TOP_OR_BETWEEN) {
if (getRelativeY(target, event) < getState().dropThreshold) {
return DropLocation.ABOVE;
} else if (target.getOffsetHeight()
- getRelativeY(target, event) < getState().dropThreshold) {
return DropLocation.BELOW;
} else {
return DropLocation.ON_TOP;
}
}
return DropLocation.ON_TOP;
}
private int getRelativeY(Element element, NativeEvent event) {
int relativeTop = element.getAbsoluteTop() - Window.getScrollTop();
return WidgetUtil.getTouchOrMouseClientY(event) - relativeTop;
}
@Override
protected void onDragEnter(Event event) {
// Generate style names for the drop target
String styleRow = gridConnector.getWidget().getStylePrimaryName()
+ "-row";
styleDragCenter = styleRow + STYLE_SUFFIX_DRAG_CENTER;
styleDragTop = styleRow + STYLE_SUFFIX_DRAG_TOP;
styleDragBottom = styleRow + STYLE_SUFFIX_DRAG_BOTTOM;
super.onDragEnter(event);
}
@Override
protected void addTargetClassIndicator(NativeEvent event) {
getTargetRow(((Element) event.getEventTarget().cast()))
.ifPresent(target -> {
// Get required class name
String className = getTargetClassName(target, event);
// Add or replace class name if changed
if (!target.hasClassName(className)) {
if (currentStyleName != null) {
target.removeClassName(currentStyleName);
}
target.addClassName(className);
currentStyleName = className;
}
});
}
private String getTargetClassName(Element target, NativeEvent event) {
String className;
switch (getDropLocation(target, event)) {
case ABOVE:
className = styleDragTop;
break;
case BELOW:
className = styleDragBottom;
break;
case ON_TOP:
default:
className = styleDragCenter;
break;
}
return className;
}
@Override
protected void removeTargetClassIndicator(NativeEvent event) {
// Remove all possible style names
getTargetRow((Element) event.getEventTarget().cast()).ifPresent(e -> {
e.removeClassName(styleDragCenter);
e.removeClassName(styleDragTop);
e.removeClassName(styleDragBottom);
});
}
private Optional<TableRowElement> getTargetRow(Element source) {
while (!Objects.equals(source, getGridBody().getElement())) {
if (TableRowElement.is(source)) {
return Optional.of(source.cast());
}
source = source.getParentElement();
}
return Optional.empty();
}
@Override
protected Element getDropTargetElement() {
return getGridBody().getElement();
}
private Escalator getEscalator() {
return gridConnector.getWidget().getEscalator();
}
private RowContainer.BodyRowContainer getGridBody() {
return getEscalator().getBody();
}
@Override
public GridDropTargetState getState() {
return (GridDropTargetState) super.getState();
}
}