Widget Wonderland for WebOS

Make your WebOS application look like the built-in applications with Widgets

Know that your application belongs

How do you know that your application belongs?

We’ve all seen them — applications that just don’t quite fit in with the rest of the applications on your mobile device.

You can easily pick out the applications written by the pros and the applications written by the hobbyists. To be fair, if you have written code for any period of time, you will most certainly have spent some time in both camps.

After all, not every project has the budget necessary for that rock-solid look and feel — and we don’t often get paid to go back and rewrite older applications just because a new widget came along or because down the road we are more knowledgable on how to best code an application.

That said, it does help when you have a feel for how to do something the “right” way the first time out of the gate. WebOS is new to everyone — even the folks at Palm — so let’s explore WebOS by adding some new widgets to an application we started a couple of weeks back which is described in this column.

In case you didn’t see the earlier project, we have a simple WebOS application which displays a basketball team roster in a List widget. We will expand the project by adding a number of features, some of which we’ll tackle in this article and then leave the others for next time. Here are the expanded features of our application:

  • Add CommandMenu buttons
  • Adding a new player via a second Scene
  • Using a TextField Widget
  • Using a ListSelector Widget
  • Using a IntegerPicker Widget
  • Sorting the List Widget
  • Persisting the List data to a Depot

In addition to working with these Palm WebOS widgets, we’ll also get a feel for some WebOS design patterns such as pushing scenes and using callback functions. Note, the source code to the application is hosted at the Linux Magazine Mobile code hosting project.

Command Menu

If you have seen the built-in WebOS applications, you may have noticed some sharp-looking buttons across the bottom of the screen as shown in the image below, taken from the Google Maps application.

commandmenu.png

While virtually all of the user interface elements of a WebOS application are defined in HTML and “skinned” with CSS, the CommandMenu is actually created entirely in Javascript code. In our application, we are adding two CommandMenu buttons: ‘Add Player’ and ‘Reset Roster’. The Add Player button causes the application to display a new scene for capturing the details for a new player. The Reset Roster button causes the list of players to be set back to the “starting 5″ after the user has perhaps added players or maybe even deleted some with the “swipe” gesture. Here is a screen shot of the application showing the two new Command Menu buttons at the bottom.

listfuncommandmenu.png

Here is the code for defining these two buttons.

this.cmdMenuModel = {
   items: [
      {label:'Add Player', command:'newplayer'},
      {label:'Reset Roster', command:'reset'}
   ]};

this.controller.setupWidget(Mojo.Menu.commandMenu, {}, this.cmdMenuModel);

The CommandMenu is setup with an array of items, one entry for each button. Each item needs to have a command property and either a label or icon. There are a handful of pre-packaged icons for commonly used commands like “Add”, which is a big plus sign. Because we had a label containing “Reset Roster” that didn’t have a clear iconic representation, we went with text labels for both of the buttons. It is a good practice to keep all of the buttons consistent and not mix-n-match icons and labels.

The application needs to have a means to process the commands when a button or menu is selected. To do this, we implement a handleCommand routine, as shown here.

HomeAssistant.prototype.handleCommand = function (event) {
	if (event.type == Mojo.Event.command) {
		if (event.command == "reset") {
                        /*implement reset command here */
			this.sortedby.value = 'name';
			this.controller.modelChanged(this.sortedby,this);
			this.listModel.items = this.originalRoster.items.slice(0);
			this.listModel.items.sort(this.mySortFunc);
			this.controller.modelChanged(this.listModel, this);
			return;
		}
		if (event.command == "newplayer") {
                        /* implement new player code here */
			var p = new Player();
			this.controller.stageController.pushScene('new-player',p,this.handleNewPlayer.bind(this));

			return;
		}
	}
}

The function receives an event object. If the event is a command, it is evaluated and dispatched appropriately.

Handling commands and making a scene

