Java tutorial
/** * GwtGridStack.java (GridStackLayout) * * Copyright 2015 Vaadin Ltd, Sami Viitanen <sami.viitanen@vaadin.org> * * 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.vaadin.alump.gridstack.client; import com.google.gwt.core.client.Duration; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import org.vaadin.alump.gridstack.client.shared.GridStackChildOptions; import org.vaadin.alump.gridstack.client.shared.GridStackOptions; import java.util.*; import java.util.logging.Logger; public class GwtGridStack extends ComplexPanel { private final static Logger LOGGER = Logger.getLogger(GwtGridStack.class.getName()); private final static int FLUSH_DELAY_MS = 250; boolean initialized = false; private static int idCounter = 0; public final String elementId; protected List<GwtGridStackMoveListener> moveListeners = new ArrayList<GwtGridStackMoveListener>(); protected List<GwtGridStackReadyListener> readyListeners = new ArrayList<GwtGridStackReadyListener>(); protected long lastEvent = 0; protected boolean draggedOrResized = false; public final static long DISABLE_CLICK_AFTER_EVENT_MS = 100L; public final static String CONTENT_CLASSNAME = "grid-stack-item-content"; public final static String DRAG_HANDLE_CLASSNAME = GridStackOptions.DRAG_HANDLE_CLASSNAME; public final static String DISABLE_SCROLLING_CLASSNAME = "disable-scrolling"; private Map<Element, Widget> widgetWrappers = new HashMap<Element, Widget>(); private Map<Widget, GwtGridStackChangedItem> moveQueue = new HashMap<Widget, GwtGridStackChangedItem>(); public interface GwtGridStackMoveListener { void onWidgetsMoved(Map<Widget, GwtGridStackChangedItem> movedChildren); } public interface GwtGridStackReadyListener { void onReady(); } public GwtGridStack() { setElement(Document.get().createDivElement()); setStyleName("grid-stack"); elementId = "gridstack-" + (idCounter++); getElement().setId(elementId); } public boolean isInitialized() { return initialized; } public void setOptions(Integer width, Integer height, GridStackOptions options) { if (!initialized) { initialize(width, height, GwtGridStackOptions.createFrom(options)); } else { if (options.cellHeight != null) { nativeSetCellHeight(options.cellHeight.intValue()); } if (options.staticGrid != null) { nativeSetGridStatic(options.staticGrid.booleanValue()); } } } public void initialize(Integer width, Integer height, GwtGridStackOptions options) { if (initialized) { LOGGER.severe("gridstack already initialized"); return; } if (width != null) { getElement().setAttribute("data-gs-width", width.toString()); } if (height != null) { getElement().setAttribute("data-gs-height", height.toString()); } Duration duration = new Duration(); initializeGridStack(options); LOGGER.info("Initialize grid stack took " + duration.elapsedMillis()); initialized = true; for (GwtGridStackReadyListener readyListener : readyListeners) { readyListener.onReady(); } } @Override public void add(Widget widget) { add(widget, new GridStackChildOptions()); } public void add(Widget widget, GridStackChildOptions info) { Element wrapper = createWrapper(info); if (initialized) { addWidgetWrapperToGridStack(wrapper); } else { getElement().appendChild(wrapper); } widgetWrappers.put(wrapper, widget); super.add(widget, wrapper.getFirstChildElement()); } @Override public boolean remove(Widget widget) { if (initialized) { Element wrapper = widget.getElement().getParentElement().getParentElement(); widgetWrappers.remove(wrapper); removeWidgetWrapperFromGridStack(wrapper); wrapper.removeFromParent(); } return super.remove(widget); } protected Element createWrapper(GridStackChildOptions info) { Element wrapper = Document.get().createDivElement(); wrapper.addClassName("grid-stack-item"); if (info.x >= 0 && info.y >= 0) { wrapper.setAttribute("data-gs-x", Integer.toString(info.x)); wrapper.setAttribute("data-gs-y", Integer.toString(info.y)); } else { wrapper.setAttribute("data-gs-auto-position", "yes"); } wrapper.setAttribute("data-gs-width", Integer.toString(info.width)); wrapper.setAttribute("data-gs-height", Integer.toString(info.height)); if (info.minWidth != null) { wrapper.setAttribute("data-gs-min-width", Integer.toString(info.minWidth.intValue())); } if (info.maxWidth != null) { wrapper.setAttribute("data-gs-max-width", Integer.toString(info.maxWidth.intValue())); } if (info.minHeight != null) { wrapper.setAttribute("data-gs-min-height", Integer.toString(info.minHeight.intValue())); } if (info.maxHeight != null) { wrapper.setAttribute("data-gs-max-width", Integer.toString(info.maxHeight.intValue())); } if (info.locked) { wrapper.setAttribute("data-gs-locked", "yes"); } Element content = Document.get().createDivElement(); content.addClassName(CONTENT_CLASSNAME); if (!info.useDragHandle) { content.addClassName(DRAG_HANDLE_CLASSNAME); } if (info.disableScrolling) { wrapper.addClassName(DISABLE_SCROLLING_CLASSNAME); } wrapper.appendChild(content); if (info.useDragHandle) { Element dragHandle = Document.get().createDivElement(); dragHandle.addClassName("separate-handle"); dragHandle.addClassName(DRAG_HANDLE_CLASSNAME); wrapper.appendChild(dragHandle); } return wrapper; } public Element getWrapper(Widget child) { if (child.getParent() == this) { throw new IllegalArgumentException("Given widget is not child of this GridStack"); } return child.getElement().getParentElement(); } protected void onGridStackChange(Event event, GwtGridStackChangedItem[] items) { // This gets called sometimes with undefined items, not sure why, but ignoring it for now. if (items == null) { return; } for (int i = 0; i < items.length; ++i) { GwtGridStackChangedItem item = items[i]; Widget child = mapElementToWidget(item.getElement()); if (child == null) { // Null children in list can be ignored? continue; } else { moveQueue.put(child, item); } } flushMovedTimer.delay(); } protected class FlushMovedTimer extends Timer { public void delay() { cancel(); schedule(FLUSH_DELAY_MS); } @Override public void run() { for (GwtGridStackMoveListener moveListener : moveListeners) { moveListener.onWidgetsMoved(moveQueue); } moveQueue.clear(); } } private FlushMovedTimer flushMovedTimer = new FlushMovedTimer(); protected void onGridStackDragStart(Event event) { updateEventFlag(true); } protected void onGridStackDragStop(Event event) { updateEventFlag(false); } protected void onGridStackResizeStart(Event event) { updateEventFlag(true); } protected void onGridStackResizeStop(Event event) { updateEventFlag(false); } protected void updateEventFlag(boolean start) { draggedOrResized = start; lastEvent = new Date().getTime(); } protected Widget mapElementToWidget(Element element) { Widget child = widgetWrappers.get(element); if (child != null) { return child; } Iterator<Widget> iter = getChildren().iterator(); while (iter.hasNext()) { child = iter.next(); if (element.isOrHasChild(child.getElement())) { return child; } } return null; } protected native void initializeGridStack(GwtGridStackOptions options) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; var that = this; $wnd.$(function () { var element = $wnd.$('#' + elementId); element.gridstack(options); element.on('change', function(e, items) { that.@org.vaadin.alump.gridstack.client.GwtGridStack::onGridStackChange(*)(e, items); }); element.on('dragstart', function(e, items) { that.@org.vaadin.alump.gridstack.client.GwtGridStack::onGridStackDragStart(*)(e); }); element.on('dragstop', function(e, items) { that.@org.vaadin.alump.gridstack.client.GwtGridStack::onGridStackDragStop(*)(e); }); element.on('resizestart', function(e, items) { that.@org.vaadin.alump.gridstack.client.GwtGridStack::onGridStackResizeStart(*)(e); }); element.on('resizestop', function(e, items) { that.@org.vaadin.alump.gridstack.client.GwtGridStack::onGridStackResizeStop(*)(e); }); }); }-*/; protected native void addWidgetWrapperToGridStack(Element element) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.addWidget(element); }); }-*/; protected native void removeWidgetWrapperFromGridStack(Element element) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.removeWidget(element, false); }); }-*/; public void addMoveListener(GwtGridStackMoveListener listener) { moveListeners.add(listener); } public void removeMoveListener(GwtGridStackMoveListener listener) { moveListeners.remove(listener); } public void addReadyListener(GwtGridStackReadyListener listener) { readyListeners.add(listener); } public void removeReadyListener(GwtGridStackReadyListener listener) { readyListeners.remove(listener); } public void updateChild(Widget widget, GridStackChildOptions options) { Element wrapper = widget.getElement().getParentElement().getParentElement(); updateWidgetWrapper(wrapper, options.x, options.y, options.width, options.height); updateWidgetSizeLimits(wrapper, GwtGridSizeLimits.create(options)); setLocked(wrapper, options.locked); if (options.disableScrolling) { wrapper.addClassName(DISABLE_SCROLLING_CLASSNAME); } else { wrapper.removeClassName(DISABLE_SCROLLING_CLASSNAME); } } protected native final void updateWidgetWrapper(Element element, int x, int y, int width, int height) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); if(x >= 0 && y >= 0) { grid.update(element, x, y, width, height); } else { grid.resize(element, width, height); } }); }-*/; protected native final void updateWidgetSizeLimits(Element element, GwtGridSizeLimits values) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.minWidth(element, values.minWidth); //grid.max_width(element, values.maxWidth); grid.minHeight(element, values.minHeight); //grid.max_height(element, values.max_height); }); }-*/; protected native final void setLocked(Element element, boolean locked) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.locked(element, locked); }); }-*/; public void commit() { if (initialized && isAttached()) { nativeCommit(); } } protected native final void nativeCommit() /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.commit(); }); }-*/; public void batchUpdate() { if (initialized && isAttached()) { nativeBatchUpdate(); } } protected native final void nativeBatchUpdate() /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.batchUpdate(); }); }-*/; public boolean isClickOk() { return !draggedOrResized && new Date().getTime() > (lastEvent + DISABLE_CLICK_AFTER_EVENT_MS); } protected native final void nativeSetGridStatic(boolean gridStatic) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.setStatic(gridStatic); }); }-*/; protected native final void nativeSetCellHeight(int cellHeight) /*-{ var elementId = this.@org.vaadin.alump.gridstack.client.GwtGridStack::elementId; $wnd.$(function () { var grid = $wnd.$('#' + elementId).data('gridstack'); grid.cellHeight(cellHeight); }); }-*/; }