TurboManage

David Chandler's Journal of Java Web and Mobile Development

  • David M. Chandler


    Web app developer since 1994 and Google Cloud Platform Instructor now residing in Colorado. Besides tech, I enjoy landscape photography and share my work at ColoradoPhoto.gallery.

  • Subscribe

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 223 other followers

  • Sleepless Nights…

    April 2010
    S M T W T F S
     123
    45678910
    11121314151617
    18192021222324
    252627282930  
  • Blog Stats

    • 1,029,259 hits

Archive for April, 2010

AppEngine + GWT at GTUG Atlanta Apr 27

Posted by David Chandler on April 24, 2010

I’ll be presenting a short case study on an application using AppEngine and GWT at next week’s Google Technology User Group in Atlanta. If you’re in the area, drop in and say hello. GTUGs meet all over the world and are a great way to meet Google enthusiasts and engineers.

Posted in Uncategorized | Leave a Comment »

GWT compiler options

Posted by David Chandler on April 14, 2010

There doesn’t appear to be much documentation on GWT compiler options, and what there is refers to an old version (1.6). Fortunately, you can always invoke the compiler on the command line to see a list of options. If you’ve installed GWT using the Google plugin for Eclipse, you’ll find GWT_HOME in your eclipse/plugins directory.

C:\...\gwt-2.0.3>java -cp gwt-dev.jar com.google.gwt.dev.Compiler
Missing required argument 'module[s]'
Google Web Toolkit 2.0.3
Compiler [-logLevel level] [-workDir dir] [-gen dir] [-style style] [-ea] [-XdisableClassMetadata] [-XdisableCastChecking] [-validateOnly] [-draftCompile] [-compileReport] [-localWorkers count] [-war dir] [-extra dir] module[s]

where
  -logLevel               The level of logging detail: ERROR, WARN, INFO, TRACE, DEBUG, SPAM, or ALL
  -workDir                The compiler's working directory for internal use (must be writeable; defaults to a system tem
p dir)
  -gen                    Debugging: causes normally-transient generated types to be saved in the specified directory
  -style                  Script output style: OBF[USCATED], PRETTY, or DETAILED (defaults to OBF)
  -ea                     Debugging: causes the compiled output to check assert statements
  -XdisableClassMetadata  EXPERIMENTAL: Disables some java.lang.Class methods (e.g. getName())
  -XdisableCastChecking   EXPERIMENTAL: Disables run-time checking of cast operations
  -validateOnly           Validate all source code, but do not compile
  -draftCompile           Enable faster, but less-optimized, compilations
  -compileReport          Create a compile report that tells the Story of Your Compile
  -localWorkers           The number of local workers to use when compiling permutations
  -war                    The directory into which deployable output files will be written (defaults to 'war')
  -extra                  The directory into which extra files, not intended for deployment, will be written
and
  module[s]               Specifies the name(s) of the module(s) to compile

To pass one of these options to the GWT compiler within Eclipse, click Advanced in the GWT Compile dialog.

I was particularly interested in the -gen option, which outputs the Java code emitted by GWT generators used by projects such as gwittir and GWTP to somewhat simplify GWT development using MVP and code splitting. More on this forthcoming…

If you haven’t tried it yet, definitely have a look at the -compileReport option also. It generates an HTML report showing the “story of your compile,” including the size of each package and any split points. You’ll find the report in your project’s build directory under extras/project_name/soycReport/compile-report/index.html.

Posted in Google Web Toolkit | 11 Comments »

SelectOneListBox for use with GWT+MVP

Posted by David Chandler on April 1, 2010

According to the Model-View-Presenter approach to GWT development, presenters should not know about specific Widgets in views, but rather call methods on interfaces like HasValue and HasClickHandlers. In practice, this works well with relatively simple widgets like TextBox whose behavior can be described to the presenter in terms of a single interface such as HasValue. However, GWT doesn’t yet provide suitable interfaces for all Widgets. One such example is the ListBox, which implements only HasChangeHandlers and HasName. Wouldn’t it be nice if there were a HasValue equivalent for ListBox that would let you get and set the selected value as well as populate the list?

Here’s my idea for such an interface:

package com.turbomanage.gwt.client.ui.widget;

import java.util.Collection;

import com.google.gwt.user.client.ui.HasValue;

/**
 * MVP-friendly interface for use with any widget that can be populated
 * with a Collection of items, one of which may be selected 
 * 
 * @author David Chandler
 *
 * @param <T>
 */
public interface HasSelectedValue<T> extends HasValue<T>
{
	void setSelections(Collection<T> selections);

	void setSelectedValue(T selected);
	
	T getSelectedValue();
}

In retrospect, the method name setSelections may be a bit confusing. It refers to all available options (perhaps setOptions instead?), not the one selected value.

And here’s a SelectOneListBox widget that implements the interface (astute readers may note a nod to JSF in the name…). It extends GWT’s ListBox, which simplifies implementation of the methods related to HasValue.

package com.turbomanage.gwt.client.ui.widget;

import java.util.ArrayList;
import java.util.Collection;

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.user.client.ui.ListBox;

/**
 * A ListBox that can be populated with any Collection
 * 
 * Published under Apache License v2
 * 
 * @author David Chandler
 * @param <T>
 */
public class SelectOneListBox<T> extends ListBox implements HasSelectedValue<T>
{

	public interface OptionFormatter<T>
	{
		abstract String getLabel(T option);
		abstract String getValue(T option);
	}
	
	private boolean valueChangeHandlerInitialized;
	private T[] options;
	private OptionFormatter<T> formatter;

	public SelectOneListBox(Collection<T> selections, OptionFormatter<T> formatter)
	{
		setSelections(selections);
		setFormatter(formatter);
	}

	public SelectOneListBox(OptionFormatter<T> formatter)
	{
		this(new ArrayList<T>(), formatter);
	}

	public void setFormatter(OptionFormatter<T> formatter)
	{
		this.formatter = formatter; 
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setSelections(Collection<T> selections)
	{
		// Remove prior options
		if (options != null)
		{
			int numItems = this.getItemCount();
			int firstOption = numItems - options.length;
			for (int i=firstOption; i<numItems; i++)
				this.removeItem(firstOption);
		}
		options = (T[]) selections.toArray();
		for (T option : options)
		{
			String optionLabel = formatter.getLabel(option);
			String optionValue = formatter.getValue(option);
			this.addItem(optionLabel, optionValue);
		}
	}

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

			for (T option : options)
			{
				if (formatter.getValue(option).equals(name))
					return option;
			}
		}

		return null;
	}

	@Override
	public void setSelectedValue(T value)
	{
		if (value == null)
			return;
		
		for (int i=0; i < this.getItemCount(); i++)
		{
			String thisLabel = this.getItemText(i);
			if (formatter.getLabel(value).equals(thisLabel))
			{
				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(SelectOneListBox.this, getValue());
				}
			});
		}
		return addHandler(handler, ValueChangeEvent.getType());
	}

}

The SelectOneListBox constructor takes any collection and an OptionsFormatter which tells it how to get the label and value associated with each item in the collection. Here’s an example of its use:

		selectPrayerList = new SelectOneListBox<PrayerList>(new OptionFormatter<PrayerList>()
		{
			@Override
			public String getLabel(PrayerList option)
			{
				return option.getName();
			}

			@Override
			public String getValue(PrayerList option)
			{
				return option.getId().toString();
			}
		});

And finally, an example of populating the SelectOneListBox in a presenter:

HasSelectedValue<PrayerList> getPrayerListFilter();
...
display.getPrayerListFilter().setSelections(result.getPrayerLists());

Because HasSelectedValue extends GWT’s HasValue, you can also add ValueChangeHandlers in the presenter like this:

		display.getPrayerListsBox().addValueChangeHandler(new ValueChangeHandler<PrayerList>()
		{
			@Override
			public void onValueChange(ValueChangeEvent<PrayerList> event)
			{
				... event.getValue() ...
			}
		});

Now, the problem always comes up (and this is probably the reason that GWT’s ListBox doesn’t implement a HasValue-like interface already), how do you add to the list things which are not model items, that is, not in the Collection? A common example is a “Select one” or “New…” prompt to appear in the list box. This is not a problem if you’re using ListBox directly, as you can add any String with addItem(). But it is a problem for SelectOneListBox. Because it implements HasSelectedValue, all options must be of type T. So you can do one of four things:

  1. Hard-code your presenter to ListBox instead of an interface (very unsatisfying)
  2. Register ValueChangeHandlers in the view and let the view call a method on the presenter (bi-directional MVP)
  3. Punt and make a fake model item representing your prompt
  4. Don’t put such items in list boxes. It confuses screen readers, anyway.

I think #4 is actually the most correct answer and fight for it wherever I can. But when screen real estate is precious, I’m willing to break the rules a little. Since I’m using gwt-presenter, #2 is not an option, which leaves #3. It offends my sensibilities, but it’s quite easy and it works. Here’s an example:

				ArrayList<PrayerList> prayerLists = new ArrayList<PrayerList>();
				// -1 represents an ID that will never be used in a real PrayerList
				prayerLists.add(new PrayerList(-1, "Select one"));
				prayerLists.addAll(result.getLists());
				display.getPrayerListFilter().setSelections(prayerLists);

While we’re at it, we can even add a little bit of CSS to style our fake -1 object with a line underneath to act as a menu separator:

/* Select one */
option[value="-1"] {
	border-bottom: 1px solid black;
	padding-bottom: 2px;
}

To me, this little hackery is worth the convenience and correctness of HasSelectedValue.

HasSelectedValues (plural) and SelectManyListBox are left as an exercise to the reader (for now, anyway).

Enjoy!

Posted in Google Web Toolkit, Model-View-Presenter | 15 Comments »

 
%d bloggers like this: