Package com.google.livingstories.client.ui

Source Code of com.google.livingstories.client.ui.ToggleDisclosurePanel

/**
* 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.uibinder.client.UiConstructor;
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.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.lsp.event.BlockToggledEvent;
import com.google.livingstories.client.lsp.event.EventBus;
import com.google.livingstories.client.util.Constants;

/**
* A widget that consists of a header and a content panel.  The content panel
* contains one or two widgets, one representing the 'closed' state and an optional
* one representing the 'open' state.  If the open state is available,
* this widget toggles between the states each time the header is clicked.
* 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 ToggleDisclosurePanel extends Composite implements HasAnimation,
    HasOpenHandlers<ToggleDisclosurePanel>, HasCloseHandlers<ToggleDisclosurePanel>,
    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);
          boolean opened = !isOpen;
          // Need to set this open first here instead of relying on the event
          // in case the user didn't specify a content item id for this widget.
          setOpen(opened, true);
          if (contentItemId != null) {
            EventBus.INSTANCE.fireEvent(new BlockToggledEvent(opened, contentItemId));
          }
      }
    }
  }

  // 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 = "gwt-header";

  private static final String STYLENAME_CONTENT = "gwt-content";

  private final VerticalPanel mainPanel = new VerticalPanel();
  private final ClickableHeader header = new ClickableHeader();
  private final SimplePanel contentWrapper = new SimplePanel();

  private boolean isAnimationEnabled = true;
  private boolean isOpen = false;
  private Widget closedWidget;
  private Widget openedWidget;
  private Command onAnimationCompletion;
  private HandlerRegistration toggleEventHandler;
  private Long contentItemId;

  @UiConstructor
  public ToggleDisclosurePanel(boolean headerOnTop) {
    init(headerOnTop);
  }
 
  public ToggleDisclosurePanel(Widget panelHeader, boolean headerOnTop) {
    this(headerOnTop);
    setHeader(panelHeader);
  }

  @Override
  protected void onUnload() {
    super.onUnload();
    if (toggleEventHandler != null) {
      toggleEventHandler.removeHandler();
      toggleEventHandler = null;
    }
  }
 
  public HandlerRegistration addCloseHandler(
      CloseHandler<ToggleDisclosurePanel> handler) {
    return addHandler(handler, CloseEvent.getType());
  }

  public HandlerRegistration addOpenHandler(
      OpenHandler<ToggleDisclosurePanel> 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 closedContent the widget to show in the closed state
   * @param openedContent the widget to show in the opened state
   */
  public void setContent(Widget closedContent, Widget openedContent) {
    final Widget currentContent = getContent();

    // Remove existing content widget.
    if (currentContent != null) {
      contentWrapper.setWidget(null);
      closedWidget = null;
      openedWidget = null;
    }

    // Add new content widget if != null.
    if (closedContent != null) {
      closedWidget = closedContent;
      openedWidget = openedContent;
      if (openedContent == null) {
        isOpen = false;
      } else {
        openedContent.addStyleName(STYLENAME_CONTENT);
      }
      closedContent.addStyleName(STYLENAME_CONTENT);
      contentWrapper.setWidget(isOpen ? openedContent : closedContent);
    }
  }

  /**
   * Make this panel listen for events being fired for the specified contentItem id.
   */
  public void handleContentItemEvents(Long currentContentItemId) {
    this.contentItemId = currentContentItemId;
    toggleEventHandler = EventBus.INSTANCE.addHandler(BlockToggledEvent.TYPE,
        new BlockToggledEvent.Handler() {
          @Override
          public void onToggle(BlockToggledEvent e) {
            if (contentItemId.equals(e.getContentItemId())) {
              setOpen(e.isOpened(), e.shouldAnimate());
              e.finish();
            }
          }
        });
  }
 
  /**
   * 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, boolean animate) {
    if (this.isOpen != isOpen && openedWidget != null) {
      this.isOpen = isOpen;
      setContentDisplay(animate);
      fireEvent();
    }
  }
 
  public void toggle() {
    setOpen(!isOpen, 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 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");

      contentWrapper.setWidget(isOpen ? openedWidget : closedWidget);

      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();
        }
      }
    });
  }
}
TOP

Related Classes of com.google.livingstories.client.ui.ToggleDisclosurePanel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.