All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.client.ui.VDragAndDropWrapper Maven / Gradle / Ivy

Go to download

Vaadin is a web application framework for Rich Internet Applications (RIA). Vaadin enables easy development and maintenance of fast and secure rich web applications with a stunning look and feel and a wide browser support. It features a server-side architecture with the majority of the logic running on the server. Ajax technology is used at the browser-side to ensure a rich and interactive user experience.

There is a newer version: 8.27.1
Show newest version
/*
 * 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.ui;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xhr.client.ReadyStateChangeHandler;
import com.google.gwt.xhr.client.XMLHttpRequest;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.Util;
import com.vaadin.client.VConsole;
import com.vaadin.client.ValueMap;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.dd.DDUtil;
import com.vaadin.client.ui.dd.VAbstractDropHandler;
import com.vaadin.client.ui.dd.VAcceptCallback;
import com.vaadin.client.ui.dd.VDragAndDropManager;
import com.vaadin.client.ui.dd.VDragEvent;
import com.vaadin.client.ui.dd.VDropHandler;
import com.vaadin.client.ui.dd.VHasDropHandler;
import com.vaadin.client.ui.dd.VHtml5DragEvent;
import com.vaadin.client.ui.dd.VHtml5File;
import com.vaadin.client.ui.dd.VTransferable;
import com.vaadin.shared.ui.dd.HorizontalDropLocation;
import com.vaadin.shared.ui.dd.VerticalDropLocation;

/**
 *
 * Must have features pending:
 *
 * drop details: locations + sizes in document hierarchy up to wrapper
 *
 */
