Monday, January 25, 2010

Composite Event Handler Registrations in GWT

In my previous entry, I wrote up a class for displaying Input Prompts in GWT. As I started to fold that code into my project, I realized that I didn't expose the handler registrations, which would make it impossible to remove the event handlers if and when the text fields for which input prompts were displayed were created and removed during the lifecycle of the application.

Because the Input Prompt registers handlers for both Blur and Focus, there are two registrations. It's not easy to return two values from a single method, and frankly, I don't think a class using InputPrompt should have to know or care what events it's employing in great detail. As a result, I've created a composite event handler registration to return:


package com.codiform.gwt.event;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.event.shared.HandlerRegistration;

public class CompositeHandlerRegistration implements HandlerRegistration {

private List registrations;

public CompositeHandlerRegistration() {
registrations = new ArrayList();
}

void add( HandlerRegistration registration ) {
if( registration instanceof CompositeHandlerRegistration ) {
CompositeHandlerRegistration composite = (CompositeHandlerRegistration) registration;
registrations.addAll( composite.getRegistrations() );
composite.clear();
} else {
registrations.add( registration );
}
}

private List getRegistrations() {
return registrations;
}

public void removeHandler() {
if ( registrations.size() > 0 ) {
for ( HandlerRegistration item : registrations ) {
item.removeHandler();
}
clear();
} else {
throw new IllegalStateException( "Composite handler registration is currently empty, and cannot remove handlers." );
}
}

private void clear() {
registrations.clear();
}

}


If a composite handler registration is passed to another composite handler registration, I flatten them; this might be unnecessary. In the spirit of YAGNI, I won't be at all unhappy if you decide you don't need that capability. I also decided I preferred to clear my local references to any inner handler registrations as soon as they've been removed, rather than hanging on to them indefinitely.

All in all, this is pretty simple GWT code, but seemed worth following up on the previous entry to talk about the need for handler registrations.

Friday, January 22, 2010

Input Prompt Pattern in GWT

For a GWT project I'm working on, I've reached a point where I wanted to apply input prompts to a text box; this doesn't seem to be something that's built in to the basic GWT framework, or an easily-located extension. For that matter, I couldn't find anyone who'd done it and blogged about it, although it might be that they've used different terminology.

I thought it was worth a quick experiment to see how easy they would be to apply, and this is what I came up with:


package com.codiform.gwt.widget;

import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;

public class InputPrompt implements BlurHandler, FocusHandler {

private String promptText;

public InputPrompt( String text, TextBox... inputs ) {
this.promptText = text;
for( TextBox item : inputs ) {
apply( item );
}
}

public void apply( TextBox input ) {
input.addBlurHandler( this );
input.addFocusHandler( this );
applyPrompt( input );
}

public void onBlur( BlurEvent event ) {
TextBox blurred = (TextBox) event.getSource();
applyPrompt( blurred );
}

private void applyPrompt( TextBox input ) {
if( input.getText().isEmpty() ) {
input.setText( promptText );
input.addStyleName( "inputPrompt" );
}
}

public void onFocus( FocusEvent event ) {
TextBox focused = (TextBox) event.getSource();
if( promptText.equals( focused.getText() ) ) {
focused.setText( "" );
focused.removeStyleName( "inputPrompt" );
}
}
}


The final version has some project-specific tweaks (interface for the textbox to make this code testable with mocks, a style name in the project namespace), but for the most part the above code seems to do the trick well and I'll be applying it shortly.