Custom component / widget - what am I missing?

Hi folks,

atm I develop a custom component for Vaadin. It looks like the Vaadin ComboBox but the popup/overlay shown on mouse click event is different. It is an arbitrary component, e.g. an option group that updates the (readonly) text field of the custom component to show the selected options (multiple allowed in my example use). So basically I need the appearance of a combo box combined with the popup of the Vaadin PopupView component. I tried to implement the relevant part of both. Now, my specific example with the option group works fine so far but when a wrap that option group in a VerticalLayout and put this component in the popup then only small popup shadow is rendered in browser but no option group. Maybe someone has expierence with this and can help me? Here’s the code:

Server-side component:


package de.volkswagen.ais.va.admin.widgets.popupfield;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;

import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponentContainer;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;

/**
 * Server side component for the VPopupField widget.
 */
@com.vaadin.ui.ClientWidget(de.volkswagen.ais.va.admin.widgets.popupfield.client.ui.VPopupField.class)
public class PopupField extends AbstractComponentContainer {

	private static final long serialVersionUID = -1147985750788136342L;

	private Component popupComponent;

	public PopupField() {
		super();
	}

	protected PopupField(Component popupComponent) {
		this();

		this.popupComponent = popupComponent;
	}

	protected Component getPopupComponent() {
		return popupComponent;
	}

	protected void setPopupComponent(Component popupComponent) {
		this.popupComponent = popupComponent;
		requestRepaint();
	}

	@Override
	public void paintContent(PaintTarget target) throws PaintException {
		super.paintContent(target);

		// Paint any component specific content by setting attributes
		// These attributes can be read in updateFromUIDL in the widget.

		if (popupComponent == null)
			throw new IllegalArgumentException("Popup component not set.");
		
		target.startTag("popupComponent");
		popupComponent.paint(target);
        target.endTag("popupComponent");
	}

	/**
	 * Receive and handle events and other variable changes from the client.
	 * 
	 * {@inheritDoc}
	 */
	@Override
	public void changeVariables(Object source, Map<String, Object> variables) {
		super.changeVariables(source, variables);

		// Variables set by the widget are returned in the "variables" map.

//		if (variables.containsKey("click")) {
//
//		}
	}

	/* implementation and overrides of AbstractComponentContainer */

	public void replaceComponent(Component oldComponent, Component newComponent) {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void addComponent(Component c) {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void removeComponent(Component c) {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void removeAllComponents() {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void moveComponentsFrom(ComponentContainer source) {
		throw new UnsupportedOperationException();
	}

	public Iterator<Component> getComponentIterator() {
		return popupComponent != null ? Arrays.asList(popupComponent)
				.iterator() : Collections.<Component> emptyIterator();
	}

}

Client-side:


package de.volkswagen.ais.va.admin.widgets.popupfield.client.ui;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
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.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Container;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.RenderInformation.Size;
import com.vaadin.terminal.gwt.client.RenderSpace;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.Util;
import com.vaadin.terminal.gwt.client.ui.VOverlay;

/**
 * Client side widget which communicates with the server. Messages from the
 * server are shown as HTML and mouse clicks are sent to the server.
 */
public class VPopupField extends Composite implements Container, Paintable,
		ClickHandler, Iterable<Widget> {

	/** Set the CSS class name to allow styling. */
	public static final String CLASSNAME = "v-popupfield";
	public static final String VFILTERSELECT_CLASSNAME = "v-filterselect";

	/**
	 * Represents the popup box with the selection options. Wraps a suggestion
	 * menu.
	 */
	protected class SelectionPopup extends VOverlay {

		private static final String Z_INDEX = "30000";

		private Paintable popupComponentPaintable = null;
		private Widget popupComponentWidget = null;
		private boolean hiding = false;

		public SelectionPopup() {
			super(true, false, true);

			setStyleName(VFILTERSELECT_CLASSNAME + "-suggestpopup");
			DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX);
			
			setAnimationEnabled(true);
		}

		public void updateFromUIDL(UIDL uidl) {
			Paintable newPopupComponent = client.getPaintable(uidl
					.getChildUIDL(0));

			if (newPopupComponent != popupComponentPaintable) {
				setWidget((Widget) newPopupComponent);

				popupComponentWidget = (Widget) newPopupComponent;
				popupComponentPaintable = newPopupComponent;
			}

			popupComponentPaintable
					.updateFromUIDL(uidl.getChildUIDL(0), client);
		}

		protected Size calculatePopupExtra() {
			Element pe = getElement();
			Element ipe = getContainerElement();

			// border + padding
			int width = Util.getRequiredWidth(pe) - Util.getRequiredWidth(ipe);
			int height = Util.getRequiredHeight(pe)
					- Util.getRequiredHeight(ipe);

			return new Size(width, height);
		}

		@Override
		protected void updateShadowSizeAndPosition() {
			super.updateShadowSizeAndPosition();
		}

		@Override
		public boolean remove(Widget w) {
			popupComponentPaintable = null;
			popupComponentWidget = null;

			return super.remove(w);
		}

		@Override
		public void show() {
			hiding = false;
			super.show();
		}

		@Override
		public void hide(boolean autoClosed) {
			hiding = true;
			super.hide(autoClosed);
		}

		/*
		 * 
		 * We need a hack make popup act as a child of VPopupView in Vaadin's
		 * component tree, but work in default GWT manner when closing or
		 * opening.
		 * 
		 * (non-Javadoc)
		 * 
		 * @see com.google.gwt.user.client.ui.Widget#getParent()
		 */
		@Override
		public Widget getParent() {
			if (!isAttached() || hiding) {
				return super.getParent();
			} else {
				return VPopupField.this;
			}
		}

		@Override
		protected void onDetach() {
			super.onDetach();
			hiding = false;
		}

	}

	/** The client side widget identifier */
	protected String paintableId;

	/** Reference to the server connection object. */
	protected ApplicationConnection client;

	/** Contained widgets in this composite */
	protected final TextBox textBox = new TextBox();
	protected final HTML popupOpener = new HTML();
	protected final FlowPanel panel = new FlowPanel();
	protected final SelectionPopup popup = new SelectionPopup();

	/**
	 * The constructor should first call super() to initialize the component and
	 * then handle any initialization relevant to Vaadin.
	 */
	public VPopupField() {
		textBox.setStyleName(VFILTERSELECT_CLASSNAME + "-input");
		textBox.setReadOnly(true);
		textBox.addClickHandler(this);

		popupOpener.sinkEvents(Event.ONMOUSEDOWN);
		popupOpener.setStyleName(VFILTERSELECT_CLASSNAME + "-button");
		popupOpener.addClickHandler(this);

		panel.add(textBox);
		panel.add(popupOpener);

		initWidget(panel);
		setStyleName(VFILTERSELECT_CLASSNAME);
	}

	/**
	 * Called whenever an update is received from the server
	 */
	public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
		// This call should be made first.
		// It handles sizes, captions, tooltips, etc. automatically.
		if (client.updateComponent(this, uidl, true)) {
			// If client.updateComponent returns true there has been no changes
			// and we do not need to update anything.
			return;
		}

		// Save reference to server connection object to be able to send
		// user interaction later
		this.client = client;

		// Save the client side identifier (paintable id) for the widget
		paintableId = uidl.getId();

		// Process attributes/variables from the server
		if (uidl.getChildCount() > 0) {
			UIDL popupUIDL = uidl.getChildUIDL(0);
			popup.updateFromUIDL(popupUIDL);
		}
	}

	/**
	 * Called when a native click event is fired.
	 * 
	 * @param event
	 *            the {@link ClickEvent} that was fired
	 */
	public void onClick(ClickEvent event) {
		DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
		showPopup(popup);
	}

	public void showPopup(final SelectionPopup popup) {
		popup.setVisible(false);
		popup.show();

		int left = getAbsoluteLeft();
		int top = textBox.getAbsoluteTop();
		top += textBox.getOffsetHeight();
		int offsetHeight = popup.getOffsetHeight();
		int offsetWidth = popup.getOffsetWidth();

		if (offsetHeight + top > Window.getClientHeight()
				+ Window.getScrollTop()) {
			// popup on top of input instead
			top = top - offsetHeight - getOffsetHeight();
			if (top < 0)
				top = 0;
		}

		if (offsetWidth + left > Window.getClientWidth()
				+ Window.getScrollLeft()) {
			left = getAbsoluteLeft() + getOffsetWidth()
					+ Window.getScrollLeft() - offsetWidth;
			if (left < 0)
				left = 0;
		}

		popup.setPopupPosition(left, top);
		popup.setVisible(true);
	}

	@Override
	protected void onDetach() {
		popup.hide();
		super.onDetach();
	}

	public RenderSpace getAllocatedSpace(Widget child) {
		Size popupExtra = popup.calculatePopupExtra();

		return new RenderSpace(RootPanel.get().getOffsetWidth()
				- popupExtra.getWidth(), RootPanel.get().getOffsetHeight()
				- popupExtra.getHeight());
	}

	public boolean hasChildComponent(Widget component) {
		if (popup.popupComponentWidget != null) {
			return popup.popupComponentWidget == component;
		} else {
			return false;
		}
	}

	public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
		popup.setWidget(newComponent);
		popup.popupComponentWidget = newComponent;
	}

	public boolean requestLayout(Set<Paintable> child) {
		popup.updateShadowSizeAndPosition();
		return true;
	}

	public void updateCaption(Paintable component, UIDL uidl) {
		// caption not supported at the moment, that means ignore
	}

	@Override
	public void onBrowserEvent(Event event) {
		super.onBrowserEvent(event);
		if (client != null) {
			client.handleTooltipEvent(event, this);
		}
	}

	public Iterator<Widget> iterator() {
		return new Iterator<Widget>() {

			int pos = 0;

			public boolean hasNext() {
				// There is a child widget only if next() has not been called.
				return (pos == 0);
			}

			public Widget next() {
				// Next can be called only once to return the popup.
				if (pos != 0) {
					throw new NoSuchElementException();
				}
				pos++;
				return popup;
			}

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

		};
	}
}

EDIT: I can provide the whole project if desired.

Thanks in advance for any help,
Steffen Harbich

Did you have any luck?