Styled items within a TwinColSelect or ListSelect

Hey everyone,

I was wondering whether it’s possible to style items of a ListSelect or TwinColSelect differently. To be more precise, I use either of those formfields within a FormFieldFactory to display a list of custom objects. Based on one of the objects properties (a boolean property), I would like to display the items in a grayish color or italic or whatever. Even an icon or something else to mark these entries would be Ok for me.

As an output I would like to have something like the following. The customclass should be set depending on the boolean property.


<select>
<option class="customclass">...</option>
<option>...</option>
<option>...</option>
<option class="customclass">...</option>
</select>

Another idea would be to use the new OptionGroup with HTML-Labels. But I would prefer one of the list widgets.

This is my usage of the TwinColSelect if it is of any help:


TwinColSelect twinSelect = new TwinColSelect();
twinSelect.setLeftColumnCaption(Messages.getString("Available")); 
twinSelect.setRightColumnCaption(Messages.getString("Assigned"));
BeanItemContainer<IProcessingTarget> container = new BeanItemContainer<IProcessingTarget>(IProcessingTarget.class, targets);
twinSelect.setContainerDataSource(container);
twinSelect.setItemCaptionPropertyId(Constants.PROPERTY_NAME_WITH_TYPE);
twinSelect.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);

cheers and thx in advance,

Sven

In the last few days, I tried to achieve this by subclassing the Vaadin widgets. In the end I got an result, which was Ok, but not 100% perfect. I managed to create a stylable ListSelect, but not an stylable TwinColSelect. The problem with the TwinColSelect was, that the class attribute disappeared, everytime an item was moved from one list to another. So in the end I switched to an OptionGroup with Icons.

I just want to document my efforts here, in case they are helpful for someone else. I’ll show how to create a stylable ListSelect (i’m passing on the TwinColSelect due to the problems described above).

First I installed and used the Vaadin Eclipse plugin to create a new widget. As a base class I chose ListSelect and named my new class StylableListSelect. I named the client widget VStylableListSelect and extended VListSelect.


/**
 * Server side component for the VStylableListSelect widget.
 * Add the feature of adding a class attribute to option-elements, 
 * so they can be styled via CSS.
 * 
 * Name of class attribute is set via {@link #setItemClassPropertyId(Object)} allowing
 * to define different class per item.
 */
@com.vaadin.ui.ClientWidget(com.subshell.feedbroker.ui.widgets.client.ui.VStylableListSelect.class)
public class StylableListSelect extends ListSelect {

	private Object itemClassPropertyId;

	public void setItemClassPropertyId(Object propertyId) {
		itemClassPropertyId = propertyId;
		requestRepaint();
	}
	
	public Object getItemClassPropertyId() {
		return itemClassPropertyId;
	}
	
	public String getItemClass(Object itemId) {
		String itemClass = "";
		final Property p = getContainerProperty(itemId, itemClassPropertyId);
		if (p != null) {
            itemClass = p.toString();
        }
		return itemClass;
	}

	@Override
	protected void paintItem(PaintTarget target, Object itemId) throws PaintException {
		super.paintItem(target, itemId);
		if (itemClassPropertyId != null) {
			String itemClass = getItemClass(itemId);
			target.addAttribute("itemClass", itemClass);
		}
	}

}

The server side of the widget is pretty straight forward. It adds methods for setting and getting a property name, which can be used as a class attribute for the option element. This way it is possible to add different class-attributes depending on a property of the added Container-Bean. The new widget would be somehow called like that (assuming the beans within the container have methods getItemName, and getItemState - both returning a String):


StylableListSelect styleableList = new StylableListSelect();
styleableList.setContainerDataSource(container);
styleableList.setItemCaptionPropertyId("itemName");
styleableList.setItemClassPropertyId("itemState");

The client side widget is a bit more complicated.


public class VStylableListSelect extends VListSelect {
	
	@Override
	protected void buildOptions(UIDL uidl) {
		super.buildOptions(uidl);
		for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
            final UIDL optionUidl = (UIDL) i.next();
            String itemClassAttribute = optionUidl.getStringAttribute("itemClass");
            String itemValue = optionUidl.getStringAttribute("key");
            if (itemClassAttribute != null && itemClassAttribute.length() > 0) {
            	OptionElement optionElement = getOptionElementByKey(itemValue);
            	if (optionElement != null) {
                        optionElement.addClassName(itemClassAttribute);
            	}
            }
		}
	}
	
	private OptionElement getOptionElementByKey(String key) {
		Element element = getElement();
		NodeList<Element> childNodes = element.getElementsByTagName("option");
		for (int i = 0; i < childNodes.getLength(); i++) {
			Element current = childNodes.getItem(i);
			String valueAttribute = current.getAttribute("value");
			if (current instanceof OptionElement && valueAttribute.equals(key)) {
				return (OptionElement) current;
			}
		}
		return null;
	}

}

So basically after “buildOptions” of the super class was called, every “key” and previously set class attribute ('item) is retrieved from the UIDL object. Afterwards for every uidl object, representing one option element, the matching HTML option element is searched within the elements childnodes by using the key (see method getOptionElementByKey). When the correct option element was found, the class attribute can be set using the method addClassName.

I have to admit, that this is a bit hacky, but as i figured out, there is no other easy way to modify the option element. The main reason for this (and also the reason why this is not working for the TwinColSelect) is that the ListBox, which manages the single items and therefore the option elements, is hidden as package protected (in case of the ListSelect) and private (in case of the TwinColSelect). In my opinion the correct way to create a stylable list widget would be to extend or create a new ListBox and set it to the ListSelect or TwinColSelect widget. Unfortunately this is not possible at the moment.

And btw: I experienced some problems with the Vaadin Eclipse plugin. The “New Widget” wizard showed an error message “no widgetset in project” and did not fill out any form fields by default, when calling the wizard from the “Java Package” view within the “Java Perspective”). I had to change to the Java EE perspective and call it from the “Project Explorer” to get it to work properly.

Hope this helps :slight_smile:

cheers,
Sven