gwtquery.plugins.draggable.client.Draggable.java Source code

Java tutorial

Introduction

Here is the source code for gwtquery.plugins.draggable.client.Draggable.java

Source

/*
 * Copyright 2010 The gwtquery plugins team.
 *
 * 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 gwtquery.plugins.draggable.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.event.shared.UmbrellaException;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.plugins.MousePlugin;
import com.google.gwt.query.client.plugins.Plugin;
import com.google.gwt.query.client.plugins.events.GqEvent;
import gwtquery.plugins.draggable.client.DraggableOptions.DragFunction;
import gwtquery.plugins.draggable.client.DraggableOptions.HelperType;
import gwtquery.plugins.draggable.client.DraggableOptions.RevertOption;
import gwtquery.plugins.draggable.client.DraggableOptions.SelectFunction;
import gwtquery.plugins.draggable.client.events.BeforeDragStartEvent;
import gwtquery.plugins.draggable.client.events.DragContext;
import gwtquery.plugins.draggable.client.events.DragEvent;
import gwtquery.plugins.draggable.client.events.DragStartEvent;
import gwtquery.plugins.draggable.client.events.DragStopEvent;
import gwtquery.plugins.draggable.client.events.DraggableSelectedEvent;
import gwtquery.plugins.draggable.client.events.DraggableUnselectedEvent;
import gwtquery.plugins.draggable.client.plugins.CursorPlugin;
import gwtquery.plugins.draggable.client.plugins.DraggablePlugin;
import gwtquery.plugins.draggable.client.plugins.GroupSelectedPlugin;
import gwtquery.plugins.draggable.client.plugins.OpacityPlugin;
import gwtquery.plugins.draggable.client.plugins.ScrollPlugin;
import gwtquery.plugins.draggable.client.plugins.SnapPlugin;
import gwtquery.plugins.draggable.client.plugins.StackPlugin;
import gwtquery.plugins.draggable.client.plugins.ZIndexPlugin;

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

/**
 * Draggable plugin for GwtQuery
 */
public class Draggable extends MousePlugin {

    /**
     * Interface containing all css classes used in this plug-in
     */
    public static interface CssClassNames {
        String GWT_DRAGGABLE = "gwtQuery-draggable";
        String GWT_DRAGGABLE_DISABLED = "gwtQuery-draggable-disabled";
        String GWT_DRAGGABLE_DRAGGING = "gwtQuery-draggable-dragging";
    }

    private class DragCaller extends StartCaller {

        public DragCaller(DragContext ctx, DraggableHandler dragHandler, GqEvent e) {
            super(ctx, dragHandler, e);
        }

        public void call(DraggablePlugin plugin) {
            plugin.onDrag(dragHandler, ctx, e);
        }
    }

    private static interface PluginCaller {
        void call(DraggablePlugin plugin);
    }

    private class StartCaller implements PluginCaller {
        protected DragContext ctx;
        protected DraggableHandler dragHandler;
        protected GqEvent e;

        public StartCaller(DragContext ctx, DraggableHandler dragHandler, GqEvent e) {
            this.ctx = ctx;
            this.dragHandler = dragHandler;
            this.e = e;
        }

        public void call(DraggablePlugin plugin) {
            plugin.onStart(dragHandler, ctx, e);
        }
    }

    private class StopCaller extends StartCaller {

        public StopCaller(DragContext ctx, DraggableHandler dragHandler, GqEvent e) {
            super(ctx, dragHandler, e);
        }

        public void call(DraggablePlugin plugin) {
            plugin.onStop(dragHandler, ctx, e);
        }
    }

    public static final Class<Draggable> Draggable = Draggable.class;

    public static final String DRAGGABLE_HANDLER_KEY = "draggableHandler";

    static List<Element> selectedDraggables;

    private static Map<String, DraggablePlugin> draggablePlugins;

