mercredi 28 mars 2012

Editor-compatible EnumListBox for GWT

I remember reading a great article form Uncle Bob about enums. Bottom line was, enums are cool, but where do we use their awesomeness ? It's true that more often than not, I use enums like I would use String constants (but it feels better anyway).
Finally, I have found a context where enums completely rock, without question.

First, I have to admit something. I don't like String manipulation. So when I see an entity with a String-typed field that has a finite number of possible values, I instantly refactor it into an enum. The problem is that at some point, I always have to convert my enum into a String, or parse a String to get my enum back. And that sucks. It makes me feel bad, I just don't want to do that anymore.
So here it is, my solution to keep my typed enums all the way from entity to UI widgets. And it does it so transparently it feels like magic.

Before going further, let me introduce my stack :
GWT with Editor framework & RequestFactory
GAE with Objectify
Even if you don't use that specific stack, you might be able to use the same solution, or at least get some inspiration, so keep on reading !

First, let me describe the context. You develop a GWT application, and your domain entities have enum typed fields. Your proxies/DTOs have enum typed fields. But when it comes to the UI, the ListBox widget only handles Plain Old Dirty String, and there you are going from enum to String, and back from String to enum. Wouldn't it be cool to have something smarter, like an EnumListBox ?

Let's take for example the issue tracker of BitBucket :

We could have an enum listing all those possible values :
public enum Priority {

 TRIVIAL, MINOR, MAJOR, CRITICAL, BLOCKER;

 public String toString() {
  return name().toLowerCase();
 };

}
So, how do we create a ListBox to display those values ? On his blog, David Chandler demonstrates the use of an EnumListBox, however it is not Editor framework compatible. Here's my implementation of an EnumListBox :
public class EnumListBox<E extends Enum<E>> extends ValueListBox<E> {

 public EnumListBox() {
  super(new EnumRenderer<E>(), new EnumKeyProvider<E>());
 }

 public EnumListBox(Class<E> clazz) {
  this();
  setAcceptableValues(Arrays.asList(clazz.getEnumConstants()));
 }

}
We're extending ValueListBox, which implements the IsEditor interface. The second constructor is for commodity, just pass in your enum class and it will initialize itself with the enum values (we'll see an example later). If you want your ListBox to list a subset only of the enum, you can use the default constructor and call setAcceptableValues() yourself.
You'll also need the EnumRenderer and EnumKeyProvider classes :
public class EnumRenderer<E extends Enum<E>> implements Renderer<E> {

 @Override
 public String render(E object) {
  if (object == null)
   return "";
  return object.toString();
 }

 @Override
 public void render(E object, Appendable appendable) throws IOException {
  appendable.append(render(object));
 }

}
On line 6, you can see that I chose to display an empty String when no value is selected. That might be the most significant change you want to make.
public class EnumKeyProvider<E extends Enum<E>> implements ProvidesKey<E> {

 @Override
 public Object getKey(E item) {
  if (item == null)
   return null;
  return item.name();
 }

}
Now that you got your EnumListBox, you must be eager to use it. Remember our example, we want to select the priority of an issue. So in our IssueEditor :
public class IssueEditor extends Composite implements Editor<IssueProxy> {

 @UiField(provided = true) EnumListBox<Priority> priority = new EnumListBox<Priority>(Priority.class);

}
It does make a long verbose line, I wish I could do better. But that's all you need, and it works with any enum.

To continue with our example with RequestFactory, let's show our IssueProxy : 
@ProxyFor(value = Issue.class)
public interface IssueProxy extends EntityProxy {
 
 Priority getPriority();

 void setPriority(Priority priority);

}
Finally, the domain entity :
public class Issue {
 
 @NotNull private Priority priority;

 public Priority getPriority() {
  return priority;
 }

 public void setPriority(Priority priority) {
  this.priority = priority;
 }

}
I hope you got the point by now. As you see, the wiring is completely automatic, thanks to Editor + RequestFactory magic. It is as simple as it can ever get, we have almost attained the highest possible level of abstraction.