Wiki

Clone wiki

javarosa / NewWidget

New Chatterbox Widgets

The Chatterbox is our Polish-based interface for filling out forms. It presents a sequential stack of frames, where each frame corresponds to one question. (In rare cases, a frame may represent more than one question / a compound datatype, but this is out of the scope of this tutorial.) Each frame displays in one of two modes: ''expanded mode'' when the question is active and being answered, and ''collapsed mode'' when the question has already been answered. Collapsed mode is not editable, and typically displays an abridged version of the question prompt and answer. Expanded mode displays the full question prompt, and a large UI widget to be used for answering the question.

Each 'question frame' is a ChatterboxWidget (package: org.javarosa.formmanager.view.chatterbox.widget, project: org.javarosa.polish.chatterbox). If you add new datatypes to JavaRosa, you will need to create new ChatterboxWidgets that are capable of handling them. That's what this tutorial will tell you how to do.

ChatterboxWidget

ChatterboxWidget is a GUI component (it extends Item) that is hosted directly in the Chatterbox's Frame. It is basically a shell class that takes care of managing the transitions between expanded and collapsed mode. The 'meat' of the ChatterboxWidget is actually provided by two other objects (using the component design pattern) that handle the details of expanded and collapsed mode, respectively. You configure a new ChatterboxWidget with these components when you create it. Therefore, you don't subclass ChatterboxWidget directly.

Example:

ChatterboxWidget myWidget = new ChatterboxWidget(chatterbox, question, form, ChatterboxWidget.VIEW_EXPANDED,
                                                 new CollapsedWidget(), new TextEntryWidget());
  • 'chatterbox' is a reference to the parent Chatterbox hosting this widget
  • 'question' is a reference to the QuestionDef this widget represents
  • 'form' is a reference to the FormDef containing 'question'
  • the 4th parameter is the initial state of the widget (expanded or collapsed)
  • the 5th parameter is the component that governs collapsed mode
  • the 6th parameter is the component that governs expanded mode

Expanded and Collapsed Mode Components

The behavior of the widget in expanded and collapsed mode is determined by component objects. Two separate objects are used for expanded and collapsed mode. The reason for this separation is that most question types will have the same collapsed mode, while their expanded modes may be very different. In fact, all standard ChatterboxWidgets so far use the same collapsed mode.

A collapsed-mode component must implement IWidgetStyle. An expanded-mode component must implement IWidgetStyleEditable (which extends IWidgetStyle).

IWidgetStyle

This interface controls the behavior of the widget in both expanded and collapsed mode. In general, these methods are called upon certain events (init, refresh, etc.) and are passed a reference to the parent ChatterboxWidget, treating it as a Container GUI object. These methods then modify the container appropriately to update the GUI. The component generally needs to keep some memory of what it did to the Container (typically by keeping references to the GUI components it added) as it needs to keep the widget updated as data changes.

void initWidget (QuestionDef question, Container c)

This method initializes the GUI structure of the widget -- adding GUI components and such -- while ignoring the question's current locale and data value. For example, in collapsed mode we will have a two text labels: one to display the question prompt, and one to display its answer. In initWidget(), we should create the two text labels and add them to the container, but we would not initialize them with any data. Filling in the data takes place in refreshWidget(). Any call to initWidget is guaranteed to be followed by a call to refreshWidget(). The reason for this separation is because refreshWidget() may be called many times over the life of the widget (whenever the locale is changed, for example), so to do the same kind of work in initWidget would be redundant.

Remember that this method needs to save off any references to GUI objects that it created, so it can use them later to due updates in refreshWidget(). If you need to change the high-level structure of the widget based on circumstances (rather than just changing the data in pre-defined fields), you can save off a reference to the Container 'c' itself.

void refreshWidget (QuestionDef question, IAnswerData data, int changeFlags)

This method is called when you need to refresh the widget in response to some change, like a locale change, or a change to the question's answer. It will also always be called immediately after initWidget().

Typically all the GUI set-up will have already been performed in initWidget(). Here you just change the values in those existing GUI objects accordingly.

void reset ()

This is called to erase all the saved state (like GUI references) associated with a widget. If the widget is to be used again, initWidget() is guaranteed to be called first.

widgetType()

This returns a unique numeric code used to identify a custom widget (custom widgets can be registered with the Chatterbox at runtime). (I think? Need to do anything here for standard (non-custom) widget types?)

IWidgetStyleEditable

In addition to implementing all of IWidgetStyle, an expanded-mode component needs to implement a few more methods because of its interactive nature. These methods are in the interface IWidgetStyleEditable.

IAnswerData getData ()

This methods returns the result of answering the question -- the value inside your interactive widget. It must return the data in the datatype appropriate to the question this widget represents. (If this widget represents a question or concept that is not meant to collect input, return null.) How to handle 'null' data is an issue left to the data type of the question involved (i.e., certain data types might not support 'null' at all).

boolean focus ()

This is a trigger to handle any custom focusing logic that the widget must perform. It is called when the ChatterboxWidget as a whole receives focus (and expanded mode is active). Return true if any actual focusing was done (thus necessitating a re-paint).

int getNextMode ()

This method returns a constant that indicates which user action causes the question to be 'answered', thus saving its answer and moving onto the next question. Values are:

  • ChatterboxWidget.NEXT_ON_SELECT -- Question is answered when the user presses the 'select' button on the device (most common; used for text entry)
  • ChatterboxWidget.NEXT_ON_ENTRY -- Question is answered as soon as the input widget has a value (used for 'date'; cannot be used for text entry because it would skip to the next question as soon as the user typed one character)
  • ChatterboxWidget.NEXT_ON_MANUAL -- Question is not answered until the user explicitly selects the 'Next' command (used for 'select-multi'; select-multi cannot use NEXT_ON_SELECT because the select button in this case is needed to select/deselect individual choices)

Item getInteractiveWidget ()

Return a reference to the widget that the user is entering data into, so that GUI listeners can be set appropriately.

Standard Implementations of IWidgetStyle and IWidgetStyleEditable

The standard implementations of IWidgetStyle and IWidgetStyleEditable in JavaRosa already take care of much of the hard work.

CollapsedWidget implements IWidgetStyle. It is the default collapsed mode for ''all'' standard data types and you can likely reuse it for your new data type as well. It displays a short form of the question prompt, and a human-readable form of the answer side-by-side. It can work with all IAnswerDatas.

ExpandedWidget is an abstract class that implements IWidgetStyleEditable. It is the parent of all standard expanded-mode components. It scaffolds the 'long prompt on top / interactive widget on bottom' structure you see in the Chatterbox. Child classes need only implement a few template methods:

Item getEntryWidget (QuestionDef question)

Create the interactive widget (text field, date field, etc.) and return a reference to it.

void updateWidget (QuestionDef question)

Update your interactive widget in response to a change in the question (not including changes to the question's answer). ExpandedWidget.entryWidget contains a reference to the widget. So far, only used to handle locale changes for 'select' questions (whose input widget contains localizable text).

void setWidgetValue (Object o)

Set your input widget to have a certain answer.

abstract IAnswerData getWidgetValue ()

Get the answer from your input widget.

Furthermore, you may want to override getNextMode(), and possibly focus().

Associating the Widget with a Certain Type of Question

After you've created your necessary components, you need to associate your components with an actual question type so that the Chatterbox knows to use your components for answering that kind of question. This is done in ChatterboxWidgetFactory. In this class there is a big block of logic that looks at properties of the question (most notably: control type, data type, and appearance), and chooses the appropriate expanded and collapsed styles. At the end, a new ChatterboxWidget is created with these styles and returned to the Chatterbox. Modify this code to properly identify when to use your new ChatterboxWidget.

Note: only modify ChatterboxWidgetFactory when you're create a new '''standard''' widget -- that is, a widget that corresponds to a normal data type likely to be useful in many JavaRosa applications. If you're building custom widget that collects obscure data and is only applicable to your project, '''do not modify''' this code. You should register your custom widget with the chatterbox at runtime. More info on this will be provided later.

Notes

The existing chatterbox widgets are a great reference to creating new widgets. You should be able to make a new widget with only slight tweaking of an existing widget.

There is kind of a lot of UI hackery going on in the Chatterbox due to bugs in various places (like Polish), and the structure we have set up for ChatterboxWidgets tries to isolate you from it for the most part. However, if you try to do anything too fancy or ambitious with your new widget, you may run into trouble.

Updated