Java tutorial
/* * 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)); } } }