    // Register the plugin in GQuery
    static {
        GQuery.registerPlugin(Draggable.class, new Plugin<Draggable>() {
            public Draggable init(GQuery gq) {
                return new Draggable(gq);
            }
        });

        // register the different draggable plugins
        registerDraggablePlugin(new OpacityPlugin());
        registerDraggablePlugin(new ScrollPlugin());
        registerDraggablePlugin(new CursorPlugin());
        registerDraggablePlugin(new ZIndexPlugin());
        registerDraggablePlugin(new StackPlugin());
        registerDraggablePlugin(new SnapPlugin());
        registerDraggablePlugin(new GroupSelectedPlugin());

        selectedDraggables = new ArrayList<Element>();
    }

    /**
     * Register a draggable plugin that will be called during the drag operation
     *
     * @param plugin
     */
    public static void registerDraggablePlugin(DraggablePlugin plugin) {
        if (draggablePlugins == null) {
            draggablePlugins = new HashMap<String, DraggablePlugin>();
        }
        draggablePlugins.put(plugin.getName(), plugin);
    }

    private static void trigger(GwtEvent<?> e, DragFunction callback, DragContext dragContext,
            HasHandlers handlerManager) {
        if (handlerManager != null && e != null) {
            handlerManager.fireEvent(e);
        }
        if (callback != null) {
            callback.f(dragContext);
        }
    }

    private boolean dragStart = false;

    /**
     * Constructor
     *
     * @param gq
     */
    protected Draggable(GQuery gq) {
        super(gq);
    }

    /**
     * Remove the draggable behavior to the selected elements. This method
     * releases resources used by the plugin and should be called when an element
     * is removed of the DOM.
     *
     * @return
     */
    public Draggable destroy() {

        for (Element e : elements()) {
            selectedDraggables.remove(e);

            $(e).removeData(DRAGGABLE_HANDLER_KEY).removeClass(CssClassNames.GWT_DRAGGABLE,
                    CssClassNames.GWT_DRAGGABLE_DISABLED, CssClassNames.GWT_DRAGGABLE_DRAGGING);
        }
        destroyMouseHandler();
        return this;
    }

    /**
     * Make the selected elements draggable with default options
     *
     * @return
     */
    public Draggable draggable() {
        return draggable(new DraggableOptions(), null);
    }

    /**
     * Make the selected elements draggable by using the <code>options</code>
     *
     * @param options options to use during the drag operation
     * @return
     */
    public Draggable draggable(DraggableOptions options) {
        return draggable(options, null);
    }

    /**
     * Make the selected elements draggable by using the <code>options</code>. All
     * drag events will be fired on the <code>eventBus</code>
     *
     * @param options  options to use during the drag operation
     * @param eventBus The eventBus to use to fire events.
     * @return
     */
    public Draggable draggable(DraggableOptions options, HasHandlers eventBus) {

        this.eventBus = eventBus;

        initMouseHandler(options);

        for (Element e : elements()) {
            if (options.getHelperType() == HelperType.ORIGINAL
                    && !positionIsFixedAbsoluteOrRelative(e.getStyle().getPosition())) {
                e.getStyle().setPosition(Position.RELATIVE);
            }
            e.addClassName(CssClassNames.GWT_DRAGGABLE);

            if (options.isDisabled()) {
                e.addClassName(CssClassNames.GWT_DRAGGABLE_DISABLED);
            }
            DraggableHandler handler = new DraggableHandler(options);
            $(e).data(DRAGGABLE_HANDLER_KEY, handler);
        }

        return this;
    }

    /**
     * Make the selected elements draggable with default options. All drag events
     * will be fired on the <code>eventBus</code>
     *
     * @param eventBus The eventBus to use to fire events.
     * @return
     */
    public Draggable draggable(HasHandlers eventBus) {
        return draggable(new DraggableOptions(), eventBus);
    }

    /**
     * Get the {@link DraggableOptions} for the first element.
     *
     * @return
     */
    public DraggableOptions options() {

        DraggableHandler handler = data(DRAGGABLE_HANDLER_KEY, DraggableHandler.class);
        if (handler != null) {
            return handler.getOptions();
        }

        return null;
    }

    /**
     * Set the DraggableOptions on each element.
     *
     * @param options
     * @return
     */
    public Draggable options(DraggableOptions options) {

        for (Element e : elements()) {
            DraggableHandler handler = $(e).data(DRAGGABLE_HANDLER_KEY, DraggableHandler.class);
            if (handler != null) {
                handler.setOptions(options);
            }
        }
        return this;
    }

    @Override
    protected String getPluginName() {
        return "draggable";
    }

    @Override
    protected boolean mouseCapture(Element draggable, GqEvent event) {

        DraggableHandler handler = $(draggable).data(DRAGGABLE_HANDLER_KEY, DraggableHandler.class);
        return handler != null && handler.getHelper() == null && !handler.getOptions().isDisabled()
                && isHandleClicked(draggable, event);
    }

    @Override
    protected boolean mouseClick(Element element, GqEvent event) {
        // react on click event only if no metakey is pressed, if no drag occurs and
        // if more than one element are selected

        if (!event.isMetaKeyPressed() && !dragStart && selectedDraggables.size() > 1) {
            DraggableHandler dragHandler = DraggableHandler.getInstance(element);
            DraggableOptions options = dragHandler.getOptions();
            unselectAll();
            select(element, options.getSelectedClassName());
        }

        dragStart = false;

        return !event.isMetaKeyPressed();
    }

    @Override
    protected boolean mouseDown(Element draggable, GqEvent event) {

        DraggableHandler dragHandler = DraggableHandler.getInstance(draggable);
        DraggableOptions options = dragHandler.getOptions();

        if (!options.isMultipleSelection()) {
            // ensure all previously selected element are unselected
            unselectAll();

        } else {

            if (event.isMetaKeyPressed()) {

                if (selectedDraggables.contains(draggable)) {

                    unselect(draggable);

                } else if (canBeSelected(draggable, dragHandler)) {
                    select(draggable, options.getSelectedClassName());
                }
            } else if (!selectedDraggables.contains(draggable)) {
                // if no meta key pressed and if the draggable is not selected ,
                // deselect all and select the draggable.
                unselectAll();
                select(draggable, options.getSelectedClassName());

            }
        }
        return super.mouseDown(draggable, event) && !event.isMetaKeyPressed();
    }

    @Override
    protected boolean mouseDrag(Element currentDraggable, GqEvent event) {
        dragStart = true;

        boolean result = false;

        DragContext ctx = new DragContext(currentDraggable, currentDraggable, selectedDraggables);
        result |= mouseDragImpl(ctx, DraggableHandler.getInstance(currentDraggable), event, false);

        for (Element draggable : selectedDraggables) {
            if (draggable != currentDraggable) {
                ctx = new DragContext(draggable, currentDraggable, selectedDraggables);
                result |= mouseDragImpl(ctx, DraggableHandler.getInstance(draggable), event, false);
            }

        }

        return result;
    }

    @Override
    protected boolean mouseStart(Element currentDraggable, GqEvent event) {

        boolean result = false;

        DraggableHandler dragHandler = getHandler(currentDraggable);
        DraggableOptions options = dragHandler.getOptions();
        // if the currentDraggable have not the same scope has the other selected
        // draggable or doesn't accept multi selection, unselect all
        if (!canBeSelected(currentDraggable, dragHandler) || !options.isMultipleSelection()) {
            unselectAll();
        }

        // if the currentDraggable is not yet selected and can be selected,
        // select it
        if (!selectedDraggables.contains(currentDraggable) && canBeSelected(currentDraggable, dragHandler)
                && options.isMultipleSelection()) {
            GWT.log("select element");
            select(currentDraggable, options.getSelectedClassName());
        }

        // select other draggable elements if select options is set
        SelectFunction selectFunction = options.getSelect();
        if (selectFunction != null) {
            GQuery followers = selectFunction.selectElements();
            followers.each(new Function() {
                @Override
                public void f(Element e) {
                    DraggableHandler handler = DraggableHandler.getInstance(e);
                    if (handler != null) {
                        GWT.log("Select automatic selected element " + e.getId());
                        select(e, handler.getOptions().getSelectedClassName());
                    }
                }
            });
        }

        // first call mouseStart for the initial draggable
        DragContext ctx = new DragContext(currentDraggable, currentDraggable, selectedDraggables);
        result |= mouseStartImpl(ctx, event);

        // call mouseStartImpl for the others
        for (Element draggable : selectedDraggables) {
            if (draggable != currentDraggable) {
                ctx = new DragContext(draggable, currentDraggable, selectedDraggables);
                result |= mouseStartImpl(ctx, event);
            }
        }

        return result;
    }

    @Override
    protected boolean mouseStop(Element initialDraggable, final GqEvent event) {
        boolean result = false;

        DragContext ctx = new DragContext(initialDraggable, initialDraggable, selectedDraggables);
        result |= mouseStopImpl(ctx, event);

        for (Element draggable : selectedDraggables) {
            if (draggable != initialDraggable) {
                ctx = new DragContext(draggable, initialDraggable, selectedDraggables);
                result |= mouseStopImpl(ctx, event);
            }

        }

        DraggableOptions options = getOptions(initialDraggable);

        // deselect automatic selected elements
        // select other draggable elements if select options is set
        SelectFunction selectFunction = options.getSelect();
        if (selectFunction != null) {
            GQuery followers = selectFunction.selectElements();
            for (Element e : followers.elements()) {
                unselect(e);
            }
        }

        return result;
    }

    private void callPlugins(PluginCaller caller, DraggableOptions options) {
        for (DraggablePlugin plugin : draggablePlugins.values()) {
            if (plugin.hasToBeExecuted(options)) {
                caller.call(plugin);
            }
        }
    }

    private boolean canBeSelected(Element draggable, DraggableHandler handler) {
        if (selectedDraggables.isEmpty()) {

            return true;
        }

        String selectedScope = DraggableHandler.getInstance(selectedDraggables.get(0)).getOptions().getScope();
        String currentScope = handler.getOptions().getScope();

        return currentScope.equals(selectedScope);

    }

    private DragAndDropManager getDragAndDropManager() {
        return DragAndDropManager.getInstance();
    }

    private DraggableHandler getHandler(Element draggable) {

        return DraggableHandler.getInstance(draggable);
    }

    private DraggableOptions getOptions(Element draggable) {
        DraggableHandler handler = getHandler(draggable);
        return handler != null ? handler.getOptions() : null;
    }

    private boolean isHandleClicked(Element draggable, final GqEvent event) {
        DraggableOptions options = getOptions(draggable);
        // if no handle or if specified handle is not inside the draggable element,
        // continue
        if (options.getHandle() == null || $(options.getHandle(), draggable).length() == 0) {
            return true;
        }

        // OK, we have a valid handle, check if we are clicking on the handle object
        // or one of its descendants
        GQuery handleAndDescendant = $(options.getHandle(), draggable).find("*").andSelf();
        for (Element e : handleAndDescendant.elements()) {
            if (e == event.getEventTarget().cast()) {
                return true;
            }
        }
        return false;
    }

    /**
     * implementation of mouse drag
     */
    private boolean mouseDragImpl(DragContext ctx, DraggableHandler dragHandler, GqEvent event,
            boolean noPropagation) {
        Element draggable = ctx.getDraggable();

        dragHandler.regeneratePositions(event);

        if (!noPropagation) {

            callPlugins(new DragCaller(ctx, dragHandler, event), dragHandler.getOptions());

            try {
                trigger(new DragEvent(ctx), dragHandler.getOptions().getOnDrag(), ctx);
            } catch (UmbrellaException e) {
                for (Throwable t : e.getCauses()) {
                    if (t instanceof StopDragException) {
                        mouseStop(draggable, event);
                        return false;
                    }
                }

            }
        }

        dragHandler.moveHelper(noPropagation);

        if (getDragAndDropManager().isHandleDroppable(ctx)) {
            getDragAndDropManager().drag(ctx, event);
        }

        return false;
    }

