Thursday, July 9, 2009

GWT: Useable Closeable Scrollable DialogBox

The GWT DialogBox is barely useable beyond demonstration-level GWT examples. It does not have a close button at the corner of its caption bar.

A usable, closeable and scrollable dialog box is available from
http://code.google.com/p/synthfuljava/ .

ScrolledDialogBox Patch
This class ScrolledDialogBox is found as
http://code.google.com/p/synthfuljava/source/browse/trunk/gwt/widgets/com/google/gwt/user/client/ui/ScrolledDialogBox.java .

Being a patch, it retains the com.google.gwt.user.client.ui namespace.

DialogBox over-rides the onBrowserEvent method and replaces it with a block of obtuse logic - to prevent the text in the caption bar from being selected while the mouse is grabbing the caption to move the DialogBox around the window. A prevention scheme that does not work very well on the hosted browser, evidently. This obtuse logic is the very reason why buttons placed on the caption bar will not react appropriately to mouse click events - consequently, you cannot hide/close the DialogBox through any button placed on the caption bar.

Therefore, either we delve into the convoluted GWT event firing scheme or invent our own mini event scheme and insert it into onBrowserEvent method of DialogBox. The scheme is defined by the interface CloserEventHandler, where only three mouse events are recognised
  • onClick - to close the dialog by clicking on the close button
  • onMouseOver - to change the colour of the close button
  • onMouseOut - to change the close button back to its original colour.
In ScrolledDialogBox.java is declared an ArrayList CloserEventHandlers. This ArrayList is to allow CloserEventHandler instances to be stacked using its add() method.

And CloserHandler embedded class implements CloserEventHandler, which is added onto the CloserEventHandlers event stack, which the new onBrowserEvent paces through to execute each registered handler.

Subclassing rather than Patching
The initial tendency to solve the problem, besides waiting for Google to release a better DialogBox (waiting and looking over the wall till your neck stretches as long as a giraffe's), is to subclass DialogBox to provide the desired effect of having a closer button at the caption bar. As in
http://code.google.com/p/synthfuljava/source/browse/trunk/gwt/widgets/org/synthful/gwt/widgets/client/ui/UnusableScrollableDialogBox.java .

However, this class has a disclaimer - it is not useable, as explained within the javadoc sections, but retained as an example why over-riding this way would not work, because DialogBox's caption acknowledges DialogBox as a parent, but DialogBox does not acknowledge its caption as a child:
  1. In DialogBox, the caption bar comprises one widget alone - an HTML widget implementing the DialogBox.Caption interface.
  2. We need to replace the caption bar with a HorizontalPanel. On the HorizontalPanel, we should place the caption widget and on the right, the closer button.
  3. We should extract the caption widget and reuse it by pulling it out of the caption bar and reinserting it as the first widget of the HorizontalPanel.
  4. However DialogBox did not add caption widget as a widget but rather by adopting (using the adopt() method) it as an element. The consequence is, caption registers DialogBox as a parent but, DialogBox does not register caption widget as a child widget.
  5. To yank caption widget out of DialogBox, we would use caption.removeParent() method. However, Widget removeParent() method first detours to let the parent check if it has the widget as a child widget - if not, the parent's failure to relate to the widget as a child causes the remove operation to abort without the widget having a chance to nullify the dead-beat parent from its private parent variable. Since a widget's parent is stored as a private variable, we muggles would not be able to access it to nullify it.
  6. Well, since the caption widget still acknowledges its dead-beat parent as a parent, the add operation would not work. That is HorizontalPanel cannot add caption widget as its child widget because add() method would first check if the widget still has a parent.
  7. Therefore the caption widget is a victim of a widget's unrequited loyalty to its dead-beat parent.

Therefore the solution is to
  • Orphan the original caption widget.
  • Instantiate an HTML widget as the new caption widget.
  • Over-ride the get/set Text, Caption & Html methods. Unfortunately, the getCaption() method is final and cannot be over-riden.
  • Instantiate a HorizontalPanel and set it as the new caption panel.
  • Add the new caption widget as first child widget of HorizontalPanel.
  • Instantiate a Button and add it to HorizontalPanel as its second widget.
  • Conjure the mini event scheme.
This exercise can be aided by merging some codes for ScrolledDialogBox into ScrollableDialogBox. At the end of the exercise, you would discover that you had replaced three quarters of the super class's code - might as well write a patch rather than a subclass.

The subclassing code is found as
http://code.google.com/p/synthfuljava/source/browse/trunk/gwt/widgets/org/synthful/gwt/widgets/client/ui/ScrollableDialogBox.java .

Disclaimer: The ScrolledDialogBox and ScrollableDialogBox classes need further refinement.
  • The position of the X button needs to be properly calculated
  • The event scheme of the X button accounts only for onClick, onMouseOver and onMouseOut.


7 comments: