Advanced Applications

We’ve pushed the simple model of single application stage far enough to incorporate services, notifications, and even dashboard stages, but we need to move to a more advanced model to access the rest of the features. An advanced application will have some or all of these characteristics:

  • Use an application assistant as the main application entry point and for handling application initialization and coordination.

  • Create a primary card stage when launched.

  • Handle relaunch or remote launch requests through a defined handleLaunch method.

  • Post banner notifications and maintain a dashboard panel for events while not in focus or in the background.

  • Schedule wakeup requests through the Alarm service and handle the alarm callbacks in the background.

If you aren’t clear on the application lifecycle or the role of the application assistant, you may want to review Chapter 2 before reading the rest of this chapter.

Back to the News: App Assistant

This chapter began with a list of guidelines for developing multistage applications. News needs to be cleaned up to conform to those guidelines, so before creating the app assistant, we’ll make these changes to News:

  1. Remove use of the global window object; change window.setInterval() to this.controller.window.setInterval().

  2. Use the local controller’s stageController methods; instead of Mojo.Controller.stageController methods for pushScene, we’ll use swapScene, as in this example in storyView-assistant.js in the handleCommand method:

    case "do-viewNext":
    Mojo.Controller.stageController.swapScene(
            {
                transition: Mojo.Transition.crossFade,
                name: "storyView"
            },
            this.storyFeed, this.storyIndex+1);
    break;
  3. Add the noWindow property to appinfo.json:

    {
        "title": "News",
        "type": "web",
        "main": "index.html",
        "id": "com.palm.app.news",
        "version": "1.0.0",
        "vendor": "Palm",
        "noWindow" : "true",
        "icon": "icon.png",
        "theme": "light"
    }
  4. And add the app assistant to sources.json; remember that it must be the first entry:

    [
      {
        "source": "app/assistants/app-assistant.js"
      },
      {
        "source": "app/assistants/stage-assistant.js"
      },
    
     .
     .
     .
    
    ]

We’ll create a minimal application assistant first, and then flesh it out step by step so that you can see each part clearly. Initially, we’ll simply move all the code from the stage assistant to app-assistant.js, removing the call to this.controller.pushScene() and adding the handleLaunch method to create the card stage and push the first scene:

/*  AppAssistant - NEWS

    Copyright 2009 Palm, Inc.  All rights reserved.

    Responsible for app startup, handling launch points and updating news feeds.
    Major components:
    - setup; app startup including preferences, initial load of feed data
        from the Depot and setting alarms for periodic feed updates
    - handleLaunch; launch entry point for initial launch, feed update
        alarm, dashboard or banner tap
    - handleCommand; handles app menu selections

    Data structures:
    - globals; set of persistant data used throughout app
    - Feeds Model; handles all feedlist updates, db handling and default data
    - Cookies Model; handles saving and restoring preferences

    App architecture:
    - AppAssistant; handles startup, feed list management and app menu management
    - FeedListAssistant; handles feedList navigation, search, feature feed
    - StoryListAssistant; handles single feed navigation
    - StoryViewAssistant; handles single story navigation
    - PreferencesAssistant; handles preferences display and changes
    - DashboardAssistant; displays latest new story and new story count

*/

//  ---------------------------------------------------------------
//    GLOBALS
//  ---------------------------------------------------------------

//  News namespace
News = {};

// Constants
News.unreadStory = "unReadStyle";
News.versionString = "1.0";
News.MainStageName = "newsStage";
News.DashboardStageName = "newsDashboard";
News.errorNone =    "0";                    // No error, success
News.invalidFeedError = "1";                // Not RSS2, RDF (RSS1), or ATOM

// Global Data Structures

// Persistent Globals - will be saved across app launches
News.featureFeedEnable = false;              // Enables feed rotation
News.featureStoryInterval = 5000;           // Feature Interval (in ms)
News.notificationEnable = true;             // Enables notifcations
News.feedUpdateBackgroundEnable = false;    // Enable device wakeup
News.feedUpdateInterval = 900000;           // Feed update interval

//  Session Globals - not saved across app launches
News.feedListChanged = false;               // Triggers update to Depot db
News.feedListUpdateInProgress = false;      // Feed update is in progress
News.featureStoryTimer = null;              // Timer for story rotations
News.dbUpdate = "";                         // Default is no update
News.wakeupTaskId = undefined;              // Id for wakeup tasks

// Setup App Menu for all scenes; all menu actions handled in
//  AppAssistant.handleCommand()
News.MenuAttr = {omitDefaultItems: true};

News.MenuModel = {
    visible: true,
    items: [
        {label: "About News...", command: "do-aboutNews"},
        Mojo.Menu.editItem,
        {label: "Update All Feeds", checkEnabled: true, command: "do-feedUpdate"},
        {label: "Preferences...", command: "do-newsPrefs"},
        Mojo.Menu.helpItem
    ]
};

function AppAssistant (appController) {

}

//  -------------------------------------------------------
//  setup - all startup actions:
//    - Setup globals with preferences
//    - Set up application menu; used in every scene
//    - Open Depot and use contents for feedList
//    - Initiate alarm for first feed update

AppAssistant.prototype.setup = function() {

    // initialize the feeds model
    this.feeds = new Feeds();
    this.feeds.loadFeedDb();

    // load preferences and globals from saved cookie
    News.Cookie.initialize();

    // Set up first timeout alarm
    this.setWakeup();

};

//  -------------------------------------------------------
//  handleLaunch - called by the framework when the application is asked to launch
//    - First launch; create card stage and first first scene
//    - Update; after alarm fires to update feeds
//    - Notification; after user taps banner or dashboard
//
AppAssistant.prototype.handleLaunch = function (launchParams) {
    Mojo.Log.info("ReLaunch");

    var cardStageController = this.controller.getStageController(News.MainStageName);
    var appController = Mojo.Controller.getAppController();

    if (!launchParams)  {
        // FIRST LAUNCH
        // Look for an existing main stage by name.
        if (cardStageController) {
            // If it exists, just bring it to the front by focusing its window.
            Mojo.Log.info("Main Stage Exists");
            cardStageController.popScenesTo("feedList");
            cardStageController.activate();
        } else {
            // Create a callback function to set up the new main stage
            // once it is done loading. It is passed the new stage controller
            // as the first parameter.
            var pushMainScene = function(stageController) {
                stageController.pushScene("feedList", this.feeds);
            };
            Mojo.Log.info("Create Main Stage");
            var stageArguments = {name: News.MainStageName, lightweight: true};
            this.controller.createStageWithCallback(stageArguments,
                pushMainScene.bind(this), "card");
        }
    }
};

// -----------------------------------------
// handleCommand - called to handle app menu selections
//
     .
     .
     .

};

Handling Launch Requests

The framework calls the application assistant’s handleLaunch method after the setup method on initial launch, and whenever a launch request is made to the application. If you don’t define one, the framework attaches a default handleLaunch method, which calls your application assistant’s setup method. Launch requests are made implicitly through the following:

  • Taps to your application’s banner notifications.

  • Calls from other applications through an Application Manager service request.

  • Alarms that wake up the application after a timeout.

By convention, you should also use this entry point for your own launch requests. For example, instead of using the Application Manager service request, launch your application after a tap to the Dashboard stage by calling the entry point directly from within the handleTap method in the Dashboard, passing an action property in the launchParams object:

DashboardAssistant.prototype.launchMain = function() {
    Mojo.Log.info("Tap to Dashboard");
    var appController = Mojo.Controller.getAppController();
    appController.assistant.handleLaunch({action: "notification",
      index: this.index});
    this.controller.window.close();
};

And we’ll add a specific case in handleLaunch for this notification action following the conditional set up to handle the first launch. We use a case statement because we’ll build on this to handle other launch actions:

