Monday, February 18, 2013

GWT SuggestBox backed by POJOs

The other day I found an occasion to make a more sophisticated use of GWT's SuggestBox. Not only did I intend to use the "suggestion feature" based on a POJO's attribute, I also wanted to
  • fill more widgets with other attribute values of the POJO the suggestion was based on
  • present the data of the other attributes by showing them to the user beforehand; ideally as part of the suggestion itself
So, the user should see something like this
and after selection, values should be inserted like that
Although the basic idea of doing so was thought of by Google's engineers, there is no clear path given to ultimately get it done. I found a working example of how to achieve something similar over at the PermGen error blog. Kudos to the author over there - he certainly got me kickstarted. However, I found it awkward to manage the text matching "myself", especially as I liked the feature set of the MultiWordSuggestOracle (and wanted to offer the user the possibility to match the brand also); so I looked for a way to extend the latter.

Define our own Suggestion

We start easy by following the recommendation to implement our own Suggestion class:
public class ArticleSuggestion extends MultiWordSuggestion {
 private final Article article;

 public ArticleSuggestion(Article article, String displayString) {
  super(article.getName(), displayString);
  this.article = article;
 }

 public Article getArticle() {
  return this.article;
 }
}

Extending MultiWordSuggestOracle

This one's a bit tricky. Unfortunately, there are no hooks or plugin-points in that class with which to easily provide your own logic. Also, basically all of the text matching (the reason that made me want to extend it) is defined in private methods. So I ended up with a somewhat blunt approach - which obviously works for me, and I assume works for most scenarios as well.
public class ArticleSuggestOracle extends MultiWordSuggestOracle {
 private final Map suggestionMap = new HashMap();

 public void add(Article article) {
  String suggestion = article.toSuggestion();
  this.suggestionMap.put(suggestion, article);
  super.add(suggestion);
 }

 public void requestSuggestions(Request request, final Callback callback) {
  super.requestSuggestions(request, new Callback() {
   @Override
   public void onSuggestionsReady(Request request, Response response) {
    callback.onSuggestionsReady(request, modifyResponse(response));
   }

  });
 }

 private Response modifyResponse(Response response) {
  ArrayList articleSuggestions = new ArrayList();
  for (Suggestion originalSuggestion : response.getSuggestions()) {
   MultiWordSuggestion suggestion = (MultiWordSuggestion) originalSuggestion;
   Article article = this.suggestionMap.get(suggestion.getReplacementString());
   articleSuggestions.add(new ArticleSuggestion(article, suggestion.getDisplayString()));
  }
  response.setSuggestions(articleSuggestions);
  return response;
 }
}

Reacting on a selected suggestion

Now, set up a SuggestBox using the ArticleSuggestOracle, and assign a SelectionHandler just like that:
articleSuggestBox.addSelectionHandler(new SelectionHandler() {
 @Override
 public void onSelection(SelectionEvent event) {
  ArticleSuggestion suggestion = (ArticleSuggestion) event.getSelectedItem();
  Article suggestedArticle = suggestion.getArticle();
  handleSuggestedArticle(suggestedArticle);
 }
});
The power of the MultiWordSuggestOracle awaits you!