Java tutorial
/** * Copyright 2010 Google Inc. * * 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.google.livingstories.client.ui; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.HasClickHandlers; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.logical.shared.HasCloseHandlers; import com.google.gwt.event.logical.shared.HasOpenHandlers; import com.google.gwt.event.logical.shared.OpenEvent; import com.google.gwt.event.logical.shared.OpenHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DeferredCommand; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasAnimation; import com.google.gwt.user.client.ui.HasText; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.livingstories.client.util.Constants; import com.google.livingstories.client.util.LivingStoryControls; import java.util.List; /** * A widget that consists of a header and a content panel. Some items within the * content panel are initially hidden, and clicking the header will toggle the display * state of these items. The panel will also animate, expanding and collapsing * to accomodate these elements. * * This class uses the same style rules as the standard gwt DisclosurePanel. * .gwt-DisclosurePanel { the panel's primary style } * .gwt-DisclosurePanel-open { dependent style set when panel is open } * .gwt-DisclosurePanel-closed { dependent style set when panel is closed } * .gwt-DisclosurePanel .header { style for the header } * .gwt-DisclosurePanel .content { style for the content } * * Quite a bit of this code comes directly from the standard DisclosurePanel itself; * however, extending that class didn't seem practical for our use case, so we rewrite * some functionality here. */ public final class PartialDisclosurePanel extends Composite implements HasAnimation, HasOpenHandlers<PartialDisclosurePanel>, HasCloseHandlers<PartialDisclosurePanel>, HasClickHandlers { /** * Used to wrap widgets in the header to provide click support. Effectively * wraps the widget in an <code>anchor</code> to get automatic keyboard * access. */ private final class ClickableHeader extends SimplePanel implements HasClickHandlers { private ClickableHeader() { // Anchor is used to allow keyboard access. super(DOM.createAnchor()); Element elem = getElement(); DOM.setElementProperty(elem, "href", "javascript:void(0);"); // Avoids layout problems from having blocks in inlines. DOM.setStyleAttribute(elem, "display", "block"); sinkEvents(Event.ONCLICK); setStyleName(STYLENAME_HEADER); } public HandlerRegistration addClickHandler(ClickHandler handler) { return addHandler(handler, ClickEvent.getType()); } @Override public void onBrowserEvent(Event event) { // no need to call super. switch (DOM.eventGetType(event)) { case Event.ONCLICK: // Prevent link default action. DOM.eventPreventDefault(event); ClickEvent.fireNativeEvent(event, this); setOpen(!isOpen); } } } // Stylename constants. private static final String STYLENAME_DEFAULT = "gwt-DisclosurePanel"; private static final String STYLENAME_SUFFIX_OPEN = "open"; private static final String STYLENAME_SUFFIX_CLOSED = "closed"; private static final String STYLENAME_HEADER = "header"; private static final String STYLENAME_CONTENT = "content"; private final VerticalPanel mainPanel = new VerticalPanel(); private final ClickableHeader header = new ClickableHeader(); private final SimplePanel contentWrapper = new SimplePanel(); private boolean isAnimationEnabled = false; private boolean isOpen = false; private List<Widget> toggledWidgets; private Command onAnimationCompletion; private Command oneTimeOnAnimationCompletion; public PartialDisclosurePanel(boolean headerOnTop) { init(headerOnTop); } public PartialDisclosurePanel(Widget panelHeader, boolean headerOnTop) { this(headerOnTop); setHeader(panelHeader); } public HandlerRegistration addCloseHandler(CloseHandler<PartialDisclosurePanel> handler) { return addHandler(handler, CloseEvent.getType()); } public HandlerRegistration addOpenHandler(OpenHandler<PartialDisclosurePanel> handler) { return addHandler(handler, OpenEvent.getType()); } public HandlerRegistration addClickHandler(ClickHandler handler) { return header.addClickHandler(handler); } public void setAnimationCompletionCommand(Command onAnimationCompletion) { this.onAnimationCompletion = onAnimationCompletion; } public void clear() { setContent(null, null); } public Widget getContent() { return contentWrapper.getWidget(); } public Widget getHeader() { return header.getWidget(); } public HasText getHeaderTextAccessor() { Widget widget = header.getWidget(); return (widget instanceof HasText) ? (HasText) widget : null; } public boolean isAnimationEnabled() { return isAnimationEnabled; } public boolean isOpen() { return isOpen; } public void setAnimationEnabled(boolean enable) { isAnimationEnabled = enable; } /** * Sets the content widget which can be opened and closed by this panel. If * there is a preexisting content widget, it will be detached. * * @param content the widget to be used as the content panel * @param toggledWidgets items that will be shown and hidden by this panel. */ public void setContent(Widget content, List<Widget> toggledWidgets) { final Widget currentContent = getContent(); // Remove existing content widget. if (currentContent != null) { contentWrapper.setWidget(null); this.toggledWidgets = null; currentContent.removeStyleName(STYLENAME_CONTENT); } // Add new content widget if != null. if (content != null) { contentWrapper.setWidget(content); this.toggledWidgets = toggledWidgets; content.addStyleName(STYLENAME_CONTENT); } } /** * Sets the widget used as the header for the panel. * * @param headerWidget the widget to be used as the header */ public void setHeader(Widget headerWidget) { header.setWidget(headerWidget); } /** * Changes the visible state of this <code>DisclosurePanel</code>. * * @param isOpen <code>true</code> to open the panel, <code>false</code> to * close */ public void setOpen(boolean isOpen) { if (this.isOpen != isOpen) { this.isOpen = isOpen; setContentDisplay(isAnimationEnabled); fireEvent(); } } public void scrollToContainedWidget(final Widget w) { Command doScroll = new Command() { @Override public void execute() { Window.scrollTo(0, w.getElement().getAbsoluteTop()); LivingStoryControls.repositionAnchoredPanel(); } }; if (isOpen()) { doScroll.execute(); } else { setOneTimeAnimationCompletionCommand(doScroll); setOpen(true); } } /** * <b>Affected Elements:</b> * <ul> * <li>-header = the clickable header.</li> * </ul> * * @see UIObject#onEnsureDebugId(String) */ @Override protected void onEnsureDebugId(String baseID) { super.onEnsureDebugId(baseID); header.ensureDebugId(baseID + "-header"); } private void setOneTimeAnimationCompletionCommand(Command oneTimeOnAnimationCompletion) { this.oneTimeOnAnimationCompletion = oneTimeOnAnimationCompletion; } private void fireEvent() { if (isOpen) { OpenEvent.fire(this, this); } else { CloseEvent.fire(this, this); } } private void init(boolean headerOnTop) { initWidget(mainPanel); if (headerOnTop) { mainPanel.add(header); mainPanel.add(contentWrapper); } else { mainPanel.add(contentWrapper); mainPanel.add(header); } DOM.setStyleAttribute(contentWrapper.getElement(), "padding", "0px"); DOM.setStyleAttribute(contentWrapper.getElement(), "overflow", "hidden"); setStyleName(STYLENAME_DEFAULT); addStyleDependentName(STYLENAME_SUFFIX_CLOSED); } private void setContentDisplay(boolean animate) { if (isOpen) { removeStyleDependentName(STYLENAME_SUFFIX_CLOSED); addStyleDependentName(STYLENAME_SUFFIX_OPEN); } else { removeStyleDependentName(STYLENAME_SUFFIX_OPEN); addStyleDependentName(STYLENAME_SUFFIX_CLOSED); } if (getContent() != null) { int oldHeight = getContent().getElement().getClientHeight(); DOM.setStyleAttribute(contentWrapper.getElement(), "height", oldHeight + "px"); for (Widget widget : toggledWidgets) { widget.setVisible(!widget.isVisible()); } int newHeight = getContent().getElement().getClientHeight(); if (animate) { ExpandEffect animation = new ExpandEffect(contentWrapper, newHeight); animation.run(Constants.ANIMATION_DURATION); } else { DOM.setStyleAttribute(contentWrapper.getElement(), "height", "auto"); runCompletionCode(); } } } private class ExpandEffect extends StyleEffect { public ExpandEffect(Widget widget, int newValue) { super(widget, "height", newValue); } @Override public void onComplete() { DOM.setStyleAttribute(widget.getElement(), "height", "auto"); runCompletionCode(); } } private void runCompletionCode() { // for IE friendliness, we always run the completion code via the DeferredCommand // mechanism. DeferredCommand.addCommand(new Command() { @Override public void execute() { if (onAnimationCompletion != null) { onAnimationCompletion.execute(); } if (oneTimeOnAnimationCompletion != null) { oneTimeOnAnimationCompletion.execute(); oneTimeOnAnimationCompletion = null; } } }); } }