//  -------------------------------------------------------
//  handleLaunch - called by the framework when the application is asked to launch
//    - First launch; create card stage and first first scene
//    - Update; after alarm fires to update feeds
//    - Notification; after user taps banner or dashboard
//
AppAssistant.prototype.handleLaunch = function (launchParams) {
    Mojo.Log.info("ReLaunch");

    var cardStageController = this.controller.getStageController(News.MainStageName);
    var appController = Mojo.Controller.getAppController();

    if (!launchParams)  {
        // FIRST LAUNCH
        // Look for an existing main stage by name.
        if (cardStageController) {
            // If it exists, just bring it to the front by focusing its window.
            Mojo.Log.info("Main Stage Exists");
            cardStageController.popScenesTo("feedList");
            cardStageController.activate();
        } else {
            // Create a callback function to set up the new main stage
            // once it is done loading. It is passed the new stage controller
            // as the first parameter.
            var pushMainScene = function(stageController) {
                stageController.pushScene("feedList", this.feeds);
            };
            Mojo.Log.info("Create Main Stage");
            var stageArguments = {name: News.MainStageName, lightweight: true};
            this.controller.createStageWithCallback(stageArguments,
                pushMainScene.bind(this), "card");
        }
    }
    else  {
        Mojo.Log.info("com.palm.app.news -- Wakeup Call", launchParams.action);
        switch (launchParams.action) {

        // NOTIFICATION
        case "notification"  :
             Mojo.Log.info("com.palm.app.news -- Notification Tap");
            if (cardStageController) {

                // If it exists, find the appropriate story list and activate it.
                Mojo.Log.info("Main Stage Exists");
                cardStageController.popScenesTo("feedList");
                cardStageController.pushScene("storyList", this.feeds.list,
                  launchParams.index);
                cardStageController.activate();
            } else {

                // Create a callback function to set up a new main stage,
                // push the feedList scene and then the appropriate story list
                var pushMainScene2 = function(stageController) {
                    stageController.pushScene("feedList", this.feeds);
                    stageController.pushScene("storyList", this.feeds.list,
                      launchParams.index);
                };
                Mojo.Log.info("Create Main Stage");
                var stageArguments2 = {name: News.MainStageName,
                  lightweight: true};
                this.controller.createStageWithCallback(stageArguments2,
                  pushMainScene2.bind(this), "card");
            }
        break;

        }
    }
};

The notification launch case is added to activate the News main stage with the storyList scene pushed to the selected feed. If the stage doesn’t exist, it will be created before pushing the storyList scene. This type of launch request will launch the application if it’s not already launched.

Looking at this a little more closely, if the stage exists, the News application’s main stage is in the card view, so the scene stack is popped back to the feedList scene, which is always at the base of the scene stack for this application. The storyList scene is pushed with the feed that was displayed by the dashboard summary. If the main card stage does not exist (just the dashboard is running), the stage is created before pushing both the feedList and storyList scenes. It’s important to set up the scene stack when launching to a scene that is normally not at the base of the stack.

This launch action is initiated by tapping either the dashboard summary (from the code sample shown immediately prior to this application assistant sample) and the banner notification. If you look at the feed.js method for updateFeedSuccess(), you’ll see that the launch argument’s action property is set to "notification":

appController.showBanner(bannerParams, {action: "notification", 
 index: this.feedIndex},
  this.list[this.feedIndex].url);

Sending and Considering Notifications

To facilitate communication between assistants, the Mojo framework supports an application specific notification chain. Any application assistant can pass a notification through the chain through a call to SendToNotificationChain() with a single hash parameter. The current stage and scene assistants have the opportunity to handle these notifications by including a considerForNotification() method.

To illustrate this, we’ll send an update notification (type: "update") through the chain, identifying that an update is in progress (update : true) or just completed (update : false), as well as the index of the affected feed (feedIndex : this.feedIndex). This is done in several places in feeds.js, wherever we were updating the spinnerModel and calling this.updateListModel(). Replace this code:

// Change feed update indicator & update widget
var spinnerModel = this.list[this.feedIndex];
spinnerModel.value = true;
this.updateListModel();

With this code:

// Notify the chain that there is an update in progress
Mojo.Controller.getAppController().sendToNotificationChain({
  type: "update",
  update: true,
  feedIndex: this.feedIndex
});

That code is used in three other places. The update property should be set to true wherever you were previously setting the spinnerModel.value to true, and false in the other cases. You can remove the registerListModel(), removeListModel(), and updateListModel() methods in feeds.js and remove the calls to those methods from the feedList-assistant.js activate() and deactivate() methods.

Scenes can receive notifications if you add a considerForNotification() method to the scene assistant. In the News example, we’ll add this to the feedList-assistant.js:

// ---------------------------------------------------------------------
// considerForNotification - called by the framework when a notification
//  is issued; look for notifications of feed updates and update the
//  feedWgtModel to reflect changes, update the feed's spinner model
FeedListAssistant.prototype.considerForNotification = function(params){
    if (params && (params.type == "update"))    {
        this.feedWgtModel.items = this.feeds.list;
        this.feeds.list[params.feedIndex].value = params.update;
        this.controller.modelChanged(this.feedWgtModel);

        // If stories exist in the this.featureIndexFeed, then start the rotation
        //  if not already started
// ** These next two lines are wrapped for book formatting only **
        if ((this.feeds.list[this.featureIndexFeed].stories.length > 0) &&
         (News.featureStoryTimer === null)) {
            var splashScreenElement = this.controller.get("splashScreen");
            splashScreenElement.hide();
            this.showFeatureStory();
        }
    }
    return undefined;
};

This method is called on any notification, but on update notifications it will set the affected feed’s spinner value to reflect whether an update is in progress or not, update the feed list, and start the feature story timer if needed.

In storyList-assistant.js, it’s used to look for changes to the displayed feed:

// considerForNotification - called when a notification is issued; if this
// feed has been changed, then update it.
StoryListAssistant.prototype.considerForNotification = function(params){
    if (params && (params.type == "update"))    {
        if ((params.feedIndex == this.feedIndex) && (params.update === false)) {
            this.storyModel.items = this.feed.stories;
            this.controller.modelChanged(this.storyModel);
        }
    }
    return undefined;
};

Among the scenes, only the active scene’s considerForNotification() method is called. That’s followed by calls to the active stage assistant and finally the application assistant. Since the application assistant is always the last on the chain, it can process what remains once the other assistants have had their chance at the notification block.

Back to the News: Creating Secondary Cards

In this final example, we’ll create a secondary card stage by adding the option to push a single feed into its own card. Add a “New Card” item to the pop-up submenu, which displays when the user taps the unread count on a specific feed in the list. When that item is tapped, it will push that feed into a new card. Figure 10-4 shows the new submenu and the card view showing the main feedList and the secondary card.

The secondary card

Figure 10-4. The secondary card

Secondary card stages are created like other stages. Here’s the case statement from the popupHandler method in feedList-assistant.js, triggered by the New Card submenu selector:

case "feed-card":
    Mojo.Log.info("Popup tear off feed to new card:",
      popupFeed.title);

    var newCardStage = "newsCard"+this.popupIndex;
    var cardStage = this.appController.getStageController(newCardStage);
    var feedList = this.feeds.list;
    var feedIndex = this.popupIndex;
    if(cardStage) {
        Mojo.Log.info("Existing Card Stage");
        cardStage.popScenesTo();
        cardStage.pushScene("storyList", this.feeds.list, feedIndex);
        cardStage.activate();
    } else  {
        Mojo.Log.info("New Card Stage");
        var pushStoryCard = function(stageController){
            stageController.pushScene("storyList", feedList,
              feedIndex);
        };
        this.appController.createStageWithCallback({
            name: newCardStage, lightweight: true},
            pushStoryCard, "card");
    }
    break;

The stage name must be unique unless you plan to reuse the same stage for each card; in this example, we use the feed index to form part of the stage name to keep it unique. When reusing the stage, popScenesTo() is used with pushScene() and activate() methods to maximize the stage with the storyList scene.

To enable this command option, add another choice to the submenu in the feedList assistant’s showFeed() method:

{label: "New Card", command: "feed-card"}

When users select this option by tapping the info icon on any feed in the feed list, a storyList scene will be pushed with the selected feed in its own card.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.20.159