Anatomy of a Widget

The HP webOS application framework, known as Mojo, provides a variety of widgets for applications to use. This page provides a high-level explanation of how Mojo widgets work from the perspectives of both application developers and widget authors.

The Application Perspective

Widgets are intended to provide reusable, relatively complex behaviors for apps in an easy to use format with appearance that follows the styling guidelines for webOS.

Unlike other frameworks, many widgets are not entirely intended to be "black boxes", but are more like "helpers", which generate complex HTML and provide useful behaviors.

Declaring Widgets

Widgets are declared in HTML as empty divs, as shown below:

<div id="myListSelector" x-mojo-element="ListSelector"></div> 

The x-mojo-element attribute specifies the widget class added to the application scene when a widget is set up. Widget instantiation is likely to change the contents of the div it is specified on. (An exception to this is the Drawer widget), but may also add HTML to other parts of the scene, or update the style of its div. This widget instantiation can happen in two ways:

  1. When a scene is pushed and the view HTML includes widgets, including HTML specified as part of a render template added to the original scene content
  2. When a widget is specified in an HTML template used by another widget, like the List widget.

Widget Setup

Before a widget can be instantiated, it must be set up. This usually happens in the scene assistant's setup() method by calling SceneController.setupWidget() to specify the starting conditions of the widget. setupWidget() accepts up to two pieces of data, the attributes and the model. Not every widget requires both a model and attributes.

If the application developer does not set up the widget in the setup method of the scene, they must call instantiateChildWidgets on the container div after setup.

The details differ depending on the widget, but here's an example for the ListSelector:

var selectorChoices = [{name:'Cosa', value:'spanish-thing'},
  {name:'Chose', value:'french-thing'},
  {name:'Ding', value:'german-thing'}
];
var selectorAttributes = {label: 'Pick a Thing',
  choices: selectorChoices,
  modelProperty:'value' };
this.selectorModel = {value:'spanish-thing'};
this.controller.setupWidget('myListSelector', selectorAttributes, this.selectorModel); 

As you'd expect, the widgetName argument specifies which widget the attributes and model apply to. This can be either the id or name attribute of the widget's div element. It's usually fine to use id, but remember that all element IDs in the page's DOM must be unique, so don't assign an id to your widget if it will be used in a list, or there are multiple copies of it on a single scene. In these cases, just use name instead.

The second parameter to setupWidget() specifies the attributes for the widget. These are properties that affect the behavior and display of the widget, but are not tied to the actual data being displayed or edited. Widget attributes currently cannot be changed after the widget is instantiated. Widgets will provide defaults for all required attributes, so it is not necessary to specify all attributes when setting up a widget. A ListSelector is used to edit a property that can have one of several values, so in this case, the attributes consists of three things:

  1. The label for the widget.
  2. The name of the property to edit/display from the model.
  3. The list of possible values for the property.

Widgets that support being disabled or getting data from a model will also supply two attributes. These are the same for all widgets, and allow the application developer to specify a different property name if the default one is taken.

The last parameter to setupWidget() specifies the data model object the widget should use. This is the actual user data that the widget operates on (displays it, edits it, etc.). It will often change, requiring the widget to update itself. In the previous case, it consists of only one property, which the list selector has been configured to edit: value.

Some properties of a widget may be specified in either the model OR the attributes. If specified in both, the widget will use the property specified in the model. Application developers should specify the property in the model if they believe they will have to change it later, or they are instantiating the widget as part of a List (more on this below).

Changing Widget Data Models

When a widget model changes, the application (usually the scene assistant) is required to call the modelChanged() method on the widget's scene controller, passing the model object that changed. The scene controller will then automatically notify all widgets using that model, so they can properly update given the current state of the model.

To continue with the ListSelector example:

this.selectorModel.value = "new value";
this.controller.modelChanged(this.selectorModel);

Note that calling modelChanged() with an entirely new model object will not do what you want, since the framework tracks model objects, not model variable names. The result would simply be that no change would occur, since no widgets would be using that new model object and so nobody would be notified of the change.

The whoChangedIt argument is intended to represent the identity of whoever is responsible for the change to the model. This is matched against a similar value provided when subscribing to model changes, and is used to ensure that objects are not notified of their own changes to a model. Scene assistants (and widget assistants, where applicable) will usually simply pass this. This argument is optional.

modelChanged() is handy for updating widgets to reflect changes in their models, but if you need to actually change the model object that a widget is using, you must use setWidgetModel() instead. While setupWidget() applies to all widgets with the given name, setWidgetModel() only ever applies to a single widget. So you must pass the widget's id, or the actual widget DOM element. (Internally, setWidgetModel calls $(widget)).)

Here's a sample, following the ListSelector example:

// SceneController.setWidgetModel(widget, newModel);

// Use a new model object in place of the old one:
this.selectorModel = {value:'german-thing'};

// Set the widget to use the new model:
this.controller.setWidgetModel('myListSelector', this.selectorModel);

Using Widgets in Lists

The distinction between widget attributes and models can become fuzzy at times, because some widgets allow a particular property to be specified in either place. The distinction becomes important when you are using widgets inside list items. List supports this, and there are examples in the framework-library sample code.

The way this works is that the HTML template used to render the list items (and provided by the scene assistant) can include widgets, which are set up much like any other widget. The main difference is that the model objects used with list item widgets do not come from the widgets' setupWidget calls. Instead, the model object used for a list item widget is actually the same object specified in the items array for the list.

Here is an example based on the 'Widgets in a List' demo in the framework-library app. This demo shows a List, where each item includes a ListSelector. The HTML for the scene simply declares a List widget. The HTML template subwidgets/subwidgets-item used for the list items declares the widgets that will be repeated for each list item as shown:

<div class="row">
  <div class="title">
      <div name="listSelector1" class="palm-list-selector" x-mojo-element="ListSelector"></div>
  </div>
</div>  

Note that the widget declared in the item template does not have an id attribute specified. An element id must be unique across the entire DOM. Because this element will be duplicated for each list item, you cannot an IDs to it. Instead, use name, which setupWidget() accepts in place of an id.

The List (widgetList) and ListSelector widgets are set up as follows:

this.controller.setupWidget('widgetList',
  {itemTemplate:'subwidgets/subwidgets-item'}, this.widgetList);
//note: ListSelector does not take a model argument in this case
this.controller.setupWidget('listSelector1',
  {label: 'Laugh', property: 'laugh', choices: this.laughChoices});

The List is set up as you'd expect: specify an element id, attributes object, and model.

This scheme allows the model for listSelector1 to be different for every list item, while also avoiding the complexity of having to specify one model per subwidget.

The ListSelector is set up slightly differently. There's no model specified in setupWidget(), only the attributes. This is because listSelector1 is rendered in a list context, so its model object comes from the array of list item objects as described above. The attributes specified in setupWidget() apply to all instances of the listSelector1 widget, in all the items in the list. In contrast, the widget's model is different for each instance of listSelector1.

Observing and Handling Widget Events

The details of event handling will depend on what widget is being used, the desired functionality, and the structure of any app-provided HTML templates. That said, apps generally set up event listeners in the scene assistant's setup() method, and listeners are usually added to the widget div declared in the HTML. For example, the ListSelector widget sends a Mojo.Event.propertyChange event when the value changes, and a scene assistant would observe the one declared above with some code like this:

this.handleSelectorChange = this.handleSelectorChange.bindAsEventListener(this);
this.controller.listen("myListSelector", Mojo.Event.propertyChanged,
  this.handleSelectorChange);

The event handling can be somewhat more complicated for a widget like List where the list item content is defined by an app-provided HTML template. The list items can contain any number of inputs, other interactive elements, or widgets. In order to help simplify event handling for these cases, List supports Mojo.Event.listTap and Mojo.Event.listChange events. Scene assistants can add event listeners to the widget div as usual if they are listening for these list events. The events carry enough information to determine the proper response:

  1. a reference to the model object used for the particular list item that was clicked/changed. (model)
  2. the data specifically attached to the row that was affected (item)
  3. the absolute index of the row affected (index)
  4. the originalEvent, so that the application developer can investigate what caused the event, and possible what specific item in the list was tapped or changed (if it were another widget)

Read more about this on the Events page.

The Framework Perspective

Widgets can be instantiated in the following ways:

In each case, the framework identifies the widget's DOM element or creates a new WidgetController, and instantiates a new object. This encapsulates the generic widget behavior needed by all widgets, and takes the following initialization steps:

In the case of widgets that need to render subwidgets, things are slightly more complex, but also handled by the framework. There's an instantiateChildWidgets() method available on the widget controller, which is used to easily instantiate newly rendered child widgets.