Indicators are used to show that activity is taking place, even it it’s not visible, and in some cases, to show some measure of the progress of the activity. Mojo has four indicator widgets, but they belong to two types:
Activity indicator, or Spinner, which spins without showing progress
Progress indicator, which shows both activity and progress
The spinner is the only activity indicator, but there are three progress indicator widgets:
Progress Pill, a wide pill that is styled to match the View menu
and the palm-header
scene
style
Progress Bar, a narrow horizontal bar with a blue progress indicator
Progress Slider, which is intended for streaming media playback applications
The Spinner widget is most appropriate when there isn’t much space in the layout for an indicator or when the duration of the activity is hard to estimate. In other cases you should use a progress indicator; it’s preferable because it gives the user a bounded sense of duration.
Use a spinner to show that an activity is taking place. The framework uses a spinner as part of any activity button, and you’ll see it used in the core applications. There are two sizes; the large spinner is 128 × 128 pixels, and the small spinner is 32 × 32 pixels. These sizes are optimized for the Palm Prē screen and may vary on other devices, but the spatial and visual characteristics will be maintained on other devices.
There aren’t any long operations in News other than the feed updates, which are asynchronous. We’ll add a spinner to the feed list whenever an update is in progress, demonstrating a simple application of an indicator.
This will also demonstrate the technique for including widgets within a list entry, a powerful Mojo feature introduced in Chapter 3. You already know that you can use widgets to display dynamic data; by combining them into lists you can create complex UI controls with the widgets as building blocks. You may want to review Chapter 3 if you have questions after reading these next few paragraphs.
You can design list entries to include other widgets, including
other lists, in almost the same way that you use widgets outside of
lists. The differences are that the list’s model includes the widgets’
models, and that you declare widgets within the list’s itemTemplate
, using a
name attribute to identify each widget.
In this example, a Spinner widget is included in each feedListWgt
entry, which will be activated
when the corresponding news feed is updated through an Ajax request.
Start by adding the spinner declaration into the feedListWgt
’s row template, views/feedList/feedRowTemplate.html:
<div class="feedlist-info icon right" id="info"></div> <div x-mojo-element="Spinner" class="right" name="feedSpinner"</div> <div class="feedlist-title truncating-text">#{title}</div> <div class="feedlist-url truncating-text">#{-url}</div>
The new line is the div with the name feedSpinner
and is simply a declaration of
the Spinner widget. The syntax should start to seem familiar by
now.
By including it into the feedListWgt
list item’s template, we have
implicitly directed the List widget to insert a new spinner element in
the DOM whenever it creates a new entry. It’s the same as creating a
spinner outside of a list, except in one major way: the spinner’s
model property must be part of the feedListWgt
’s items array.
We still have to set up the Spinner widget, which we do in the
setup method of feedList-assistant.js, but we don’t include
a model in the call to setupWidget()
, as that is assumed to be part
of the feedListWgt
’s items
array:
// Setup the feed list, but it's empty this.controller.setupWidget("feedListWgt", { itemTemplate:"feedList/feedRowTemplate", listTemplate:"feedList/feedListTemplate", addItemLabel:"Add...", swipeToDelete:true, renderLimit: 40, reorderable:true }, this.feedWgtModel = {items: this.feeds.list}); // Setup event handlers: list selection, add, delete and reorder feed entry this.showFeedHandler = this.showFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listTap, this.showFeedHandler); this.addNewFeedHandler = this.addNewFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listAdd, this.addNewFeedHandler); this.listDeleteFeedHandler = this.listDeleteFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listDelete, this.listDeleteFeedHandler); this.listReorderFeedHandler = this.listReorderFeed.bindAsEventListener(this); this.controller.listen("feedListWgt", Mojo.Event.listReorder, this.listReorderFeedHandler); // Setup spinner for feedlist updates this.controller.setupWidget("feedSpinner", {property: "value"});
Most of the feedList
assistant’s setup method
is shown in this code sample; the only addition is the last line of
code (and preceding comment). It sets up the spinner, naming the model
property as value
, but the model is
not in the arguments list; the list’s model, this.feedWgtModel
, is used implicitly as the
spinner’s model.
The feedList
default data is
defined at the beginning of the stage-assistant.js. Add the value property
to each default list entry, and set it to false. This is the spinner’s
model, and will start as false since there is no activity. You need to
include this for every feedList
entry, as in this example:
{ title:"New York Times", url:"http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", type:"rss", value:false, numUnRead:0, newStoryCount:0, stories:[] },
There’s one other place where a new feed list entry is created;
in the checkOk
method of addDialog-assistant.js:
// If a new feed, push the entered feed data on to the feedlist and // call processFeed to evaluate it. if (this.feedIndex === null) { this.feeds.list.push({title:this.nameModel.value, url:this.urlModel.value, type:"", value:false, numUnRead:0, stories:[]}); // processFeed - index defaults to last entry feedError = this.feeds.processFeed(transport); } else { this.feeds.list[this.feedIndex] = {title:this.nameModel.value, url:this.urlModel.value, type:"", value:false, numUnRead:0, stories:[]}; feedError = this.feeds.processFeed(transport, this.feedIndex); }
The spinner is set up and integrated into the list’s template and model. All that remains is to activate and deactivate the spinner at the right times. Those times are just before the Ajax request is made (spinner activated) and after the response is received (spinner deactivated) whether the request was successful or not.
There are four changes to make, all in the feeds
model:
updateFeedRequest
Activate before Ajax request
updateFeedFailure
Deactivate
updateFeedSuccess
Deactivate after processing new feed data, and activate before another feed update request is made
The sample below shows the changes to updateFeedSuccess()
, which includes both an
activate and a deactivate call:
// Process the feed, passing in transport holding the updated feed data var feedError = this.processFeed(transport, this.feedIndex); // If successful processFeed returns News.errorNone, if (feedError !== News.errorNone) { // There was a feed process error; unlikely, but could happen if the // feed was changed by the feed service. Log the error. if (feedError == News.invalidFeedError) { Mojo.Log.info("Feed ", this.nameModel.value, " is not a supported feed type."); } } News.feedListChanged = true; // Change feed update indicator & update widget var spinnerModel = this.list[this.feedIndex]; spinnerModel.value = false; this.updateListModel(); // If NOT the last feed then update the feedsource and request next feed this.feedIndex++; if(this.feedIndex < this.list.length) { this.currentFeed = this.list[this.feedIndex]; // Request an update for the next feed but first // change the feed update indicator & update widget spinnerModel = this.list[this.feedIndex]; spinnerModel.value = true; this.updateListModel(); this.updateFeedRequest(this.currentFeed); } else { // Otherwise, this update is done. Reset index to 0 for next update this.feedIndex = 0; News.feedListUpdateInProgress = false; } . . .
In each case, we set the spinnerModel.value
and call this.updateListModel()
to update the model
changing the state of the spinner. The spinner’s model is accessed by
referencing the this.feedIndex
, the
array index for the feed that is being updated, then setting that
entry’s value property to change just the spinner in that list
entry.
Run the application, and the feeds will update one after another. If you wait for the feed interval to pass, they will update again. You will see a spinner appear between the feed title and the unread count badge on the left side, as shown in Figure 5-1, with the spinner on the BBC News feed item.
Spinners only take up space when they’re active. In some cases, if the feed title is long enough, you’ll see the title truncated to accommodate the spinner, then resize to fill the vacated space after the spinner is deactivated. Elegant integration of indicators is the type of polish that makes an application appealing and easy to do with Mojo’s widgets.
The progress pill is the most common progress indicator, and is styled to match the Mojo button and header styles. The other two indicators, progress bars and progress sliders, are more specialized, but are functionally derived from progress pills; you’ll manage them in the same way.
Use a Progress Pill widget (Figure 5-2) to show download progress when loading from a database, or anytime you initiate a long-running operation and have a sense of the duration.
The indicator is designed to show a pill image that corresponds to the model’s value property, where a value of 0 has no pill exposed and a value of 1 has the pill completely filling the container. Initialize the indicator’s model value to 0, then progressively update the model property until it has a value of 1.
It’s best to use an interval timer. At each interval callback,
increase the progress indicator’s value property and call the updateModel
function. For example, start
with the progress pill’s value property set to 0 and set an interval
timer for 600 ms. Assuming the planned operation will take about 3
seconds, you would increase the value property from 0 to 0.2 at the
first update and again by 0.2 at each update thereafter:
if (this.progressCounter > 1) { // This operation is complete! this.completeProgress(); } else { this.pillModel.progress = this.pillModel.progress + 0.2; this.controller.modelChanged(this.pillModel); }
The Progress Bar widget is exactly the same as the progress pill, except that you
use x-mojo-element="ProgressBar"
in
your scene file. Otherwise, you code it and manage it just as you do
the progress pill. Figure 5-3
shows a progress bar.
In the default style, there isn’t room on the bar for a title or image, but the properties are supported nonetheless.
For media or other applications where you want to show progress as part of a tracking slider, the Progress Slider widget is an ideal choice. Combining the Slider widget with the progress pill, the behavior is fully integrated, but not all of the configuration options are represented. Figure 5-4 shows a progress slider.
All of the slider properties are represented, and you configure
the progress slider just as you would a Slider widget. You have a
model property of value
, which can
be renamed through the attributes property. You manage it exactly as
you would the progress pill, by progressively increasing the value
property from 0 to 1.
3.144.14.150