    private boolean mouseStartImpl(final DragContext ctx, final GqEvent event) {

        Element draggable = ctx.getDraggable();
        final DraggableHandler dragHandler = DraggableHandler.getInstance(draggable);
        DraggableOptions options = dragHandler.getOptions();

        try {
            trigger(new BeforeDragStartEvent(ctx), options.getOnBeforeDragStart(), ctx);
        } catch (UmbrellaException e) {
            for (Throwable t : e.getCauses()) {
                if (t instanceof StopDragException) {
                    return false;
                }
            }

        }

        dragHandler.createHelper(draggable, event);
        dragHandler.cacheHelperSize();

        dragHandler.initialize(draggable, event);
        callPlugins(new StartCaller(ctx, dragHandler, event), options);

        try {
            trigger(new DragStartEvent(ctx), options.getOnDragStart(), ctx);
        } catch (UmbrellaException e) {
            for (Throwable t : e.getCauses()) {
                if (t instanceof StopDragException) {
                    mouseStop(draggable, event);
                    return false;
                }
            }

        }

        dragHandler.cacheHelperSize();

        if (getDragAndDropManager().isHandleDroppable(ctx)) {
            getDragAndDropManager().initialize(ctx, event);
        }

        dragHandler.getHelper().addClass(CssClassNames.GWT_DRAGGABLE_DRAGGING);
        // defer the mouseDragImpl to be sure that all selected draggable are
        // initialized
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            public void execute() {
                mouseDragImpl(ctx, dragHandler, event, true);

            }
        });

        return true;
    }

    private boolean mouseStopImpl(final DragContext ctx, final GqEvent event) {
        final Element draggable = ctx.getDraggable();
        final DraggableHandler handler = getHandler(draggable);
        final DraggableOptions options = handler.getOptions();

        boolean dropped = isDropped(ctx, event);

        if (draggable == null) {
            return false;
        }

        RevertOption revertOption = options.getRevert();
        if (revertOption.doRevert(dropped)) {
            handler.revertToOriginalPosition(new Function() {
                @Override
                public void f(Element e) {
                    callPlugins(new StopCaller(ctx, handler, event), options);
                    triggerDragStop(ctx, options);

                    handler.clear(draggable);
                }
            });
            return false;
        }

        callPlugins(new StopCaller(ctx, handler, event), options);
        triggerDragStop(ctx, options);

        handler.clear(draggable);

        return false;
    }

    private boolean isDropped(DragContext ctx, GqEvent event) {

        boolean dropped = false;

        if (ctx.getDraggable() == ctx.getInitialDraggable()) {

            if (getDragAndDropManager().isHandleDroppable(ctx)) {
                dropped = getDragAndDropManager().drop(ctx, event);
            }

            $(ctx.getInitialDraggable()).data("_is_dropped", dropped);

        } else {
            dropped = $(ctx.getInitialDraggable()).data("_is_dropped", Boolean.class);

        }
        return dropped;
    }

    private native boolean positionIsFixedAbsoluteOrRelative(String position) /*-{
                                                                              return (/^(?:r|a|f)/).test(position);
                                                                              }-*/;

    private void select(Element draggable, String selectedCssClass) {
        if (selectedDraggables.contains(draggable)) {
            return;
        }
        selectedDraggables.add(draggable);

        if (selectedCssClass != null) {
            draggable.addClassName(selectedCssClass);
        }
        GWT.log("trigger DraggableSelectedEvent");
        trigger(new DraggableSelectedEvent(draggable), getOptions(draggable).getOnSelected(), draggable);
    }

    private void unselect(Element draggable) {

        DraggableHandler handler = DraggableHandler.getInstance(draggable);
        DraggableOptions options = handler.getOptions();

        if (options.getSelectedClassName() != null) {
            draggable.removeClassName(options.getSelectedClassName());
        }
        GWT.log("trigger DraggableUnselectedEvent");
        selectedDraggables.remove(draggable);
        trigger(new DraggableUnselectedEvent(draggable), options.getOnUnselected(), draggable);
    }

    private void trigger(GwtEvent<?> e, DragFunction callback, DragContext dragContext) {
        trigger(e, callback, dragContext, eventBus);
    }

    /**
     * Use a deferred command to be sure that this event is trigger after the
     * possible drop event.
     *
     * @param draggable
     * @param options
     */
    private void triggerDragStop(final DragContext ctx, final DraggableOptions options) {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            public void execute() {
                trigger(new DragStopEvent(ctx), options.getOnDragStop(), ctx);

            }
        });
    }

    private void unselectAll() {
        // TODO concurent modification list !
        while (selectedDraggables.size() != 0) {
            unselect(selectedDraggables.get(0));
        }

    }

}