/**
* Sencha GXT 3.1.0-beta - Sencha for GWT
* Copyright(c) 2007-2014, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.widget.core.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiChild;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.Style.Direction;
import com.sencha.gxt.core.client.Style.Side;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.util.Size;
import com.sencha.gxt.fx.client.animation.AfterAnimateEvent;
import com.sencha.gxt.fx.client.animation.AfterAnimateEvent.AfterAnimateHandler;
import com.sencha.gxt.fx.client.animation.Fx;
import com.sencha.gxt.fx.client.animation.SlideIn;
import com.sencha.gxt.fx.client.animation.SlideOut;
import com.sencha.gxt.messages.client.DefaultMessages;
import com.sencha.gxt.widget.core.client.Header.HeaderAppearance;
import com.sencha.gxt.widget.core.client.button.ButtonBar;
import com.sencha.gxt.widget.core.client.button.IconButton.IconConfig;
import com.sencha.gxt.widget.core.client.button.ToolButton;
import com.sencha.gxt.widget.core.client.container.BoxLayoutContainer.BoxLayoutPack;
import com.sencha.gxt.widget.core.client.container.SimpleContainer;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseEvent;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseEvent.BeforeCollapseHandler;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseEvent.HasBeforeCollapseHandlers;
import com.sencha.gxt.widget.core.client.event.BeforeExpandEvent;
import com.sencha.gxt.widget.core.client.event.BeforeExpandEvent.BeforeExpandHandler;
import com.sencha.gxt.widget.core.client.event.BeforeExpandEvent.HasBeforeExpandHandlers;
import com.sencha.gxt.widget.core.client.event.CollapseEvent;
import com.sencha.gxt.widget.core.client.event.CollapseEvent.CollapseHandler;
import com.sencha.gxt.widget.core.client.event.CollapseEvent.HasCollapseHandlers;
import com.sencha.gxt.widget.core.client.event.ExpandEvent;
import com.sencha.gxt.widget.core.client.event.ExpandEvent.ExpandHandler;
import com.sencha.gxt.widget.core.client.event.ExpandEvent.HasExpandHandlers;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
/**
* ContentPanel is a component container that has specific functionality and structural components that make it the
* perfect building block for application-oriented user interfaces. A content panel contains separate header, footer and
* body sections. The header may contain an icon, text and a tool area that can be wired up to provide customized
* behavior. The footer contains buttons added using {@link #addButton(Widget)}. The body contains a single widget,
* added using {@link #add}. The widget is resized to match the size of the container. A content panel provides built-in
* expandable and collapsible behavior.
*
* Code snippet:
*
* <pre>
* public void onModuleLoad() {
* ContentPanel cp = new ContentPanel();
* cp.setHeadingText("Content Panel");
* cp.setPixelSize(250, 140);
* cp.setPosition(10, 10);
* cp.setCollapsible(true);
* cp.addTool(new ToolButton(ToolButton.GEAR));
* cp.addTool(new ToolButton(ToolButton.CLOSE));
* cp.setWidget(new HTML("This is an HTML Widget in a ContentPanel."));
* cp.addButton(new TextButton("Ok"));
* RootPanel.get().add(cp);
* }
* </pre>
*/
public class ContentPanel extends SimpleContainer implements HasBeforeExpandHandlers, HasExpandHandlers,
HasBeforeCollapseHandlers, HasCollapseHandlers, Collapsible {
/**
* The appearance of a content panel. A content panel has a header, body and footer. The header includes a button that
* can be used to collapse or expand the body. The button has an icon that changes to indicate whether a collapse or
* expand is possible. The body contains a single widget, added using {@link ContentPanel#add}. The widget is resized
* to match the size of the container. The footer contains a button bar with optional buttons.
*/
public interface ContentPanelAppearance {
/**
* Returns the button icon that indicates a collapse is possible.
*
* @return the collapse icon
*/
IconConfig collapseIcon();
/**
* Returns the button icon that indicates an expand is possible.
*
* @return the expand icon
*/
IconConfig expandIcon();
/**
* Returns the element that wraps the content panel body. In the default implementation, this wraps the body widget
* and footer.
*
* @param parent the content panel root element
* @return the element that wraps the body
*/
XElement getBodyWrap(XElement parent);
/**
* Returns the content panel body element.
*
* @param parent the content panel root element
* @return the body element
*/
XElement getContentElem(XElement parent);
/**
* Returns the content panel footer element.
*
* @param parent the content panel root element
* @return the body element
*/
XElement getFooterElem(XElement parent);
/**
* Returns the total height of the content panel frame elements.
*
* @param parent the content panel root element
* @return the total height of the frame elements
*/
int getFrameHeight(XElement parent);
/**
* Returns the total width of the content panel frame elements.
*
* @param parent the content panel root element
* @return the total width of the frame elements
*/
int getFrameWidth(XElement parent);
/**
* Returns the content panel header's appearance
*
* @return the header appearance
*/
HeaderAppearance getHeaderAppearance();
/**
* Returns the content panel header element.
*
* @param parent the content panel root element
* @return the content panel header element
*/
XElement getHeaderElem(XElement parent);
/**
* Returns the header size excluding any framing.
*
* @return the header size
*/
Size getHeaderSize(XElement parent);
/**
* Handles a change in the visibility of the body border.
*
* @param parent content panel root element
* @param border true to display the border
*/
void onBodyBorder(XElement parent, boolean border);
/**
* Hides or shows the header.
*
* @param parent content panel root element
* @param hide true to hide the header
*/
void onHideHeader(XElement parent, boolean hide);
/**
* Renders the appearance of a content panel as HTML into a {@link SafeHtmlBuilder}, suitable for passing to
* {@link Element#setInnerHTML(String)} on a container element.
*
* @param sb receives the rendered appearance
*/
void render(SafeHtmlBuilder sb);
}
/**
* Provides access to content panel messages.
*/
public interface ContentPanelMessages {
/**
* Returns the content panel collapse message.
*
* @return the content panel collapse message
*/
String panelCollapse();
/**
* Returns the content panel expand message.
*
* @return the content panel expand message
*/
String panelExpand();
}
/**
* Provides support for deferred binding for the panel header appearance.
*/
public interface PanelHeaderAppearance extends HeaderAppearance {
}
protected class DefaultContentPanelMessages implements ContentPanelMessages {
public String panelCollapse() {
return DefaultMessages.getMessages().panel_collapsePanel();
}
public String panelExpand() {
return DefaultMessages.getMessages().panel_expandPanel();
}
}
protected Header header;
protected ButtonBar buttonBar;
private final ContentPanelAppearance appearance;
private ContentPanelMessages messages;
private boolean animating;
private boolean animCollapse = true;
private int animationDuration = 500;
private ToolButton collapseBtn;
private boolean collapsed, hideCollapseTool;
protected boolean secondPassRequired;
private boolean collapsible;
private boolean titleCollapse;
private boolean headerVisible = true;
private boolean layoutOnExpand;
/**
* Creates a content panel with default appearance.
*/
public ContentPanel() {
this((ContentPanelAppearance) GWT.create(ContentPanelAppearance.class));
}
/**
* Creates a content panel with the specified appearance.
*
* @param appearance the appearance of the content panel.
*/
public ContentPanel(ContentPanelAppearance appearance) {
super(true);
this.appearance = appearance;
setDeferHeight(true);
SafeHtmlBuilder sb = new SafeHtmlBuilder();
appearance.render(sb);
setElement((Element) XDOM.create(sb.toSafeHtml()));
header = new Header(appearance.getHeaderAppearance());
ComponentHelper.setParent(this, header);
XElement headerElem = appearance.getHeaderElem(getElement());
headerElem.appendChild(header.getElement());
buttonBar = new ButtonBar();
buttonBar.setMinButtonWidth(75);
buttonBar.setPack(BoxLayoutPack.END);
buttonBar.setVisible(false);
appearance.getFooterElem(getElement()).appendChild(buttonBar.getElement());
}
@Override
public HandlerRegistration addBeforeCollapseHandler(BeforeCollapseHandler handler) {
return addHandler(handler, BeforeCollapseEvent.getType());
}
@Override
public HandlerRegistration addBeforeExpandHandler(BeforeExpandHandler handler) {
return addHandler(handler, BeforeExpandEvent.getType());
}
/**
* Adds a widget the the button bar.
*
* @param widget the widget to add
*/
@UiChild
public void addButton(Widget widget) {
buttonBar.add(widget);
if (isOrWasAttached() && !buttonBar.isVisible()) {
buttonBar.setVisible(true);
if (isAttached()) {
forceLayout();
}
}
}
@Override
public HandlerRegistration addCollapseHandler(CollapseHandler handler) {
return addHandler(handler, CollapseEvent.getType());
}
@Override
public HandlerRegistration addExpandHandler(ExpandHandler handler) {
return addHandler(handler, ExpandEvent.getType());
}
/**
* Adds a Tool to Header
*
* @param tool the tool to add
*/
@UiChild
public void addTool(Widget tool) {
header.addTool(tool);
}
@Override
public void collapse() {
if (!collapsed && !animating && fireCancellableEvent(new BeforeCollapseEvent())) {
hideShadow();
onCollapse();
}
}
@Override
public void expand() {
if (collapsed && !animating && fireCancellableEvent(new BeforeExpandEvent())) {
hideShadow();
onExpand();
}
}
@Override
public void forceLayout() {
if (collapsed) {
doLayout();
} else {
super.forceLayout();
}
}
/**
* Gets the duration for the expand/collapse animations
*
* @return the duration for the expand/collapse animations in milliseconds.
*/
public int getAnimationDuration() {
return animationDuration;
}
/**
* Gets a reference to the appearance this object was instantiated with
*
* @return the appearance impl used by this component
*/
public ContentPanelAppearance getAppearance() {
return appearance;
}
/**
* Returns the panel's body element.
*
* @return the body
*/
public XElement getBody() {
return getAppearance().getContentElem(getElement());
}
/**
* Returns the panel's button alignment.
*
* @return the button alignment
*/
public BoxLayoutPack getButtonAlign() {
return buttonBar.getPack();
}
/**
* Returns the content panel button bar. In the default implementation, the button bar is displayed in the content
* panel's footer.
*
* @return the button bar
*/
public ButtonBar getButtonBar() {
return buttonBar;
}
/**
* Returns the content panel header.
*
* @return the header
*/
public Header getHeader() {
return header;
}
/**
* Returns the HTML displayed in the header.
*
* @return the header HTML
*/
public String getHTML() {
return header.getHTML();
}
/**
* Returns the content panel messages.
*
* @return the content panel messages
*/
public ContentPanelMessages getMessages() {
if (messages == null) {
messages = new DefaultContentPanelMessages();
}
return messages;
}
/**
* Returns the minimum button width.
*
* @return the minimum button width
*/
public int getMinButtonWidth() {
return buttonBar.getMinButtonWidth();
}
/**
* Returns the content panel header text set by a previous call to {@link #setHeadingText(String)}.
*
* @return the header text
*/
public String getText() {
return header.getText();
}
/**
* Returns true if animated collapsing is enabled.
*
* @return true if animating
*/
public boolean isAnimCollapse() {
return animCollapse;
}
/**
* Returns true if the panel is collapsed.
*
* @return the collapsed state
*/
public boolean isCollapsed() {
return collapsed;
}
/**
* Returns true if the panel is collapsible.
*
* @return the collapsible state
*/
public boolean isCollapsible() {
return collapsible;
}
@Override
public boolean isExpanded() {
return !isCollapsed();
}
/**
* Returns true if the collapse tool is hidden.
*
* @return the hide collapse tool state
*/
public boolean isHideCollapseTool() {
return hideCollapseTool;
}
/**
* Returns true if title collapsing has been enabled.
*
* @return true for title collapse
*/
public boolean isTitleCollapse() {
return titleCollapse;
}
@Override
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
if (event.getTypeInt() == Event.ONCLICK && event.getEventTarget() != null) {
onClick(event);
}
}
/**
* Sets the duration for the expand/collapse animations.
*
* @param animationDuration the duration of the expand/collapse animations in milliseconds
*/
public void setAnimationDuration(int animationDuration) {
this.animationDuration = animationDuration;
}
/**
* Sets whether expand and collapse is animating (defaults to true).
*
* @param animCollapse true to enable animations
*/
public void setAnimCollapse(boolean animCollapse) {
this.animCollapse = animCollapse;
}
/**
* Displays or hides the body border.
*
* @param border true to display the border
*/
public void setBodyBorder(boolean border) {
getAppearance().onBodyBorder(getElement(), border);
}
/**
* Sets multiple style properties on the body element. Style attribute names must be in lower camel case, e.g.
* "backgroundColor:white; color:red;"
*
* @param style the style(s) to set
*/
public void setBodyStyle(String style) {
getAppearance().getContentElem(getElement()).applyStyles(style);
}
/**
* Adds a style class name to the body element.
*
* @param style the style class name
*/
public void setBodyStyleName(String style) {
getAppearance().getContentElem(getElement()).addClassName(style);
}
/**
* Sets the button alignment of any buttons added to this panel (defaults to RIGHT, pre-render).
*
* @param buttonAlign the button alignment
*/
public void setButtonAlign(BoxLayoutPack buttonAlign) {
assertPreRender();
buttonBar.setPack(buttonAlign);
}
/**
* True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into the
* header tool button area (defaults to false, pre-render).
*
* @param collapsible the collapsible state
*/
public void setCollapsible(boolean collapsible) {
assertPreRender();
this.collapsible = collapsible;
}
/**
* Sets the panel's expand state.
*
* @param expanded <code>true<code> true to expand
*/
public void setExpanded(boolean expanded) {
if (expanded) {
expand();
} else {
collapse();
}
}
/**
* Shows or hides the content panel header.
*
* @param visible true to show the header.
*/
public void setHeaderVisible(boolean visible) {
this.headerVisible = visible;
getAppearance().onHideHeader(getElement(), !visible);
}
/**
* Sets the heading.
*
* @param html the heading as HTML
*/
public void setHeadingHtml(SafeHtml html) {
header.setHTML(html);
}
/**
* Sets the heading.
*
* @param html the heading as HTML
*/
public void setHeadingHtml(String html) {
header.setHTML(html);
}
/**
* Sets the heading.
*
* @param text the heading text
*/
public void setHeadingText(String text) {
header.setText(text);
}
/**
* Sets whether the collapse tool should be displayed when the panel is collapsible.
*
* @param hideCollapseTool true if the tool is hidden
*/
public void setHideCollapseTool(boolean hideCollapseTool) {
this.hideCollapseTool = hideCollapseTool;
}
/**
* Sets the content panel messages.
*
* @param messages the messages
*/
public void setMessages(ContentPanelMessages messages) {
this.messages = messages;
}
/**
* Sets the minimum button width.
*
* @param width the button width
*/
public void setMinButtonWidth(int width) {
buttonBar.setMinButtonWidth(width);
}
/**
* True to allow expanding and collapsing the panel (when {@link #setCollapsible(boolean)} = true) by clicking
* anywhere in the header bar, false to allow it only by clicking to tool button (defaults to false).
*
* @param titleCollapse the titleCollapse to set
*/
public void setTitleCollapse(boolean titleCollapse) {
this.titleCollapse = titleCollapse;
if (titleCollapse) {
header.getElement().getStyle().setCursor(Cursor.POINTER);
sinkEvents(Event.ONCLICK);
} else {
header.getElement().getStyle().setCursor(Cursor.DEFAULT);
}
}
protected Size adjustBodySize() {
return new Size(0, 0);
}
protected void afterCollapse() {
collapsed = true;
animating = false;
getAppearance().getBodyWrap(getElement()).hide();
for (int i = 0; i < getWidgetCount(); i++) {
Widget w = getWidget(i);
if (w.isVisible() && w instanceof Component) {
((Component) w).notifyHide();
}
}
if (buttonBar != null && buttonBar.isAttached()) {
buttonBar.notifyHide();
}
sync(true);
// Re-enable the toggle tool after an animated collapse
if (animCollapse && collapseBtn != null) {
collapseBtn.enable();
}
if (collapseBtn != null) {
collapseBtn.changeStyle(getAppearance().expandIcon());
}
fireEvent(new CollapseEvent());
}
protected void afterExpand() {
collapsed = false;
animating = false;
for (int i = 0; i < getWidgetCount(); i++) {
Widget w = getWidget(i);
if (w.isVisible() && w instanceof Component) {
((Component) w).notifyShow();
}
}
if (buttonBar != null && buttonBar.isAttached()) {
buttonBar.notifyShow();
}
sync(true);
// Re-enable the toggle tool after an animated collapse
if (animCollapse && collapseBtn != null) {
collapseBtn.enable();
}
if (collapseBtn != null && !hideCollapseTool) {
collapseBtn.changeStyle(getAppearance().collapseIcon());
}
if (layoutOnExpand) {
layoutOnExpand = false;
super.forceLayout();
}
fireEvent(new ExpandEvent());
}
@Override
protected void doAttachChildren() {
super.doAttachChildren();
ComponentHelper.doAttach(header);
ComponentHelper.doAttach(buttonBar);
}
@Override
protected void doDetachChildren() {
super.doDetachChildren();
ComponentHelper.doDetach(header);
ComponentHelper.doDetach(buttonBar);
}
@Override
protected void doLayout() {
if (collapsed && widget != null && resize) {
layoutOnExpand = true;
return;
}
// EXTGWT-3414 - When the button is autosized, a width is not given to it, this will give it a width.
doLayoutButtonBar();
super.doLayout();
}
protected void doLayoutButtonBar() {
if (buttonBar != null) {
Size containerSize = getContainerTarget().getStyleSize();
buttonBar.setWidth(containerSize.getWidth());
}
}
@Override
protected XElement getContainerTarget() {
return getAppearance().getContentElem(getElement());
}
protected Size getFrameSize() {
return new Size(getAppearance().getFrameWidth(getElement()), getAppearance().getFrameHeight(getElement()));
}
protected void initTools() {
if (collapsible && !hideCollapseTool) {
collapseBtn = new ToolButton(collapsed ? getAppearance().expandIcon() : getAppearance().collapseIcon());
collapseBtn.addSelectHandler(new SelectHandler() {
@Override
public void onSelect(SelectEvent event) {
setExpanded(!isExpanded());
}
});
header.addTool(collapseBtn);
}
}
protected boolean layoutBars() {
if (buttonBar != null && buttonBar.getWidgetCount() > 0) {
boolean hlr = hadLayoutRunning;
if (!hadLayoutRunning) {
hadLayoutRunning = true;
}
// first call to layoutBars will happen before the panel has been sized
// button bar width = 0
boolean overflow = buttonBar.isEnableOverflow();
buttonBar.setEnableOverflow(false);
buttonBar.forceLayout();
buttonBar.setEnableOverflow(overflow);
hadLayoutRunning = hlr;
sync(true);
return true;
}
return false;
}
@Override
protected void onAfterFirstAttach() {
super.onAfterFirstAttach();
if (buttonBar.getWidgetCount() > 0) {
buttonBar.setVisible(true);
}
initTools();
layoutBars();
if (!headerVisible) {
header.setVisible(false);
}
}
protected void onClick(Event ce) {
if (collapsible && titleCollapse && header.getElement().isOrHasChild(ce.getEventTarget().<Element> cast())) {
setExpanded(!isExpanded());
}
}
protected void onCollapse() {
if (isAttached() && animCollapse) {
animating = true;
// Disable layout adjustment during animation
// Disable toggle tool during animated collapse
if (collapseBtn != null) {
collapseBtn.disable();
}
Fx fx = new Fx(getAnimationDuration());
fx.addAfterAnimateHandler(new AfterAnimateHandler() {
@Override
public void onAfterAnimate(AfterAnimateEvent event) {
afterCollapse();
}
});
fx.run(new SlideOut(getAppearance().getBodyWrap(getElement()), Direction.UP));
addStyleDependentName("animated");
} else {
getAppearance().getBodyWrap(getElement()).hide();
afterCollapse();
}
}
@Override
protected void onDisable() {
mask();
super.onDisable();
}
@Override
protected void onEnable() {
unmask();
super.onEnable();
}
protected void onExpand() {
if (isAttached() && animCollapse) {
animating = true;
// Show the body before animating
getAppearance().getBodyWrap(getElement()).show();
addStyleDependentName("animated");
Fx fx = new Fx(getAnimationDuration());
fx.addAfterAnimateHandler(new AfterAnimateHandler() {
@Override
public void onAfterAnimate(AfterAnimateEvent event) {
afterExpand();
}
});
fx.run(new SlideIn(getAppearance().getBodyWrap(getElement()), Direction.DOWN));
// Disable toggle tool during animated expand
if (collapseBtn != null) {
collapseBtn.disable();
}
} else {
getAppearance().getBodyWrap(getElement()).show();
afterExpand();
}
}
@Override
protected void onResize(int width, int height) {
Size frameSize = getFrameSize();
Size adjustBodySize = adjustBodySize();
if (isAutoWidth()) {
getContainerTarget().getStyle().clearWidth();
} else {
width -= frameSize.getWidth();
if (header != null) {
int aw = width - appearance.getHeaderElem(getElement()).getFrameWidth(Side.LEFT, Side.RIGHT);
header.setWidth(aw);
}
getContainerTarget().setWidth(width - adjustBodySize.getWidth(), true);
}
layoutBars();
// EXTGWT-2773 - Button bar inner frame height
if (buttonBar != null) {
appearance.getFooterElem(getElement()).setHeight(buttonBar.getOffsetHeight());
}
if (isAutoHeight()) {
getContainerTarget().getStyle().clearHeight();
} else {
height -= frameSize.getHeight();
height -= headerVisible ? getAppearance().getHeaderSize(getElement()).getHeight() : 0;
height -= getAppearance().getFooterElem(getElement()).getHeight(false);
getContainerTarget().setHeight(height - adjustBodySize.getHeight(), true);
}
super.onResize(width, height);
}
}