Friday, December 18, 2009

Web Runtime Widget Tutorial – Part 2: Structuring the Digg Client UI

Grab the code!

In this part of the tutorial we will start to turn our Hello World widget into a Digg Client widget. In a real widget project you would have sat down and considered the use cases that your widget was intended to address and hopefully taken care to design a UI that delivered a good experience to users.

In the case of our Digg Client widget, I’ve designed a UI that takes the essence of Scott Guthrie’s Silverlight Digg Client UI and transposes it to its closest WRTKit equivalent. The result is a UI that some might argue is more complex than it needs to be, but I’d argue that it allows us to examine more possibilities in this tutorial than a minimalist functional UI might do.

Let’s start by looking at the target UI. I’ve designed a widget consisting of three views.

Main View

The first view the user will see is the main view which will allow them to enter a topic on which they would like to retrieve stories Digg. It looks like this:

Main view

The view consists of a ListView, a TextField, a FormButton and a set of NavigationButtons representing the stories received from Digg.

The content of the NavigationButtons – the icon & text – are temporary, as is the number of buttons included in the view. Later in the tutorial real data returned from Digg will be used to dynamically populate the list of stories. I could have omitted the NavigationButtons and simply used a text label saying there were no results available, but by creating temporary results we’re able to write and test the code that glues the views together and create a functional UI prototype of our widget.

About View

The about view is basically identical to the equivalent view in our Hello World widget. A small update to the text shown by the label is all that has changed.

About view

Detail View

The detail view is new in this part of the tutorial. Here we can provide more information about a story selected by the user from the list of stories displayed in the main view.

Detail view

This view is quite simple and consists of just three Label controls. Each Label control is populated with a different piece of text – the title of the story, the number of “Diggs” this story has had and last but not least, the text of the story itself.

Three times the UI… three times the code?

Thankfully not! We’ve got more views and more controls to think about, so there are quite a few more UI objects to construct and set up. We also need some more logic to handle navigation between the three views, but it’s all quite straightforward.

All of the work we need to do is confined to the diggclient.js file. Every other file in the project remains as it was in part 1 of the tutorial.

More objects, more variables

Let’s start with all these new UI objects and the variables we’ll use to represent them:

Global variables

As in part 1 of the tutorial, we declare a variable for our WRTKit uiManager and then one variable for each of our three views – mainView, detailView & aboutView.

Next there are three sets of variable declarations, each set declaring the variables needed to represent the UI objects specific to a given view.

For the main view we need a TextField and a FormButton, so we declare two variables to represent them. Notice that there are no variables declared to represent the stories.

For the detail view, we need three Label controls, so we declare three variables to represent them.

As in part 1 of the tutorial, the about view only requires a single variable to represent a Label control.

The next variable declaration – viewFunc – helps to support the logic needed to navigate between the three views of our widget. It  is used to reference one of the functions that we call to show a specific view.

In part 1 of the tutorial we only had two views – the main view & the about view - and we always returned to the main view when we dismissed the about view. However this approach will not work now that we have three views. This time, prior to showing the about view, we will set viewFunc to refer to the appropriate showXXX() function to ensure the correct view is displayed when the user closes the about view.

Finally, as before, we declare MENU_ITEM_ABOUT, which defines the position of the “About” option in the widget’s option menu.

Initialisation

Because every other file in the project except diggclient.js remains the same between parts 1 & 2 of this tutorial, you know from part 1 that the JavaScript method called when our widget starts up is init().

The first block of code in init() should look familiar:

Initialisation

If you can’t remember what this code does, refer back to the explanation given in part 1 of this tutorial.

The next block of code creates the WRTKit UIManager object and the three ListView objects we need:

Initialisation - core UI variables

Again, this code is almost identical to the code in part 1 of the tutorial. The only difference is that we’re creating three views this time instead of two.

Now it’s time to add come controls to the three views. This code is a mixture of old and new:

Initialisation - main view controls

First we create the controls needed for the main view. If you refer back to the UI screenshots earlier in this part of the tutorial, you can see we need a TextField, a FormButton and a set of NavigationButtons that will represent a list of stories.

The code to create the TextField and FormButton controls will again look familiar to you from part 1 of the tutorial. The TextField’s prompt and the FormButton’s text have changed to suit the needs of our UI, and the function that will be called when the FormButton is clicked has been given a new name, to keep it consistent with the name of the object who’s event it is handling.

Next we have some new code. What we need to help demonstrate that our UI works as designed is something to represent some stories. Then we’ll need some code to let us read the stories and go back and choose another story to read. I’m using a for loop to create three objects that will represent stories in our main view.

Initialisation - temporary content

The for loop creates three NavigationButton controls and adds each one to the main view. A NavigationButton is another WRTKit control which is intended to be used to allow users to navigate between views in a widget. The icon & text on the face of the NavigationButton should make it clear to the user which view they will be taken to if they were to click the button. In our case we don’t have any real data yet so we’ll have to cheat!

We create each NavigationButton by passing three parameters to the NavigationButton constructor. First is the familiar object id. Previously we’ve used null for all our object id’s because we haven’t yet needed to refer to them other than through the variable to which the objects are assigned. Now however we’re going to need to know which story the user wants to read, so we pass the integer i as the id of each object; obviously i has a different value each time round the for loop, so each NavigationButton object gets a unique id. Notice that the constructor actually requires a string for the object id; we’re relying on JavaScript converting i from an integer to a string for us, which it does.

The second parameter passed to NavigationButton’s constructor should be an image to be displayed on the face of the button. For the time being we’ll use the widget’s icon as the image for each button. The image on each button will eventually be retrieved via a URL contained within the story data we receive from Digg.

Lastly the constructor needs some text to display on the face of the button. Here we construct a string, again using the integer i to give each button a different string. This string will be the title of a story in our finished widget.

NavigationButtons and FormButtons both derive from the abstract base class ActionControl, which fires the ActionPerformed event whenever the user clicks a button derived from ActionControl. So just as we did with the FormButton control on the main view, we add an event listener to each NavigationButton we create. The same function, storyClicked(), is passed as the event listener for each button because we want the same work to be performed regardless of the story the user wants to read, all that changes is the data that makes up the story.

Moving on to our new detail view, we need to create three Label controls to represent the title of a story, an indication of the number of “Diggs” the story has and finally, the body of the story itself.

Initialisation - detail view controls

The code above shows that we simply create three Label controls, giving each one a self-explanatory caption, and then add the three Label’s to our new detail view.

The code to create the Label control for the about view is once again the same as we used in part 1 of the tutorial:

Initialisation - about view control

We close the init() function by telling the WRTKit UIManager object which view to use initially:

Initialisation complete

Button Event Handlers

At this point we’ve completed all the preparation needed. We have three views, each of which is populated with some controls to allow the user to interact with the widget and allow the widget to present information to the user. Now we need to write some code to handle the various events, such as the user clicking on the “Digg it” button or on any of the NavigationButtons that represent stories returned from Digg.com.

Let’s start with some more familiar code – diggButtonClicked() – which is called, not surprisingly, when the user clicks on the “Digg it” button:

Digg button event handler

Although the variable names are different, you should be able to spot the same pattern of code here as we used to handle the click on the “Say Hello!” button in part 1 of this tutorial.

We get the topic the user would like to search Digg.com for by retrieving the text held in the topicField. If the topic to be searched for is empty, we pop up a notification suggesting the user enters a topic.

If there is text in the topic field, we also pop up a notification, however this is temporary code. Eventually we will send a request to Digg.com for a list of stories about the topic the user entered. For now we just let the user know that we are aware of the topic they would like stories about.

Now let’s look at how we deal with a click on any one of the NavigationButtons that represent stories:

Story button event handler

Here we call a function that sounds like it might be adding content to the detail view controls after which we call another function which looks like it will change the view to show the new detail view. Indeed, that’s exactly what this code does. But why have we done this when we could have just added the relevant text to the Label controls in showDetailView()?

We can’t wait until we’re inside showDetailView() to add the relevant content to the view’s Label controls because showDetailView() can be called from two places:

  1. as a result of clicking on a story from the main view.
  2. on return from the about view, if the about view was called from the detail view.

We don’t really want the different views to understand much about what each others purpose is. It is better that we separate the code that puts the correct content into the detail view’s controls from the code that handles setting up the softkeys and activating the view. This is why we have two functions where previously we might just have used one function.

What about the parameter we pass to populateDetailView()?

Event Message

The parameter passed to event handlers is a JavaScript object which contains three properties you can access in your event handler code:

  • type – this is a string that tells you the type of the event your code has been called to handle, for example, ActionPerformed.
  • source – this is a reference to the object that fired the event, typically a view or a control. In our event handlers, the source will either be our FormButton object, or one of our NavigationButton objects.
  • value – this contains additional data specific to the type of event. We don’t currently use this in our code.

So, when populateDetailView() is called from storyClicked() with a parameter of event.source, we’re calling populateDetailView() with the object that fired the event – the NavigationButton representing the story the user now wants to read.

Populating the Detail view

Here’s the code of populateDetailView():

Populating the detail view

We now know that the story parameter passed to populateDetailView() is the source property of the event message that was passed to storyClicked(). We also now know that this means populateDetailView() is being passed a reference to the NavigationButton that fired the event which resulted in storyClicked() being called.

When we created the controls for the main view, we said that the caption of each NavigationButton would hold the title of a story in our completed widget. We can get hold of the title of the story the user wants to read by calling story.getText() and use that text as the content of detail view’s titleLabel.

The other Label controls are assigned hardcoded text which we will dynamically populate in the next part of this tutorial.

Look at the views!

Let’s start our look at the code used to display each view by looking at what we need to do to present the details of a story:

Showing the detail view

Having implemented the function populateDetailView() to prepare the content of the detail view and ensured that the storyClicked() event handler calls populateDetailView() before calling showDetailView(), it should be no surprise to see that showDetailView() is very simple.

Both the detail and about views need the same softkeys – Options & Back – so it would be good to use the same function to prepare the softkeys for both views. We achieve this through the use of a new function,  setSubViewSoftkeys().

What differs between the needs of the detail and about views is what function they should call to return to the previous view. We handle this by passing setSubViewSoftkeys() the function which should be called when the “Back” softkey is pressed.

Having set the softkeys, we ask the UIManager to show the detail view.

Setting common softkeys & remembering where we were

Before going on to look at the methods used to show the main and about views, let’s take a closer look at setSubViewSoftkeys():

Setting softkeys for detail & about views

As you can see, the right softkey is always set to “Back” by this function. However, depending on the value of the parameter, func, the function that will be called when the user selects the “Back” softkey is different.

The parameter func is assumed to be a reference to a function, rather than a reference to a control or view or any other object.

If func contains a value (func is not null), func is used as the second parameter to menu.setRightSoftkeyLabel(), meaning that when the “Back” softkey is selected by the user, the function referred to by func will be called.

If func is null we pass another the variable viewFunc to menu.setRightSoftkeyLabel(). So in this case when the “Back” softkey is selected by the user, the function referred to by viewFunc will be called.

Showing the About view

So what is viewFunc? To answer that, we need to look at how we display the about view, which we do with this code:

Showing the about view

Earlier in this part of the tutorial while discussing the variables we said  that prior to showing the about view, we will set viewFunc to refer to the appropriate function to ensure the correct view is displayed when the user closes the about view. That’s exactly what the first part of this function is doing.

First we get a reference to the current view from the UIManager object. We then compare that reference to our view variables to determine which view is currently being displayed.

If the current view is either the main view or the detail view, we set viewFunc to refer to the function used to show that view. However, if the current view is actually the about view, we don’t set viewFunc, or, more accurately, we don’t change the value of viewFunc.

In this way we ensure that viewFunc always refers to the function needed to display the view the user was in prior to displaying the about view, even if they try to trick us by selecting Options –> About while already in the about view.

Once viewFunc is correctly set, it’s a simple matter of putting the appropriate text in the aboutLabel control, calling setSubViewSoftkeys() to ensure we get “Back” as our right softkey and then asking the UIManager object to show the about view. Notice that we don’t pass any parameter to setSubViewSoftkeys(). This means that the value of the func parameter that setSubViewSoftkeys() expects will be null, and the value of viewFunc will be used as the function to be called when the right softkey is selected by the user.

Showing the Main view

The function, showMainView(), should be familiar to you as it is exactly as it was in part 1 of the tutorial:

Showing the main view

Menu Event Handling

The code used to handle selections from the options menu is also the same as we used in part 1 of the tutorial. No changes were necessary here because we didn’t introduce any new options.

Menu event handler

Additional Notes

WRTKit offers an alternative control that could have removed the need for the new detail view. We could have used a ContentPanel object to represent each story. The ContentPanel is an expandable UI element that supports a caption that could represent the title of a story, and additional content that could represent the text of a story. The user can expand and collapse each panel to read whichever story they wish. If we used ContentPanel controls instead of NavigationButton controls we could remove the detail view.

I chose to use NavigationButtons to illustrate the use of another view and the work needed to navigate between them. You can of course use the tutorial code as the basis for your own work. Feel free to re-write the widget to use ContentPanel controls rather than NavigationButton controls & remove the detail view.

It is also worth noting that we could have simplified some of the view switching logic if we had ensured that the user could not select Options –> About while they were already in the about view. If you want to try rewriting the code to achieve this, think about use of the menu object’s remove() function to help you.

Coming next…

This concludes the second part of our tutorial on creating a Digg client with Nokia WRT & Aptana Studio. Now we have a functional UI, involving three views and all the logic needed to navigate between them. We have used a mixture of temporary code and hardcoded data to simulate some stories.

In the next part of this tutorial we will get real data into our widget from the internet.

Part 3>

No comments:

Post a Comment