org.symbian.tools.wrttools.doc.WRTKit/html/WRTKit_Travel_Companion_user_interface-GUID-989d17cc-f019-46bb-b6f5-49166e258aca.html
author Eugene Ostroukhov <eugeneo@symbian.org>
Fri, 04 Jun 2010 09:44:40 -0700
changeset 345 7723a46fe224
parent 230 7848c135d915
permissions -rw-r--r--
Bug 2480 - Excluded resources are still available in preview and debugger

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xml:lang="en">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<meta name="copyright" content="(C) Copyright 2005" />
<meta name="DC.rights.owner" content="(C) Copyright 2005" />
<meta content="concept" name="DC.Type" />
<meta name="DC.Title" content="Travel Companion user interface" />
<meta scheme="URI" name="DC.Relation" content="WRTKit_Travel_Companion_Tutorial-GUID-be79ba64-fa03-4968-964e-d7dcc42d7053.html" />
<meta content="XHTML" name="DC.Format" />
<meta content="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA" name="DC.Identifier" />
<meta content="en" name="DC.Language" />
<link href="commonltr.css" type="text/css" rel="stylesheet" />
<title>
Travel Companion user interface</title>
</head>
<body id="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA"><a name="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA"><!-- --></a>



    <h1 class="topictitle1">
Travel Companion user interface</h1>

    <div>

        <div class="section"><h2 class="sectiontitle">
Handling multiple views</h2>

            
            <p>

                The Travel Companion widget will follow many of the same patterns that the Hello World
                and RSS Reader widgets have followed. The entry point to the widget is the init() function
                that we call from the onload event handler in the main HTML file TravelCompanion.html.
                We will implement the init() function along with the rest of the widget and user interface
                in the TravelCompanion.js file. Let's start with the init() function so that we get the
                widget up and running.
            </p>

            <p>

                From previous widgets we already know a lot of things about the WRTKit and about what
                we will need. For example we know that we will have a user interface manager and that
                we'll need a reference to each of the views that our widget uses. And since we have
                designed the Travel Companion widget we know what views we will have. Before we write the
                init() function we can go ahead and declare the variables that we will use to refer to
                the views and the user interface manager. And while we're at it we can also declare the
                variable that we'll use to refer to the Travel Companion engine. All of these variables
                are globals and thus go outside the init() function:
            </p>

<pre>

// Reference to travel companion engine that implements the business logic.
var engine;

// Reference to the WRTKit user interface manager.
var uiManager;

// References to views.
var mainView;
var infoView;
var converterView;
var weatherView;
var settingsView;
</pre>

            <p>

                Previously we've written the init() function so that it creates the entire user interface
                but because the Travel Companion widget has a much larger user interface than anything
                we've done before, it's probably a good idea to split things up into smaller functions
                so we don't end up with a huge init() function that will be hard to read and maintain.
                Let's create a function for the creation of each view, and then one function that will
                call these view creation functions and setup all other things that we need for the user
                interface, such as the Options menu. We'll just create stub functions for now but we
                will soon get to implement the functions:
            </p>

<pre>

// Creates the user interface.
function createUI() {
}

// Creates the main view.
function createMainView() {
}

// Creates the info view.
function createInfoView() {
}

// Creates the converter view.
function createConverterView() {
}

// Creates the weather view.
function createWeatherView() {
}

// Creates the settings view.
function createSettingsView() {
}
</pre>

            <p>

                In the RSS Reader widget we used functions called showMainView() and showSettings() to
                move between views. That allowed us to implement all the logic that was required when
                moving between views in one place regardless of what action triggered the change of
                views. We'll do the same thing in the Travel Companion widget but this time we'll need
                five functions since we have five views. Let's create stubs for these too at this stage
                so that the functions exist if we need to refer to them.
            </p>

<pre>

// Displays the main view.
function showMainView() {
}

// Displays the info view.
function showInfoView() {
}

// Displays the converter view.
function showConverterView() {
}

// Displays the weather view.
function showWeatherView() {
}

// Displays the settings view.
function showSettingsView() {
}
</pre>

            <p>

                Since we will create each user interface view in its own function our init() function
                will be much simpler than in previous widgets. We will create the business logic engine,
                create the user interface by calling createUI() and then display the main view.
            </p>

<pre>

// Called from the onload event handler to initialize the widget.
function init() {
    // initialize the business logic engine
    engine = new Engine();
    
    // create the user interface
    createUI();
    
    // display the main view
    showMainView();
}
</pre>

            <p>

                Let's get started with creating the user interface by returning to the createUI()
                function. We'll want to widget to show the softkey bar and we'll want it to be in
                tab navigation mode instead of the default pointer navigation mode. We then need
                to create the user interface manager and the five views that this widget has. Of 
                course those five views are actually created by the five create-functions that we
                just wrote stubs for so we just need to call those functions from createUI().
            </p>

<pre>

// Creates the user interface.
function createUI() {
    if (window.widget) {
        // set tab-navigation mode and show softkeys
        widget.setNavigationEnabled(false);
        menu.showSoftkeys();
    }
    
    // create UI manager
    uiManager = new UIManager();
    
    // create views
    createMainView();
    createInfoView();
    createConverterView();
    createWeatherView();
    createSettingsView();
}
</pre>

            <p>

                Because we created the stub functions for moving between views, we can also create
                the Options menu now. We'll only add one item to the menu in addition to the default
                "Exit" menu item. The item we'll add is one that will take the user to the settings
                view. We'll first need to create a global menu item identifier:
            </p>

<pre>

// Constants for menu item identifiers.
var MENU_ITEM_SETTINGS = 0;
</pre>

            <p>

                Now that we have the menu item identifier we can actually create the menu item. Because
                the Options menu functionality is only available in the S60 Web Runtime and not in PC
                browsers we'll add that code in the same if-clause where we set the navigation mode and
                show the softkey bar.
            </p>

<pre>

// create menu
var settingsMenuItem = new MenuItem("Settings", MENU_ITEM_SETTINGS);
settingsMenuItem.onSelect = menuItemSelected;
menu.append(settingsMenuItem);
</pre>

            <p>

                When the menu item is selected it calls a function called menuItemSelected(). Let's
                create that function and handle the case when the settings menu item is selected.
                When it is, we want to call showSettingsView() so that the user ends up in the settings
                view as expected:
            </p>

<pre>

// Callback for when menu items are selected.
function menuItemSelected(id) {
    switch (id) {
        case MENU_ITEM_SETTINGS:
            showSettingsView();
            break;
    }
}
</pre>

            <p>

                The framework that we'll need when implementing the actual views is now nearly complete.
                But before we move on let's talk about how we'll be using softkeys in the Travel
                Companion widget.
            </p>

            <p>

                In the main view we want the right softkey to be the default "Exit". But in the
                four functional views we don't want it to exit the widget but rather to return to
                the main view. We'll thus use "Back" as the title in the functional views, except
                in the settings view where we'll use "Cancel".
            </p>

            <p>

                Let's create three functions that will handle setting the widget right softkey to
                these configurations. The functions will check if we're actually in the S60 Web
                Runtime before attempting to change the right softkey in order to make the widget
                also work in a PC browser for testing purposes.
            </p>

<pre>

// Sets the softkeys for the main view.
function setMainViewSoftkeys() {
    if (window.widget) {
        // set right softkey to "exit"
        menu.setRightSoftkeyLabel("", null);
    }
}

// Sets the softkeys for sub views.
function setSubViewSoftkeys() {
    if (window.widget) {
        // set right softkey to "back" (to main view)
        menu.setRightSoftkeyLabel("Back", showMainView);
    }
}

// Sets the softkeys for settings view.
function setSettingsViewSoftkeys() {
    if (window.widget) {
        // set right softkey to "cancel" (returns to main view)
        menu.setRightSoftkeyLabel("Cancel", showMainView);
    }
}
</pre>

        </div>

        <div class="section"><h2 class="sectiontitle">
Main view</h2>

            
            <p>

                Let's create our main view now so that we get something visible. The main view is
                of course created in the function createMainView() for which we already created a
                stub function. The main view will have four WRTKit NavigationButton controls in it,
                one for each of the views (not counting the main view itself) in the widget. Using
                the NavigationButton control is just like using a FormButton, with the difference
                that it looks different and can have an icon image in addition to just text. Earlier
                when we looked at the files that were in the Examples/TravelCompanion directory we
                noticed that there were four icons there. Now it's time to use them as icons in the
                navigation buttons.
            </p>

            <p>

                We will add event listerners to the buttons so that when they are clicked (when they
                emit an ActionPerformed event) they will call the functions we have created for
                moving between views. At this point the functions are just stubs, but we will of
                course fix that later.
            </p>

            <p>

                We've had captions for the views that we've created so far in the Hello World and
                RSS Reader widgets and the Travel Companion will also have view captions. However
                for the Travel Companion we'll use an image as our view caption. We will set the
                view caption to an empty string because if it's undefined or null then the view
                caption area will be hidden. Then we will create a CSS rule for the ListViewCaptionText
                class that is used by the area where the caption text goes in a list view. Because
                our CSS rule is defined after the default WRTKit CSS rule for ListViewCaptionText,
                our rule will be override the default one. Let's open up TravelCompanion.css and
                create this rule:
            </p>

<pre>

/* Place logo in list view caption */
.ListViewCaptionText {
    background: url("ListViewCaptionLogo.png") no-repeat;
    height: 35px;
}
</pre>

            <p>

                We specified a height in addition to the background image. The height is needed
                so that the caption text area won't collapse to zero height since it won't contain
                any text. We're now ready to implement the createMainView() function:
            </p>

<pre>

// Creates the main view.
function createMainView() {
    // empty caption text to display the caption bar - custom background using CSS
    mainView = new ListView(null, "");
    
    // info
    var navToInfoButton = new NavigationButton(null, "NavInfoIcon.png", "Information");
    navToInfoButton.addEventListener("ActionPerformed", showInfoView);
    mainView.addControl(navToInfoButton);
    
    // converter
    var navToConverterButton = new NavigationButton(null, "NavConverterIcon.png", "Currency Converter");
    navToConverterButton.addEventListener("ActionPerformed", showConverterView);
    mainView.addControl(navToConverterButton);
    
    // weather
    var navToWeatherButton = new NavigationButton(null, "NavWeatherIcon.png", "Weather Forecast");
    navToWeatherButton.addEventListener("ActionPerformed", showWeatherView);
    mainView.addControl(navToWeatherButton);
    
    // settings
    var navToSettingsButton = new NavigationButton(null, "NavSettingsIcon.png", "Settings");
    navToSettingsButton.addEventListener("ActionPerformed", showSettingsView);
    mainView.addControl(navToSettingsButton);
}
</pre>

            <p>

                We now have a main view and our widget even calls showMainView() when it starts
                but nothing will actually show up yet because we haven't implemented the showMainView()
                function yet. Let's do that now:
            </p>

<pre>

// Displays the main view.
function showMainView() {
    setMainViewSoftkeys();
    uiManager.setView(mainView);
}
</pre>

            <p>

                The function calls the setMainViewSoftkeys() function to put the right softkey
                in the proper state and then asks the user interface manager to show the main
                view. Remember that we're already calling the showMainView() function from the
                init() function so we can go ahead and test this now in a PC browser, emulator
                or handset. Notice the custom list view caption image and the navigation button
                icons. Clicking the navigation buttons doesn't do anything yet because we don't
                have any other views yet and the functions to show other views except the main
                view aren't implemented. But then again, because we wrote stub functions we are
                also not getting any error messages.
            </p>

            <div class="fignone" id="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA__GUID-B006B086-0D8B-4EA4-8A71-179507B7595A"><a name="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA__GUID-B006B086-0D8B-4EA4-8A71-179507B7595A"><!-- --></a><span class="figcap">Figure 1. 
Travel Companion main view</span>

                
                <br /><img src="Travel_Companion_Main_Screenshot_1.png" /><br />
            </div>

        </div>

        <div class="section"><h2 class="sectiontitle">
Settings view</h2>

            
            <p>

                It's a good idea to implement the settings view as early as possible because
                that allows you to test the rest of the widget more easily. In order to get
                the settings view up and running we'll need to do three things. First of all
                we'll need to implement the createSettingsView() function so that the widget
                actually has a settings view. Second, we'll need to implement the actions
                that can be performed in that view. There are two actions: "Save" and "Cancel".
                Of these, save is the tricker one and we'll implement it in a function that
                we will call saveSettingsClicked(). The cancel action will simply take us back
                to the main view and will be handled by the showMainView() function that we
                have already implemented. And third, we need to implement the showSettingsView()
                function so that clicking on the settings navigation button in the main view
                takes us to the settings view and sets it up correctly so that the view
                reflects the current preferences.
            </p>

            <p>

                The settings view will have four sections that we will separate using a WRTKit
                Separator control. The first section lets the user select the home city and the
                whether the home city is in daylight saving time. Here we'll use a SelectionMenu
                control for the city selection and a SelectionList control with a single option
                for the daylight saving time option. We could have used a SelectionList with
                two options "Daylight saving time" and "Normal time". Since the selection would
                have been a single selection this would have resulted in two radio buttons. But
                partly for the sake of usability and partly to show how to do it, we'll implement
                it so that it's a single checkbox instead. Thus, we'll use a SelectionList with
                a single option but we'll get the checkboxes instead of radio buttons by putting
                the SelectionList in multiple selection mode. Because there's only one option
                there will only be one checkbox.
            </p>

            <p>

                The second section is just like the first one but for the local city. The third
                section is for the temperature unit selection (Celsius or Fahrenheit) and we'll
                implement it as a single selection SelectionList control. And finally the fourth
                section is for the Save and Cancel FormButton controls.
            </p>

            <p>

                The options for the selection controls will be created just before we create
                the controls themselves. The daylight saving time and temperature unit options
                are just static JSON defined option lists since we know what the options are.
                But for the city selection we'll create it dynamically by asking the business
                logic engine for a list of all the supported cities. We'll then create one option
                for each city.
            </p>

            <p>

                Because we'll need to access the controls in the settings view from outside the
                creation function we'll need to create global references to them:
            </p>

<pre>

// Settings view controls.
var homeCitySelection;
var homeCityDSTSelection;
var localCitySelection;
var localCityDSTSelection;
var temperatureUnitSelection;
</pre>

            <p>

                We can now implement the settings view creation function:
            </p>

<pre>

// Creates the settings view.
function createSettingsView() {
    // empty caption text to display the caption bar - custom background using CSS
    settingsView = new ListView(null, "");
    
    // create city options from cities array
    var cityOptions = [];
    var cities = engine.getCities();
    for (var i = 0; i &lt; cities.length; i++) {
        cityOptions.push({ value: cities[i], text: cities[i].name });
    }
    
    // create DST option
    dstOptions = [ { value: true, text: "DST (+1h)" } ];
    
    // home city
    homeCitySelection = new SelectionMenu(null, "Home City", cityOptions);
    settingsView.addControl(homeCitySelection);
    
    // home city DST (using a multiple selection but only one option to get a single checkbox)
    homeCityDSTSelection = new SelectionList(null, "Daylight Saving Time (home)", dstOptions, true);
    settingsView.addControl(homeCityDSTSelection);
    
    // separator
    settingsView.addControl(new Separator());
    
    // local city
    localCitySelection = new SelectionMenu(null, "Local City", cityOptions);
    settingsView.addControl(localCitySelection);
    
    // local city DST (using a multiple selection but only one option to get a single checkbox)
    localCityDSTSelection = new SelectionList(null, "Daylight Saving Time (local)", dstOptions, true);
    settingsView.addControl(localCityDSTSelection);
    
    // separator
    settingsView.addControl(new Separator());
    
    // temperature unit
    var temperatureUnitOptions = [ { value: "c", text: "Celsius" }, { value: "f", text: "Fahrenheit" } ];
    temperatureUnitSelection = new SelectionList(null, "Temperature Unit", temperatureUnitOptions);
    settingsView.addControl(temperatureUnitSelection);
    
    // separator
    settingsView.addControl(new Separator());
    
    // save button
    var saveSettingsButton = new FormButton(null, "Save");
    saveSettingsButton.addEventListener("ActionPerformed", saveSettingsClicked);
    settingsView.addControl(saveSettingsButton);
    
    // cancel button
    var cancelSettingsButton = new FormButton(null, "Cancel");
    cancelSettingsButton.addEventListener("ActionPerformed", showMainView);
    settingsView.addControl(cancelSettingsButton);
}
</pre>

            <p>

                Note that we used the actual city as the value for the city options. You can
                use anything you want for the value property for a selection control option
                and in this case we used the actual city object because it will come in handy
                later when we want to set the home and local cities that the user has selected
                to the engine.
            </p>

            <p>

                Our save button calls the saveSettingsClicked() function we talked about earlier
                but we haven't created that function yet. We'll continue implementing the user
                interface by writing this function.
            </p>

            <p>

                You'll recall that the business logic engine actually stores all the preferences
                so all we really need to do is read what values have been selected in the settings
                view, telling these values to the engine, calling the savePreferences() method in
                the engine to persist the settings, and then return to the main view.
            </p>

<pre>

// Called when the user clicks on the "save" button in the settings view.
function saveSettingsClicked() {
    // update the selected home city
    var selectedHomeCityOption = homeCitySelection.getSelected();
    if (selectedHomeCityOption != null) {
        engine.setHomeCity(selectedHomeCityOption.value);
    }
    
    // update the selected local city
    var selectedLocalCityOption = localCitySelection.getSelected();
    if (selectedLocalCityOption != null) {
        engine.setLocalCity(selectedLocalCityOption.value);
    }
    
    // update home and local city DST
    // there's only one checkbox but we are using a multiple-selection menu.
    // if the selected array has one element then the checkbox is checked.
    engine.setHomeCityDST(homeCityDSTSelection.getSelected().length == 1);
    engine.setLocalCityDST(localCityDSTSelection.getSelected().length == 1);
    
    // update temperature unit
    var selectedTemperatureUnitOption = temperatureUnitSelection.getSelected();
    if (selectedTemperatureUnitOption != null) {
        engine.setTemperatureUnit(selectedTemperatureUnitOption.value);
    }
    
    // save settings
    engine.savePreferences();
    
    // go back to the main view
    showMainView();
}
</pre>

            <p>

                We're writing defensive code here and making sure that the selection controls
                actually have anything selected at all. This should never be possible with
                single selection control if they have one option already selected, but we'll
                use the principle of "better safe than sorry".
            </p>

            <p>

                Notice that the daylight saving time selection is checked by asking for the
                selected options array (we're using a multiple selection control) and checking
                if there is exactly one selected option. We don't care about the actual selected
                option because we know that there's only one. Either it's selected or it's not.
            </p>

            <p>

                We have now implemented two of the three steps we said were necessary to get the
                settings view fully functional. Let's implement the final step: showing the view.
                Showing the settings view is done by first updating the settings view controls
                to reflect the current configuration, setting the right softkey to the state that
                it should be in for the settings view and then asking the user interface manager
                to show the settings view.
            </p>

<pre>

// Displays the settings view.
function showSettingsView() {
    // update settings view controls to match current configuration
    // the DST selected sets are either the options array with its single option or an empty array
    
    // home city and DST setting
    homeCitySelection.setSelected(homeCitySelection.getOptionForValue(engine.getHomeCity()));
    homeCityDSTSelection.setSelected(engine.getHomeCityDST() ? homeCityDSTSelection.getOptions() : []);
    
    // local city and DST setting
    localCitySelection.setSelected(localCitySelection.getOptionForValue(engine.getLocalCity()));
    localCityDSTSelection.setSelected(engine.getLocalCityDST() ? localCityDSTSelection.getOptions() : []);
    
    // temperature unit
    temperatureUnitSelection.setSelected(temperatureUnitSelection.getOptionForValue(engine.getTemperatureUnit()));
    
    setSettingsViewSoftkeys();
    uiManager.setView(settingsView);
}
</pre>

            <p>

                We're using the convenient getOptionForValue() function that all selection controls
                have to retrieve the right option to select for each of the selection controls. The
                way we handle the daylight saving time selection controls may seem a little strange
                at first but remember that we only have a single option and thus if that option should
                be selected then the selected options array is identical with the options array - both
                are arrays containing the one and same option. Selection controls always copy the options
                and selected options arrays when the setOptions() and setSelected() methods are called
                so there is no problem in passing the options array as the selected options array.
            </p>

            <p>

                The settings view is now completed. You can try it out and play with it, change the
                settings, open it up again, change some more settings, cancel the changes, etc. to
                verify that it's working as it should.
            </p>

            <div class="fignone" id="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA__GUID-66154A40-DD96-45B3-BF53-01594426BA71"><a name="GUID-989D17CC-F019-46BB-B6F5-49166E258ACA__GUID-66154A40-DD96-45B3-BF53-01594426BA71"><!-- --></a><span class="figcap">Figure 2. 
Travel Companion settings view</span>

                
                <br /><img src="Travel_Companion_Settings_Screenshot_1.png" /><br />
            </div>

        </div>

    </div>

<div>
<div class="familylinks">
<div class="parentlink"><strong>Parent topic:</strong> <a href="WRTKit_Travel_Companion_Tutorial-GUID-be79ba64-fa03-4968-964e-d7dcc42d7053.html">Travel Companion</a></div>
</div>
</div>

</body>
</html>