Saturday, August 6, 2011

GWT DecoratorLayoutPanel

I've been experimenting with GWT and GWT Designer lately. I get the feeling that a version or two down the road, these are going to give me a massive productivity boost. Right now, I still find myself stuck trying to figure out why things aren't working as I expect.

For example the new GWT 2.0 Layout framework does not sit well with the pre-2.0 widgets such as DecoratorPanel. Try adding a LayoutPanel to a DecoratorPanel - it won't work, you'll just see nothing inside the DecoratorPanel.

To get round this, I've implemented a DecoratorLayoutPanel, which subclasses LayoutPanel and so can contain any Layout-compatible widgets. I've added the source code below - feel free to copy/reuse/extend as you like, and I'd welcome any feedback!

import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>
 * A {@link LayoutPanel} that wraps its contents in stylized boxes, which can be
 * used to add rounded corners to a {@link Widget}.
 * </p>
 * <p>
 * This class aims to be a drop-in replacement for
 * {@link com.google.gwt.user.client.ui.DecoratorPanel}.
 * However, it uses a {@link LayoutPanel} to layout the 9-box, whereas
 * {@link com.google.gwt.user.client.ui.DecoratorPanel} uses a DOM table.
 * The benefit is that this panel can contain other {@link LayoutPanel} widgets, which
 * {@link com.google.gwt.user.client.ui.DecoratorPanel} cannot do successfully.
 * </p>
 * <p>
 * All of the same class names used in {@link com.google.gwt.user.client.ui.DecoratorPanel}
 * are used here, e.g. .gwt-DecoratorPanel, .topCenter, .bottomRightInner
 * </p>
 * <p>
 * Important differences to {@link com.google.gwt.user.client.ui.DecoratorPanel}:
 * <ul>
 * <li>.middleCenter should not be given height and width of 100%, as this will cause
 * unexpected behaviour.</li>
 * <li>The size of the corners is set in the constructor, but note that this can be
 * overridden in CSS.</li>
 * </ul> 
 * </p>
 */
public class DecoratorLayoutPanel extends LayoutPanel implements AcceptsOneWidget {
    /**
     * The default style name.
     */
    private static final String DEFAULT_STYLENAME = "gwt-DecoratorPanel";
    
    /**
     * Wrapper for middle center widget.
     */
    private LayoutPanel container;

    /**
     * Middle center widget
     */
    private Widget widget;
    
    /**
     * Create new instance using default size of corners.
     */
    public DecoratorLayoutPanel() {
        this(5, Unit.PX);
    }
    
    /**
     * Create new instance using specified size of corners.
     * 
     * @param size Height and width of corners
     * @param unit Unit to use for size 
     */
    public DecoratorLayoutPanel(double size, Unit unit) {
        this.setStyleName(DEFAULT_STYLENAME);
        
        /* Container for center widget */
        this.container = new LayoutPanel();            
        
        /* All inner widgets */
        Widget topLeftInner = new HTML(" ");
        Widget topCenterInner = new HTML(" ");
        Widget topRightInner = new HTML(" ");
        Widget middleLeftInner = new HTML(" ");
        Widget middleCenterInner = this.container;
        Widget middleRightInner = new HTML(" ");
        Widget bottomLeftInner = new HTML(" ");
        Widget bottomCenterInner = new HTML(" ");
        Widget bottomRightInner = new HTML(" ");
        
        /* Add to LayoutPanel */
        super.add(topLeftInner);
        super.add(topCenterInner);
        super.add(topRightInner);
        super.add(middleLeftInner);
        super.add(middleCenterInner);
        super.add(middleRightInner);
        super.add(bottomLeftInner);
        super.add(bottomCenterInner);
        super.add(bottomRightInner);
        
        /* Set default positioning - can override using CSS */
        this.setWidgetLeftWidth(topLeftInner, 0, unit, size, unit);
        this.setWidgetLeftRight(topCenterInner, size, unit, size, unit);
        this.setWidgetRightWidth(topRightInner, 0, unit, size, unit);
        this.setWidgetTopHeight(topLeftInner, 0, unit, size, unit);
        this.setWidgetTopHeight(topCenterInner, 0, unit, size, unit);
        this.setWidgetTopHeight(topRightInner, 0, unit, size, unit);
        
        this.setWidgetLeftWidth(middleLeftInner, 0, unit, size, unit);
        this.setWidgetLeftRight(middleCenterInner, size, unit, size, unit);
        this.setWidgetRightWidth(middleRightInner, 0, unit, size, unit);
        this.setWidgetTopBottom(middleLeftInner, size, unit, size, unit);
        this.setWidgetTopBottom(middleCenterInner, size, unit, size, unit);
        this.setWidgetTopBottom(middleRightInner, size, unit, size, unit);
        
        this.setWidgetLeftWidth(bottomLeftInner, 0, unit, size, unit);
        this.setWidgetLeftRight(bottomCenterInner, size, unit, size, unit);
        this.setWidgetRightWidth(bottomRightInner, 0, unit, size, unit);
        this.setWidgetBottomHeight(bottomLeftInner, 0, unit, size, unit);
        this.setWidgetBottomHeight(bottomCenterInner, 0, unit, size, unit);
        this.setWidgetBottomHeight(bottomRightInner, 0, unit, size, unit);
        
        /* Set CSS Styles */
        topLeftInner.setStyleName("topLeftInner");
        topCenterInner.setStyleName("topCenterInner");
        topRightInner.setStyleName("topRightInner");
        middleLeftInner.setStyleName("middleLeftInner");
        middleCenterInner.setStyleName("middleCenterInner");
        middleRightInner.setStyleName("middleRightInner");
        bottomLeftInner.setStyleName("bottomLeftInner");
        bottomCenterInner.setStyleName("bottomCenterInner");
        bottomRightInner.setStyleName("bottomRightInner");        
        this.getWidgetContainerElement(topLeftInner).setClassName("topLeft");
        this.getWidgetContainerElement(topCenterInner).setClassName("topCenter");
        this.getWidgetContainerElement(topRightInner).setClassName("topRight");
        this.getWidgetContainerElement(middleLeftInner).setClassName("middleLeft");
        this.getWidgetContainerElement(middleCenterInner).setClassName("middleCenter");
        this.getWidgetContainerElement(middleRightInner).setClassName("middleRight");
        this.getWidgetContainerElement(bottomLeftInner).setClassName("bottomLeft");
        this.getWidgetContainerElement(bottomCenterInner).setClassName("bottomCenter");
        this.getWidgetContainerElement(bottomRightInner).setClassName("bottomRight");
    }
    
    /**
     * Set the center middle widget.
     * 
     * @param widget The widget to add
     */
    @Override
    public void add(Widget widget) {
        setOneWidget(widget);
    }
    
    /**
     * Overloaded version for IsWidget.
     * 
     * @see #add(Widget)
     */
    public void add(IsWidget w) {
        setWidget(w);
    }
    
    /**
     * Set the center middle widget.
     * 
     * @param w The widget to add
     */
    @Override
    public void setWidget(IsWidget w) {
        setOneWidget(asWidgetOrNull(w));
    }
    
    private void setOneWidget(Widget w) {
        // validate
        if (w == widget) {
            return;
        }
        
        // remove the old widget
        if (widget != null) {
            this.container.remove(widget);
        }
        
        // logical attach
        widget = w;

        if (w != null) {
            this.container.add(w);
        }            
    }
}