Let’s start by looking at the code to handle the “Add Player” command. When we add a new player, we need to display a new scene to capture the required information. But even prior to doing that we need to know what data elements make up a “Player”. To accomplish this, we’ve added a new file called Player.js and added it to the “model” folder in our project as shown below. The term “model” comes from the Model-View-Controller world. While this pattern does not match 100% with Palm WebOS, it does make sense to put “object definitions” in a central area of an application. For us, that means the model folder. You are free to put the file wherever in the project it makes sense for you to do so.

project.png

This code is very simple — we set every player to default to being a Guard with a number of 5. Obviously this is just an arbitrary set of values. Many times classes will have more sophisticated intialization.

var Player = Class.create({

	initialize: function(){

		this.name = '';
		this.position = 'Guard';
		this.number = 5;
	}
});

Note that if you add a new file to a WebOS application, you will not get the benefit of it — also known as pulling your hair out when your application doesn’t work as expected — until you add the javascript file reference to the file named sources.json which is in the root of your project. sources.json is used when the application is packaged up. If the file is not referenced here, it does not make it to the device!

[
    {"source": "app\/model\/player.js"},
	{"source": "app\/assistants\/stage-assistant.js"},
    {
        "source": "app\/assistants\/home-assistant.js",
        "scenes": "home"
    },
    {
        "source": "app\/assistants\/new-player-assistant.js",
        "scenes": "new-player"
    }
]

A common Palm WebOS design pattern is to pass an object to a scene. This approach allows the scene to act on the object and/or pass the object back via a callback function. Passing the object in to begin with makes future enhancements like editing an existing player a bit easier to implement. Note that the code first creates a new Player object and then “pushes” the scene entitled “new-player”. When a scene is pushed, it is displayed on the top of the stack of screens. Some folks in Palm land call this the deck of cards. Whatever you call it, your scene is now visible.

In addition to the scene name, there are two additional parameters we pass to the scene:

The first paramter is the Player object we just created. The second parameter is a binding to a function within our code which is used as a “callback”. Again, another design pattern to get used to — Palm WebOS is callback crazy. Get comfortable with it! The reason we need to mess with the “bind” syntax is that we want the instance of this function to be called back that is part of this scene object, not a class-wide function. This is like calling a membe function, not a “static” or class-level function in other environments.

Let’s have a quick look at the “new-player” scene to better understand how this application works.

Adding a new player

The new-player scene takes three pieces of input:

  1. Player Name
  2. Position
  3. Jersey Number

To gather this data, we are going to use three different kinds of widgets:

  1. TextField for the player’s Name
  2. ListSelector for the player’s Position
  3. IntegerPicker for the player’s Jersey Number

Each of these widgets is declared in the new-player.html file which is located in the /app/views/new-player folder.

<div class="palm-group">
	<div class="palm-group-title" style="text-align:center">Player Details</div>
	<div x-mojo-element="TextField" id="playerName"></div><br />
	<div x-mojo-element="ListSelector" id="playerPosition"></div><br />
	<div x-mojo-element="IntegerPicker" id="playerNumber"></div>
</div>

And here is a screen shot of the scene in action:

newplayerscene.png

It take a bit of code to get these widgets setup — though not too much so don’t worry.

function NewPlayerAssistant(player,callback) {
	this.player = player;
	this.callback = callback;
	Mojo.Log.info("new player!" + this.player.Name);
}

NewPlayerAssistant.prototype.setup = function() {

	this.controller.setupWidget("playerName",
	{
		hintText:'enter player name',
		multiline: false,
		focus: true,
		modelProperty: 'name',
		label : 'Name'
	},
	this.player
	);

	this.controller.setupWidget("playerPosition",
			{
				choices: [{label:"Guard",value:"Guard"},
				          {label:"Forward",value:"Forward"},
						  {label:"Center",value:"Center"}
				          ],
				modelProperty: 'position',
				label:'Position',
				labelPlacement: Mojo.Widget.labelPlacementLeft
			},
			this.player
		);

	this.controller.setupWidget("playerNumber",
		{
			modelProperty: 'number',
			label:'Jersey Number',
			min:0,
			max:55
		},
		this.player
	);

}

NewPlayerAssistant.prototype.cleanup = function(event) {
	this.callback(this.player);
}

Breaking down the code

The parameters of player and callback are passed into the scene. We save these references off and store them in this.player and this.callback respectively. For fun, we also send a message to the Log. OK, it’s not for fun — it is a sanity check that things are wired up the way we hope them to be. Palm WebOS is currently pretty short on debugging tools, so using the Log is an important skill, no matter how pedestrian it may appear to be.

What is of real interest to us is the setup function. Here we see each of the three widgets being configured with a call to this.controller.setupWidget. The general syntax for this function includes:

  • The object’s id as defined in the html file associated with this scene.
  • An object representing various attributes to specify the specific behavior of this widget. For example we indicate that we do not want the player name TextField to be multiline. Additionally, we set the hint text to provide some guidance for the user. For the ListSelector, we provide an array of choices to be displayed along with a data model for storing the current value. And for the IntegerPicker, we tell it what range of integers to accept, from 0 – 55.
  • Lastly, we include a data model. This is data that is automatically updated when the user interfaces with the data.

Once we have finished making our entries and hit the “back” gesture, we signal the calling scene that we’re done editing this new player. This is accomplished in the “cleanup” function by invoking the caller-provided callback function, passing in the reference to the player. The new-player scene is now “popped” off of the display and our home scene is again visible. Let’s jump back to the HomeAssistant.js file to see what happens when our callback is invoked with the new player!

Heading Home

Our new player has been created — AC Green, a tremendously talented forward who played for a number of NBA teams. That’s great, we added AC — but what do we do with him? We add him to the list of course!

HomeAssistant.prototype.handleNewPlayer = function(player) {
	Mojo.Log.info("handleNewPlayer: " + player.name + "," + player.position + "," + player.number);
	this.listModel.items.push(player);
	this.listModel.items.sort(this.mySortFunc);
	this.controller.modelChanged(this.listModel,this);
}

Let’s step through this function:

The function takes a single parameter: player. This is the same player object that was passed to the new-player scene previously — and the same player object that was sent via the callback function.

As a sanity check, I like to dump the new object’s info to the Log file. Over time these kinds of calls to the log typically get removed when we’re confident that everything is working to our satisfaction.

Next, this player is “pushed” into the list of items which holds our roster of players.

Next, the list is sorted — we cover that in the next column, so look for the next article to see how that works.

Lastly, we notify any widgets that are using the “this.listModel” data as the their model data. Once the List Widget is notified of the changed data, it re-draws itself as shown below.

acgreen.png

OK, so now we’ve added a new player to our team. Let’s say things didn’t work out with salary negotiations with AC, so we want to revert to our starting line-up. We can either swipe-to-delete AC’s entry in the list or we can hit the “Reset Roster” button at the bottom of the screen. When we hit the reset button we want the list to revert to our original roster. This is a very simple operation.

We need to get a copy of the original roster, which we can do with a call to the array function slice.

this.listModel.items = this.originalRoster.items.slice(0);

This makes a copy of the originalRoster.items array and stores it into the listModel.items array. If we were to simply do an assignment, such as listModel.items = originalRoster.items, we would wind up with two references to the same array. We need to use the “slice” function because we want a copy, not a reference. Yes, I learned that the hard way, thank you.

Of course, just updating the data alone does not make the List widget reflect the changes — we need to notify the List that the data has changed. We do this with the modelChanged() function:

this.controller.modelChanged(this.listModel, this);

Now any widgets keeping tabs on that data model will be notified of the change and refresh their view of the data.

Wrap Up

We have added a new scene to the application which allows us to include new players to the roster. Along the way we learned a bit about the TextField, ListSelector and IntegerPicker widgets. We also learned how data is passed between scenes. Of course, there are other design patterns to this environment and arguably one of the most frustrating (and powerful) aspects of the Palm WebOS is that there is so much flexibility with which to tackle a problem — at times you’re left wondering which way is the “right” way. Over time we will get a clearer picture of those patterns as the experience level of the developer community rises. Next time, we’ll finish up this application with by adding list sorting and saving data to a Depot.

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