论坛首页 Web前端技术论坛

MVP For GWT 系列资料转载二:An MVP-compatible EnumListBox for GWT

浏览 1272 次
该帖已经被评为隐藏帖
作者 正文
   发表时间:2010-03-07  
GWT

源文转自:An MVP-compatible EnumListBox for GWT

 

A frequent request on the GWT and gwt-presenter forums is for a ListBox that implements HasValue like a TextBox. I recently needed one myself, and thought it would be especially cool if I could use it with a Java enum type like this:

public static enum Frequency {DAILY, WEEKLY, MONTHLY};

private ConstantsWithLookup enumLabels = GWT.create(EnumLabels.class);
private EnumListBox<Frequency> freqBox;

freqBox = new EnumListBox<Frequency>(Frequency.class, enumLabels);
 

In keeping with MVP philosophy, the presenter’s display interface only needs the HasValue type to get and set the selected value as well as add a ValueChangeHandler to respond to a new selection. Here as some relevant excerpts from a presenter that uses the EnumListBox:

public interface Display extends WidgetDisplay
{
	HasValue<Frequency> getFrequency();
}
...
protected void onFirstRequest()
{
	...
	display.getFrequency().addValueChangeHandler(new ValueChangeHandler<Frequency>()
	{
		@Override
		public void onValueChange(ValueChangeEvent<Frequency> event)
		{
			// Do something with the newly selected event.getValue()
			...
		}
	});
}

 

Here’s a straightforward implementation of an EnumListBox that implements HasValue. Thanks to the gwt-ent project for the original idea for this。

package com.roa.app.client.ui.widget;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.ConstantsWithLookup;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.ListBox;
import com.roa.common.client.util.EnumTranslator;

public class EnumListBox<T extends Enum<T>> extends ListBox implements HasValue<T>
{

	private final Class<T> clazzOfEnum;
	private boolean valueChangeHandlerInitialized;

	public EnumListBox(final Class<T> clazzOfEnum, final ConstantsWithLookup constants)
	{
		if (clazzOfEnum == null)
			throw new IllegalArgumentException("Enum class cannot be null");

		this.clazzOfEnum = clazzOfEnum;
		EnumTranslator enumTranslator = new EnumTranslator(constants);

		T[] values = clazzOfEnum.getEnumConstants();

		for (T value : values)
		{
//			this.addItem(constant.toString(), constant.name());
			this.addItem(enumTranslator.getText(value), value.name());
		}
	}

	public T getSelectedValue()
	{
		if (getSelectedIndex() >= 0)
		{
			String name = getValue(getSelectedIndex());

			T[] values = clazzOfEnum.getEnumConstants();
			for (T value : values)
			{
				if (value.name().equals(name))
					return value;
			}
		}

		return null;
	}

	public void setSelectedValue(T value)
	{
		T[] values = clazzOfEnum.getEnumConstants();
		for (int i = 0; i < values.length; i++)
		{
			if (values[i] == value)
			{
				this.setSelectedIndex(i);
				return;
			}
		}
		throw new IllegalArgumentException("No index found for value " + value.toString());
	}

	/*
	 * Methods to implement HasValue
	 */

	@Override
	public T getValue()
	{
		return this.getSelectedValue();
	}

	@Override
	public void setValue(T value)
	{
		this.setValue(value, false);
	}

	@Override
	public void setValue(T value, boolean fireEvents)
	{
		T oldValue = getValue();
		this.setSelectedValue(value);
		if (fireEvents)
		{
			ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
		}
	}

	@Override
	public HandlerRegistration addValueChangeHandler(ValueChangeHandler<T> handler)
	{
		// Initialization code
		if (!valueChangeHandlerInitialized)
		{
			valueChangeHandlerInitialized = true;
			super.addChangeHandler(new ChangeHandler()
			{
				public void onChange(ChangeEvent event)
				{
					ValueChangeEvent.fire(EnumListBox.this, getValue());
				}
			});
		}
		return addHandler(handler, ValueChangeEvent.getType());
	}

}

 

There’s really not much to it, just a little weirdness that always comes with generics. Notice that the constructor uses an EnumTranslator to populate the labels in the ListBox. This allows you to use a standard GWT ConstantsWithLookup inteface to supply localized text for the enum values instead of the constant names. ConstantsWithLookup is just like Constants, but with the important ability to find a value dynamically without invoking a method corresponding to the property name. Unfortunately, you still have to define a method for each value of the enum in your ConstantsWithLookup class, even though it’s never used directly. Here’s a sample interface:

public interface EnumLabels extends ConstantsWithLookup {
	// Enums
	String com_mypackage_MyClass_Frequency_DAILY();
	String com_mypackage_MyClass_Frequency_WEEKLY();
	String com_mypackage_MyClass_Frequency_MONTHLY();
	String com_mypackage_MyClass_Frequency_QUARTERLY();
	String com_mypackage_MyClass_Frequency_YEARLY();

 

And the corresponding default properties file EnumLabels.properties:

com_mypackage_MyClass_Frequency_DAILY=daily
com_mypackage_MyClass_Frequency_WEEKLY=weekly
com_mypackage_MyClass_Frequency_MONTHLY=monthly
com_mypackage_MyClass_Frequency_QUARTERLY=quarterly
com_mypackage_MyClass_Frequency_YEARLY=yearly

 

And finally, here’s my EnumTranslator:

package com.roa.common.client.util;

import com.google.gwt.i18n.client.ConstantsWithLookup;

/**
 * Does a properties file lookup to get text associated with an enum value
 * Property keys use the full class name with all dots and dollars
 * converted to underscores. Keys are case-sensitive and GWT requires a
 * method in the interface that extends ConstantsWithLookup, even though
 * the method is never called.
 *
 * Example:
 * String my_package_class_Frequency_DAILY();
 *
 * In the corresponding properties file:
 * my_package_class_Frequency_DAILY=daily
 *
 * @author David Chandler
 */
public class EnumTranslator
{
	private ConstantsWithLookup constants;

	public EnumTranslator(ConstantsWithLookup constants)
	{
		this.constants = constants;
	}

	public String getText(Enum e)
	{
		String lookupKey = e.getClass().getName() + "." + e.name();
		lookupKey = lookupKey.replace(".", "_");
		lookupKey = lookupKey.replace("$", "_");
		return constants.getString(lookupKey);
	}
}

 

This EnumListBox is a fairly hard-wired kind of ListBox. In the near future, I anticipate refactoring along these lines:

 

Add a constructor that takes any java.util.List, not just an Enum.

Create an interface HasSelectedValue that extends HasValue by adding a populateAllSelections() method. This would allow the available selections to come from the presenter through the Display interface and is thus even better for MVP. Versions of the new method could also take a java.util.List or Enum and would replace the constructor.

Ditto for a HasSelectedValues interface to deal with multiple-select type ListBoxes.

Stay tuned.

论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics