Using Lists in webOS – Mastering the Oldest Mobile Profession

The List widget is central to webOS applications -- master the List widget and you're well on your way to creating great webOS applications

In the beginning…

In the beginning God created the heavens and the earth.

And not long after we had lots and lots of data to manage.

Fast forward to today and we get to manage that data with cool little gadgets called smartphones. Smartphones are supposed to help make our lives more efficient and on a good day, I think that might be the case. And even if we only carry the device to look cool (ahhem, can anyone say iPhone …) we still wind up managing data with our devices. At minimum, we view our call and SMS history on the device. More often than not, that data is presented in one or more lists.

Because lists are so central to mobile software development, we are going to take a look at using the List widget in Palm’s webOS.

Getting Started

We are going to build an application which manages a basketball team roster. At the risk of aging myself, the default data will be some of the players from the Los Angeles Lakers from the 80′s as shown in the image below.

mainscreen.png

Palm webOS lists have quite a bit of functionality — much more so than a typical column of data as you might find on other platforms. This functionality includes but is not limited to:

  • Swipe to Delete
  • Reordering
  • Built-in “Add New feature
  • Templatized layout
  • Dynamic rendering capability, as desired
  • and much more

With all of this capability comes a little complexity — which is to say that it just takes some practice to get a feel for the design pattern of working with a webOS List widget. Once you have built one or two applications containing List widgets, you will start to think differently about how the application should be constructed to begin with. The List can really become central to many applications as you start to “think” like a webOS user. I really appreciate the fact that so much can be done intuitively with the List without the need for extra buttons and menus to crowd out the user interface and confuse your user. There is no need for highlighting an item and then tapping a menu or using a separate button to delete it — just swipe it and confirm the deletion. The image below shows us deleting Michael Cooper. Sorry Mike.

swipetodelete.PNG

Rearranging entries is as simple as tap-and-hold-and-drag. Want to add a new entry? Just tap the “+Add New Player” entry at the bottom. Obviously you can change the text to suit your application’s needs, unless you are an NBA General Manager, and then perhaps we should talk. Let’s have a look at what we need to do to build this application. Note, if you are anxious to browse the full code and mess with it, you can find it here. You can check out a copy with a subversion client if you want to build the application yourself.

The code

We’ll start with the scene description, found in home-scene.html. In this scene we define a List widget with a “div” element with a special attribute of “x-mojo-element”.

<div class="palm-group">
	<div class="palm-group-title" style="text-align:center">List Fun</div>
		<div class="palm-list" >
		<div x-mojo-element="List" id="teamList"></div>
	</div>
</div>

In order to use this List widget, we need to set it up in the scene’s assistant file, home-assistant.js. In particular, we are interested in the setup function where we invoke a method called setupWidget from the Scene Controller. The setupWidget function takes three parameters:

  • The id attribute value defined in the scene’s html file. In this case the value is “teamList”
  • A json object which represents the options to setup this widget
  • A data model — the data for the list

Note the following options values in use in our code (shown below):

itemTemplate This is a required snippet of html (stored in an actual html file) which contains the formatting information for laying out an individual entry in the list.
listTemplate This is an optional snippet of html used to define how the listItems are arranged as a group.
addItemLabel If this option is present, an additional list entry is added to the bottom of the list with the text provided. Tapping this entry caused the Mojo.Event.listAdd event to be fired.
swipeToDelete Permits the deletion event when an item is swiped. When this gesture is detected by webOS, the function registered to handle the Mojo.Event.listDelete event is invoked.
reorderable Permits the items in the list to be reordered.
dividerFunction This is a function used to help sort the entries into groupings.

This represents just a subset of the options available for the List widget. For a complete reference visit the Palm Developer website. Here is the code to setup our List widget.

this.controller.setupWidget("teamList",
	{
		itemTemplate: 'home/listentry',
		listTemplate: 'home/listcontainer',
		addItemLabel: 'Add New Player',
		swipeToDelete: true,
                reorderable: true,
		dividerFunction : this.whatPosition
	},
        this.listModel);

Let’s take a moment to discuss the data model in particular. This is represented in our sample application as the variable “listModel”. It is defined in the scene’s constructor:

function HomeAssistant() {
	this.listModel =
	{
		items : [
                            {name:'Magic',position:'Guard',number:'32'},
                            {name:'Scott',position:'Guard',number:'4'},
                            {name:'Cooper',position:'Guard',number:'21'},
                            {name:'Kareem',position:'Center',number:'33'},
                            {name:'Rambis',position:'Forward',number:'31'}
		        ]
	};
}

Note that the variable has a property named “items”. This is very important — the List widget expects to find a property of the dataModel which contains an array of objects. The property name must be items. In this case, our items array contains objects with NBA player information. Each player “record” contains properties representing: name, position, and jersey number. These properties wind up in the list by including them in the listentry template. In our case it is found in the /app/views/home/listentry.html file:

<div class='palm-row' x-mojo-tap-highlight="momentary"&rt;
	#{name} (##{number})
</div&rt;

Note that the pattern for property inclusion is the pound or hash sign (#) followed by the property name within curly braces. Don’t get confused by the double ## in this example. The first symbol is a literal # used to designate a player’s jersey number and the second is used as a template marker for the property containing the jersey number.

After we have setup our widget, we want to register event listeners for the various events of interest. In this case we are interested in capturing when a list entry is tapped, when the “add new player” is selected, when an entry is deleted and when items are re-ordered by the user. Note that these event handler registrations are normally included in the setup method following the call to setupWidget.

	/* add event handlers to listen to events from widgets */
	this.controller.listen('teamList',Mojo.Event.listTap, this.listTapped.bindAsEventListener(this));
	this.controller.listen('teamList',Mojo.Event.listAdd, this.listAdd.bindAsEventListener(this));
	this.controller.listen('teamList',Mojo.Event.listDelete, this.listDelete.bindAsEventListener(this));
	this.controller.listen('teamList',Mojo.Event.listReorder, this.listReorder.bindAsEventListener(this));

Proper form would have use “unregister” these listeners in the cleanup method. We’ll save that for a rainy day.

The event handlers

When an event occurs we want to handle it appropriately — let’s take a quick look at the handlers we have registered for this List widget instance.

When the list is tapped, we want to take some sort of action — in this case we are going to write an entry to the application log and then display a simple dialog to display the player’s name represented by the selected list item. The method we are using is errorDialog. Don’t get alarmed about the word error — this just happens to be a quick and dirty way to show a message.

HomeAssistant.prototype.listTapped = function(event){
	Mojo.Log.info("#############List Tapped [" + event.index + "]");
	Mojo.Controller.errorDialog(this.listModel.items[event.index].name);
}

dialogbox.png

Note the multitude of #’s is to help us find the entry in the very busy log output.

When the “Add New Player” entry is tapped, the listAdd function is invoked, as previously registered. At present this function does nothing other than make an entry into the log.

HomeAssistant.prototype.listAdd = function(event){
	Mojo.Log.info("#############List Add");
   // TODO : add more features to this sample application :)
}

When an entry is “swiped” and the Delete button is confirmed, the listDelete function is invoked.

HomeAssistant.prototype.listDelete = function(event){
	Mojo.Log.info("#############Delete entry" + event.index);
	this.listModel.items.splice(event.index,1);
}

Note that in this function we do a little management of the listModel.items array to actually remove the selected index.

And here is the re-order function, invoked when one of the list items is dragged to a new position. Note that there are properties of the event argument named fromIndex and toIndex — these tell us which items were involved in the swap.

HomeAssistant.prototype.listReorder = function(event){
	Mojo.Log.info("#############List Reorder" + event.fromIndex + " to " + event.toIndex);
	var f = this.listModel.items[event.fromIndex];
	var t = this.listModel.items[event.toIndex];
	this.listModel.items[event.fromIndex] = t;
	this.listModel.items[event.toIndex] = f;
}

At this point our list has most of the functionality we could ask for. There is one other function I want to hilight before we wrap up with a couple of important pieces of trivia. Note that in the list we have the “G”, the “C” and the “F” showing in the list as dividers. This is implemented by way of the function defined in the dividerFunction option passed to setupWidget. In our case we are simply taking the first letter from the position property of the data. So for example, the first letter of “Guard” is “G”. This places the items under a divider tab in the list of “G”.

The visual effect comes from a built-in, default style provided by Palm. You can also override this with a dividerTemplate. One thing to keep in mind is that you can have multiple occurences of these dividers depending on the order of the data. For the sake of being complete, here is the divider function’s implementation for our application.

HomeAssistant.prototype.whatPosition = function(data){
	// return the first letter of the position (G,C,F)
	return data.position[0];
}

Now for that trivia.

A cautionary tail about framework_config.json

When I first built this application, I kept getting an annoying problem when rendering the scene. Then I remembered that I needed to add a file called framework_config.json. In this file I have added two attributes — without these entries, this application will not run and you would be frustrated. Like I was, until I remembered them.

{
	"escapeHTMLInTemplates": false,
	"logLevel": 99
}

The “escapeHTMLInTemplates” entry allows us to use html in the templates supplied to the setupWidget function as options. If we don’t set this value to false, we don’t see our list. You have been warned.

The “logLevel” entry is setting the logging level to the maximum threshold (or minimum, depending on how you want to look at this). Any log entry made via the Mojo.Log.info, Mojo.Log.error, or Mojo.Log.warn functions will be seen when this value is above their respective thresholds. At this point in my webOS career, I just set this value to 99 so I see everything.

Speaking of seeing the log entries, these can be seen in either the Palm Inspector application (be sure to start your app in debug mode as ‘inspectable’). Alternatively, the way I normally view the logs is by establishing an SSH session and viewing a tail of the log. For example, this application log can be viewed with a command line of :

log com.msiservices.listfun

That about wraps things up for now. In a future column we’ll look at persisting this data so our changes are saved from one run to the next of the application.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62