public class VDragAndDropWrapper extends VCustomComponent
        implements VHasDropHandler {

    /**
     * Minimum pixel delta is used to detect click from drag. #12838
     */
    private static final int MIN_PX_DELTA = 4;
    private static final String CLASSNAME = "v-ddwrapper";
    protected static final String DRAGGABLE = "draggable";

    /** For internal use only. May be removed or replaced in the future. */
    public boolean hasTooltip = false;
    private int startX = 0;
    private int startY = 0;

    public VDragAndDropWrapper() {
        super();
        hookHtml5Events(getElement());
        setStyleName(CLASSNAME);

        addDomHandler(new MouseDownHandler() {

            @Override
            public void onMouseDown(final MouseDownEvent event) {
                if (getConnector().isEnabled()
                        && event.getNativeEvent()
                                .getButton() == Event.BUTTON_LEFT
                        && startDrag(event.getNativeEvent())) {
                    event.preventDefault(); // prevent text selection
                    startX = event.getClientX();
                    startY = event.getClientY();
                }
            }
        }, MouseDownEvent.getType());

        addDomHandler(new MouseUpHandler() {

            @Override
            public void onMouseUp(final MouseUpEvent event) {
                final int deltaX = Math.abs(event.getClientX() - startX);
                final int deltaY = Math.abs(event.getClientY() - startY);
                if ((deltaX + deltaY) < MIN_PX_DELTA) {
                    Element clickedElement = WidgetUtil.getElementFromPoint(
                            event.getClientX(), event.getClientY());
                    clickedElement.focus();
                }
            }

        }, MouseUpEvent.getType());

        addDomHandler(new TouchStartHandler() {

            @Override
            public void onTouchStart(TouchStartEvent event) {
                if (getConnector().isEnabled()
                        && startDrag(event.getNativeEvent())) {
                    /*
                     * Dont let eg. panel start scrolling.
                     */
                    event.stopPropagation();
                }
            }
        }, TouchStartEvent.getType());

        sinkEvents(Event.TOUCHEVENTS);
    }

    /**
     * Starts a drag and drop operation from mousedown or touchstart event if
     * required conditions are met.
     *
     * @param event
     * @return true if the event was handled as a drag start event
     */
    private boolean startDrag(NativeEvent event) {
        if (dragStartMode == WRAPPER || dragStartMode == COMPONENT
                || dragStartMode == COMPONENT_OTHER) {
            VTransferable transferable = new VTransferable();
            transferable.setDragSource(getConnector());

            ComponentConnector paintable = Util.findPaintable(client,
                    Element.as(event.getEventTarget()));
            Widget widget = paintable.getWidget();
            transferable.setData("component", paintable);
            VDragEvent dragEvent = VDragAndDropManager.get()
                    .startDrag(transferable, event, true);

            transferable.setData("mouseDown", MouseEventDetailsBuilder
                    .buildMouseEventDetails(event).serialize());

            if (dragStartMode == WRAPPER) {
                dragEvent.createDragImage(getElement(), true);
            } else if (dragStartMode == COMPONENT_OTHER
                    && getDragImageWidget() != null) {
                dragEvent.createDragImage(getDragImageWidget().getElement(),
                        true);
            } else {
                dragEvent.createDragImage(widget.getElement(), true);
            }
            return true;
        }
        return false;
    }

    protected final static int NONE = 0;
    protected final static int COMPONENT = 1;
    protected final static int WRAPPER = 2;
    protected final static int HTML5 = 3;
    protected final static int COMPONENT_OTHER = 4;

    /** For internal use only. May be removed or replaced in the future. */
    public int dragStartMode;

    /** For internal use only. May be removed or replaced in the future. */
    public ApplicationConnection client;

    /** For internal use only. May be removed or replaced in the future. */
    public VAbstractDropHandler dropHandler;

    /** For internal use only. May be removed or replaced in the future. */
    public UploadHandler uploadHandler;

    private VDragEvent vaadinDragEvent;

    int filecounter = 0;

    /** For internal use only. May be removed or replaced in the future. */
    public Map fileIdToReceiver;

    /** For internal use only. May be removed or replaced in the future. */
    public ValueMap html5DataFlavors;

    private Element dragStartElement;

    /** For internal use only. May be removed or replaced in the future. */
    public void initDragStartMode() {
        Element div = getElement();
        if (dragStartMode == HTML5) {
            if (dragStartElement == null) {
                dragStartElement = getDragStartElement();
                dragStartElement.setPropertyBoolean(DRAGGABLE, true);
                VConsole.log("draggable = "
                        + dragStartElement.getPropertyBoolean(DRAGGABLE));
                hookHtml5DragStart(dragStartElement);
                VConsole.log("drag start listeners hooked.");
            }
        } else {
            dragStartElement = null;
            if (div.hasAttribute(DRAGGABLE)) {
                div.removeAttribute(DRAGGABLE);
            }
        }
    }

    protected com.google.gwt.user.client.Element getDragStartElement() {
        return getElement();
    }

    private boolean uploading;

    private final ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {

        @Override
        public void onReadyStateChange(XMLHttpRequest xhr) {
            if (xhr.getReadyState() == XMLHttpRequest.DONE) {
                // #19616 Notify the upload handler that the request is complete
                // and let it poll the server for changes.
                uploadHandler.uploadDone();
                uploading = false;
                startNextUpload();
                xhr.clearOnReadyStateChange();
            }
        }
    };
    private Timer dragleavetimer;

    /** For internal use only. May be removed or replaced in the future. */
    public void startNextUpload() {
        Scheduler.get().scheduleDeferred(new Command() {

            @Override
            public void execute() {
                if (!uploading) {
                    if (fileIds.size() > 0) {

                        uploading = true;
                        final Integer fileId = fileIds.remove(0);
                        VHtml5File file = files.remove(0);
                        final String receiverUrl = client.translateVaadinUri(
                                fileIdToReceiver.remove(fileId.toString()));
                        ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
                                .create();
                        extendedXHR
                                .setOnReadyStateChange(readyStateChangeHandler);
                        extendedXHR.open("POST", receiverUrl);
                        extendedXHR.postFile(file);
                    }
                }

            }
        });

    }

    public boolean html5DragStart(VHtml5DragEvent event) {
        if (dragStartMode == HTML5) {
            /*
             * Populate html5 payload with dataflavors from the serverside
             */
            JsArrayString flavors = html5DataFlavors.getKeyArray();
            for (int i = 0; i < flavors.length(); i++) {
                String flavor = flavors.get(i);
                event.setHtml5DataFlavor(flavor,
                        html5DataFlavors.getString(flavor));
            }
            event.setEffectAllowed("copy");
            return true;
        }
        return false;
    }

    public boolean html5DragEnter(VHtml5DragEvent event) {
        if (dropHandler == null) {
            return true;
        }
        try {
            if (dragleavetimer != null) {
                // returned quickly back to wrapper
                dragleavetimer.cancel();
                dragleavetimer = null;
            }
            if (VDragAndDropManager.get()
                    .getCurrentDropHandler() != getDropHandler()) {
                VTransferable transferable = new VTransferable();
                transferable.setDragSource(getConnector());

                vaadinDragEvent = VDragAndDropManager.get()
                        .startDrag(transferable, event, false);
                VDragAndDropManager.get()
                        .setCurrentDropHandler(getDropHandler());
            }
            try {
                event.preventDefault();
                event.stopPropagation();
            } catch (Exception e) {
                // VConsole.log("IE9 fails");
            }
            return false;
        } catch (Exception e) {
            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
            return true;
        }
    }

    public boolean html5DragLeave(VHtml5DragEvent event) {
        if (dropHandler == null) {
            return true;
        }

        try {
            dragleavetimer = new Timer() {

                @Override
                public void run() {
                    // Yes, dragleave happens before drop. Makes no sense to me.
                    // IMO shouldn't fire leave at all if drop happens (I guess
                    // this
                    // is what IE does).
                    // In Vaadin we fire it only if drop did not happen.
                    if (vaadinDragEvent != null && VDragAndDropManager.get()
                            .getCurrentDropHandler() == getDropHandler()) {
                        VDragAndDropManager.get().interruptDrag();
                    }
                }
            };
            dragleavetimer.schedule(350);
            try {
                event.preventDefault();
                event.stopPropagation();
            } catch (Exception e) {
                // VConsole.log("IE9 fails");
            }
            return false;
        } catch (Exception e) {
            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
            return true;
        }
    }

    public boolean html5DragOver(VHtml5DragEvent event) {
        if (dropHandler == null) {
            return true;
        }

        if (dragleavetimer != null) {
            // returned quickly back to wrapper
            dragleavetimer.cancel();
            dragleavetimer = null;
        }

        vaadinDragEvent.setCurrentGwtEvent(event);
        getDropHandler().dragOver(vaadinDragEvent);

        try {
            String s = event.getEffectAllowed();
            if ("all".equals(s) || s.contains("opy")) {
                event.setDropEffect("copy");
            } else {
                event.setDropEffect(s);
            }
        } catch (Exception e) {
            // IE10 throws exception here in getEffectAllowed, ignore it, let
            // drop effect be whatever it is
        }

        try {
            event.preventDefault();
            event.stopPropagation();
        } catch (Exception e) {
            // VConsole.log("IE9 fails");
        }
        return false;
    }

    public boolean html5DragDrop(VHtml5DragEvent event) {
        if (dropHandler == null || !currentlyValid) {
            VDragAndDropManager.get().interruptDrag();
            return true;
        }
        try {

            VTransferable transferable = vaadinDragEvent.getTransferable();

            JsArrayString types = event.getTypes();
            for (int i = 0; i < types.length(); i++) {
                String type = types.get(i);
                if (isAcceptedType(type)) {
                    String data = event.getDataAsText(type);
                    if (data != null) {
                        transferable.setData(type, data);
                    }
                }
            }

            final int eventFileCount = event.getFileCount();
            int fileIndex = 0;
            for (int i = 0; i < eventFileCount; i++) {
                // Transfer only files and not folders
                if (event.isFile(i)) {
                    final int fileId = filecounter++;
                    final VHtml5File file = event.getFile(i);
                    VConsole.log("Preparing to upload file " + file.getName()
                            + " with id " + fileId + ", size="
                            + file.getSize());
                    transferable.setData("fi" + fileIndex, "" + fileId);
                    transferable.setData("fn" + fileIndex, file.getName());
                    transferable.setData("ft" + fileIndex, file.getType());
                    transferable.setData("fs" + fileIndex, file.getSize());
                    queueFilePost(fileId, file);
                    fileIndex++;
                }
            }
            if (fileIndex > 0) {
                transferable.setData("filecount", fileIndex);
            }

            VDragAndDropManager.get().endDrag();
            vaadinDragEvent = null;
            try {
                event.preventDefault();
                event.stopPropagation();
            } catch (Exception e) {
                // VConsole.log("IE9 fails");
            }
            return false;
        } catch (Exception e) {
            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
            return true;
        }

    }

    protected String[] acceptedTypes = new String[] { "Text", "Url",
            "text/html", "text/plain", "text/rtf" };

    private boolean isAcceptedType(String type) {
        for (String t : acceptedTypes) {
            if (t.equals(type)) {
                return true;
            }
        }
        return false;
    }

    static class ExtendedXHR extends XMLHttpRequest {

        protected ExtendedXHR() {
        }

        public final native void postFile(VHtml5File file)
        /*-{

            this.setRequestHeader('Content-Type', 'multipart/form-data');
            // Seems like IE10 will loose the file if we don't keep a reference to it...
            this.fileBeingUploaded = file;

            this.send(file);
        }-*/;

    }

    /** For internal use only. May be removed or replaced in the future. */
    public List fileIds = new ArrayList<>();

    /** For internal use only. May be removed or replaced in the future. */
    public List files = new ArrayList<>();

    private void queueFilePost(final int fileId, final VHtml5File file) {
        fileIds.add(fileId);
        files.add(file);
    }

    @Override
    public VDropHandler getDropHandler() {
        return dropHandler;
    }

    protected VerticalDropLocation verticalDropLocation;
    protected HorizontalDropLocation horizontalDropLocation;
    private VerticalDropLocation emphasizedVDrop;
    private HorizontalDropLocation emphasizedHDrop;

    /**
     * Flag used by html5 dd
     */
    private boolean currentlyValid;
    private Widget dragImageWidget;

    private static final String OVER_STYLE = "v-ddwrapper-over";

    public class CustomDropHandler extends VAbstractDropHandler {

        @Override
        public void dragEnter(VDragEvent drag) {
            if (!getConnector().isEnabled()) {
                return;
            }
            updateDropDetails(drag);
            currentlyValid = false;
            super.dragEnter(drag);
        }

        @Override
        public void dragLeave(VDragEvent drag) {
            deEmphasis(true);
            dragleavetimer = null;
        }

        @Override
        public void dragOver(final VDragEvent drag) {
            if (!getConnector().isEnabled()) {
                return;
            }
            boolean detailsChanged = updateDropDetails(drag);
            if (detailsChanged) {
                currentlyValid = false;
                validate(new VAcceptCallback() {

                    @Override
                    public void accepted(VDragEvent event) {
                        dragAccepted(drag);
                    }
                }, drag);
            }
        }

        @Override
        public boolean drop(VDragEvent drag) {
            if (!getConnector().isEnabled()) {
                return false;
            }
            deEmphasis(true);

            Map dd = drag.getDropDetails();

            // this is absolute layout based, and we may want to set
            // component
            // relatively to where the drag ended.
            // need to add current location of the drop area

            int absoluteLeft = getAbsoluteLeft();
            int absoluteTop = getAbsoluteTop();

            dd.put("absoluteLeft", absoluteLeft);
            dd.put("absoluteTop", absoluteTop);

            if (verticalDropLocation != null) {
                dd.put("verticalLocation", verticalDropLocation.toString());
                dd.put("horizontalLocation", horizontalDropLocation.toString());
            }

            return super.drop(drag);
        }

        @Override
        protected void dragAccepted(VDragEvent drag) {
            if (!getConnector().isEnabled()) {
                return;
            }
            currentlyValid = true;
            emphasis(drag);
        }

        @Override
        public ComponentConnector getConnector() {
            return VDragAndDropWrapper.this.getConnector();
        }

        @Override
        public ApplicationConnection getApplicationConnection() {
            return client;
        }

    }

    public ComponentConnector getConnector() {
        return ConnectorMap.get(client).getConnector(this);
    }

    /**
     * @deprecated As of 7.2, call or override
     *             {@link #hookHtml5DragStart(Element)} instead
     */
    @Deprecated
    protected native void hookHtml5DragStart(
            com.google.gwt.user.client.Element el)
    /*-{
        var me = this;
        el.addEventListener("dragstart",  $entry(function(ev) {
            return [email protected]::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
        }), false);
    }-*/;

    /**
     * @since 7.2
     */
    protected void hookHtml5DragStart(Element el) {
        hookHtml5DragStart(DOM.asOld(el));
    }

    /**
     * Prototype code, memory leak risk.
     *
     * @param el
     * @deprecated As of 7.2, call or override {@link #hookHtml5Events(Element)}
     *             instead
     */
    @Deprecated
    protected native void hookHtml5Events(com.google.gwt.user.client.Element el)
    /*-{
            var me = this;

            el.addEventListener("dragenter",  $entry(function(ev) {
                return [email protected]::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
            }), false);

            el.addEventListener("dragleave",  $entry(function(ev) {
                return [email protected]::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
            }), false);

            el.addEventListener("dragover",  $entry(function(ev) {
                return [email protected]::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
            }), false);

            el.addEventListener("drop",  $entry(function(ev) {
                return [email protected]::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
            }), false);
    }-*/;

    /**
     * Prototype code, memory leak risk.
     *
     * @param el
     *
     * @since 7.2
     */
    protected void hookHtml5Events(Element el) {
        hookHtml5Events(DOM.asOld(el));
    }

    public boolean updateDropDetails(VDragEvent drag) {
        VerticalDropLocation oldVL = verticalDropLocation;
        verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(),
                drag.getCurrentGwtEvent(), 0.2);
        drag.getDropDetails().put("verticalLocation",
                verticalDropLocation.toString());
        HorizontalDropLocation oldHL = horizontalDropLocation;
        horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(),
                drag.getCurrentGwtEvent(), 0.2);
        drag.getDropDetails().put("horizontalLocation",
                horizontalDropLocation.toString());
        if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) {
            return true;
        } else {
            return false;
        }
    }

    protected void deEmphasis(boolean doLayout) {
        if (emphasizedVDrop != null) {
            VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false);
            VDragAndDropWrapper.setStyleName(getElement(),
                    OVER_STYLE + "-" + emphasizedVDrop.toString().toLowerCase(),
                    false);
            VDragAndDropWrapper.setStyleName(getElement(),
                    OVER_STYLE + "-" + emphasizedHDrop.toString().toLowerCase(),
                    false);
        }
        if (doLayout) {
            notifySizePotentiallyChanged();
        }
    }

    private void notifySizePotentiallyChanged() {
        LayoutManager.get(client).setNeedsMeasure(getConnector());
    }

    protected void emphasis(VDragEvent drag) {
        deEmphasis(false);
        VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true);
        VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
                + verticalDropLocation.toString().toLowerCase(), true);
        VDragAndDropWrapper.setStyleName(getElement(),
                OVER_STYLE + "-"
                        + horizontalDropLocation.toString().toLowerCase(),
                true);
        emphasizedVDrop = verticalDropLocation;
        emphasizedHDrop = horizontalDropLocation;

        // TODO build (to be an example) an emphasis mode where drag image
        // is fitted before or after the content
        notifySizePotentiallyChanged();
    }

    /**
     * Set the widget that will be used as the drag image when using
     * DragStartMode {@link COMPONENT_OTHER} .
     *
     * @param widget
     */
    public void setDragAndDropWidget(Widget widget) {
        dragImageWidget = widget;
    }

    /**
     * @return the widget used as drag image. Returns null if no
     *         widget is set.
     */
    public Widget getDragImageWidget() {
        return dragImageWidget;
    }

    /**
     * Internal client side interface used by the connector and the widget for
     * the drag and drop wrapper to signal the completion of an HTML5 file
     * upload.
     *
     * @since 7.6.4
     */
    public interface UploadHandler {

        public void uploadDone();

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy