/* * Copyright (C) 2013 Red Hat, Inc. and/or its affiliates. * * 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 org.jboss.errai.demo.grocery.client.local; import java.util.List; import javax.enterprise.context.Dependent; import org.jboss.errai.demo.grocery.client.shared.Department; import org.jboss.errai.ioc.client.api.LoadAsync; import org.jboss.errai.ui.client.widget.ListWidget; import com.google.gwt.animation.client.Animation; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.DragEnterEvent; import com.google.gwt.event.dom.client.DragEnterHandler; import com.google.gwt.event.dom.client.DragLeaveEvent; import com.google.gwt.event.dom.client.DragLeaveHandler; import com.google.gwt.event.dom.client.DragOverEvent; import com.google.gwt.event.dom.client.DragOverHandler; import com.google.gwt.event.dom.client.DragStartEvent; import com.google.gwt.event.dom.client.DragStartHandler; import com.google.gwt.event.dom.client.DropEvent; import com.google.gwt.event.dom.client.DropHandler; /** * A list of Department objects (each represented by a DepartmentWidget) whose entries can be dragged to rearrange their order. * <p> * It should be easy at some point in the future to generalize this and include it alongside ListWidget in the framework. * * @author Jonathan Fuerth <jfuerth@redhat.com> */ @Dependent @LoadAsync public class DepartmentList extends ListWidget<Department, DepartmentWidget> { /** * When an entry in this list is currently being dragged by the user, this field refers to it. When nothing is being * dragged, this field is null. */ private DepartmentWidget draggingDepartmentWidget; @Override protected Class<DepartmentWidget> getItemComponentType() { return DepartmentWidget.class; } /** * Sets the list of model objects that should be represented in the list. This widget supports rearrangement of its items by * drag-and-drop. The rearrangement is performed on the given list object, so be sure of two things: * <ol> * <li>The given item list must be mutable * <li>If you want to save the list in its rearranged state, you have to retain a reference to the list and persist it in * its new order when this list widget has been disposed (or any other time you like) * </ol> * * @param items The list of items to display in the list. This list must support the add() and remove() operations, or * drag-and-drop operations will fail. */ @Override public void setItems(final List<Department> items) { super.setItems(items); } /** * Adding drag and drop support to all rendered item widgets. */ @Override protected void onItemsRendered(final List<Department> items) { // make all the widgets draggable for (int i = 0; i < getPanel().getWidgetCount(); i++) { final int widgetIndex = i; final DepartmentWidget dw = getComponent(widgetIndex); dw.getElement().getStyle().setPaddingRight(20, Unit.PX); final ItemMoveAnimation growAnimation = new ItemMoveAnimation(dw); dw.getElement().setDraggable(Element.DRAGGABLE_TRUE); dw.addDragStartHandler(new DragStartHandler() { @Override public void onDragStart(DragStartEvent event) { draggingDepartmentWidget = dw; event.setData("text", dw.getModel().getName()); event.getDataTransfer().setDragImage(dw.getElement(), 10, 10); dw.getElement().getStyle().setColor("#ddd"); } }); dw.addBitlessDomHandler(new DragEnterHandler() { @Override public void onDragEnter(DragEnterEvent event) { if (draggingDepartmentWidget == null) return; // some foreign object must be dragging over if (draggingDepartmentWidget == dw) return; // don't try to drag the widget onto itself! growAnimation.forward(draggingDepartmentWidget.getOffsetHeight()); } }, DragEnterEvent.getType()); dw.addBitlessDomHandler(new DragOverHandler() { @Override public void onDragOver(DragOverEvent event) { // we need to observe DragOver events, or we will not get a drop event from the browser } }, DragOverEvent.getType()); dw.addBitlessDomHandler(new DragLeaveHandler() { @Override public void onDragLeave(DragLeaveEvent event) { growAnimation.reverse(); } }, DragLeaveEvent.getType()); dw.addBitlessDomHandler(new DropHandler() { @Override public void onDrop(DropEvent event) { event.preventDefault(); growAnimation.reverse(); int indexOfDraggingWidget = -1; for (int i = 0; i < getPanel().getWidgetCount(); i++) { if (draggingDepartmentWidget == getPanel().getWidget(i)) { indexOfDraggingWidget = i; break; } } // remove first then add. if done the other way around, indices > widgetIndex become incorrect items.remove(indexOfDraggingWidget); int addIndex = widgetIndex; items.add(addIndex, draggingDepartmentWidget.getModel()); onItemsRendered(items); } }, DropEvent.getType()); } } /** * Animates the creation and destruction of a gap above a certain item in the list. * * @author Jonathan Fuerth <jfuerth@redhat.com> */ class ItemMoveAnimation extends Animation { private final DepartmentWidget item; private double start; private double current; private double end; public ItemMoveAnimation(DepartmentWidget item) { this.item = item; } @Override protected void onUpdate(double progress) { current = start + ((end - start) * progress); } @Override protected void onComplete() { current = end; item.getElement().getStyle().setPaddingTop(current, Unit.PX); super.onComplete(); } /** * Runs this animation forward, to make the widget grow empty space above its contents. * * @param targetGrowth the amount to grow by once the animation has finished. */ public void forward(double targetGrowth) { start = 0; end = targetGrowth; run(400); } /** * Runs this animation in reverse, to make the widget shrink to its normal size. Safe to call any time: whether the * animation is already running, has finished running, or was never run in the first place. */ public void reverse() { start = current; end = 0; // don't run the animation if we're already collapsed! if (current != 0) { run(400); } } } }