Simple DecoratorPanel with title.

April 6th, 2009 Alex Moffat Posted in GWT | No Comments »

Adding a rounded border to a panel can be handled nicely in GWT using the DecoratorPanel. I needed an extension to this, a panel with rounded corners and a title area as shown below.

Panel with title and button

The basic functionality can be created pretty easily using the DecoratorPanel as a base.


DecoratorPanel extends SimplePanel and uses a table to surround the contents with eight cells that can be given background images to create the borders you want. The image below shows how the DecoratorPanel cells surround the central cell where the Hello World text has been placed.

DecoratorPanel Example

Each element of the DecoratorPanel is given its own CSS class so it’s easy to add the styles needed. I find it easiest to use Firebug or similar to find what CSS classes are being set and what the DOM structure is when doing this sort of work. For instance the top right corner cell has the class topRight. If you set the style of the DecoratorPanel to panelStyle then so you can set the top right corner image to tr.gif using the CSS below (choosing appropriate values for width and height of course).


.panelStyle .topRight {
    background: url("tr.gif") no-repeat;
    width: 12px;
    height: 43px;
}

Unfortunately DecoratorPanel only lets you place a widget in the central area, you need to subclass it to get access to the area where the title will go. Each cell in the DecoratorPanel contains a DIV and DecoratorPanel provides a protected method, getCellElement, that gives you access to this DIV. A simple extension to add a setTitle method that places a widget in the center of the top row would be


    public void setTitle(Widget title) {
        this.title = title;
        getCellElement(0, 1).appendChild(title.getElement());
    }

Then, by using a FlowPanel to contain the text and button you can get the appearance you want. Note that you can only call the setTitle method once, you can’t use it to change the title widget because it doesn’t implement all of the functionality of SimplePanel setWidget method, which does support replacing the existing widget.

However, setTitle isn’t enough to get the button to actually work. If you try this you’ll find that click listeners attached to the button aren’t called. Two additional things are needed. First, setTitle must implement more of the behavior of the setWidget code in SimplePanel. In particular after the widget element has been added to the DOM structure with the appendChild call the widget itself needs to be incorporated into the GWT structure by calling the adopt method. The code for setTitle must call adopt(title) after it uses appendChild(title.getElement()).


    public void setTitle(Widget title) {
        this.title = title;
        getCellElement(0, 1).appendChild(title.getElement());
        adopt(title);
    }

Second, the iterator method must be overridden to return the title widget during iteration. We need this because one of the important things adopt ends up doing is calling onAttach on the widget being adopted, and it’s onAttach that sets up the event listeners we need for the button to work. However, onAttach is only called as a result of calling adopt if the parent, the DecoratorPanel subclass in this case, is already attached to the DOM. Otherwise when the parent is attached to the DOM at a later time the parent’s onAttach method uses the iterator to find and attach all its children (implement in the doAttachChildren method in Widget). The simple implementation below, while it violates the documented contract for HasWidgets iterator because it does not support remove, is enough to produce a working panel with a clickable button in the header.


    public Iterator iterator() {
        final Iterator superIterator = super.iterator();
        return new Iterator() {
            boolean hasTitle = title != null;

            public boolean hasNext() {
                return superIterator.hasNext() || hasTitle;
            }

            public Widget next() {
                if (superIterator.hasNext()) {
                    return superIterator.next();
                } else if (hasTitle) {
                    hasTitle = false;
                    return title;
                } else {
                    throw new NoSuchElementException();
                }
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

If you want to be able to remove the title widget using the remove method then more programming is required but for the simple requirements that I had this works fine and was quick to create, in fact quicker than writing this post.

Leave a Reply