Scrollers

The Scroller widget provides the scrolling behavior in Mojo. A scroller is installed automatically in every scene, and you can have any number of additional scrollers anywhere in the DOM.

Note

You can disable the scroller in a scene by setting the disableSceneScroller property to true in the scene arguments to pushScene.

In the current release of Mojo, you can select one of six scrolling modes, specified in the mode property of the widget’s attributes:

free

Allow scrolling along both the horizontal and vertical axes.

horizontal

Allow scrolling only along the horizontal axis.

vertical

Allow scrolling only along the vertical axis.

dominant

Allow scrolling along the horizontal or vertical axis, but not both at once. The direction of the initial drag will determine the scrolling axis.

horizontal-snap

In this mode, scrolling is locked to the horizontal axis, but snaps to points determined by the position of the block elements found in the model’s snapElements property. As the scroller scrolls from snap point to snap point it will send a propertyChange event.

vertical-snap

This mode locks scrolling to the vertical axis, and snaps to points determined by the elements in the snapElements property array.

Upon rendering, the widget targets its single child element for scrolling. If it has more than one child element, it will create a single div to wrap the child elements. It will never update this element, so if you replace the contents of a Scroller widget after it is instantiated, scrolling might not work. Instead, put another block element inside the scroller and update its contents as needed.

Note

The size of the scroller’s target div, the child element, must be set in CSS. By default, the div will expand to the size of the contents. You must constrain the width, on a horizontal scroller, or the height, on a vertical scroller, within your CSS or the scroller will not function.

A Scroller widget will ignore any drag start event that doesn’t indicate a valid scroll start for its mode setting, so you can nest scrollers if they don’t conflict. For example, you can put a small horizontal scroller inside the default scene vertical scroller. This configuration will pass horizontal swipes to the horizontal scroller, but vertical swipes on the horizontal scroller, or any kinds of swipes outside the horizontal scroller, will be passed to the scene scroller.

Back to the News: Adding a featured feed Scroller

In this example, a rotating feature story will be added to the News application. This will present a title and story from the feed list for 5 seconds, after which time it will be replaced by another story. This rotating feature story will be displayed in a fixed-size area above the feedListWgt to allow for a stable view, but we’ll attach a vertical scroller to allow users to read the full story if it is longer than the view, and we’ll enclose it all in a drawer so that users can selectively enable or disable this view.

Start by modifying the feedList scene (feedList-scene.html) to add an icon to the palm-header to serve as a tap target to open and close the drawer. We’re going to change the image to match the state of the drawer by using two different classes, featureFeed-close and featureFeed-open. We’ll start with the drawer closed.

The Scroller is declared and encloses featureStoryDiv, which will be fixed to a specific height through CSS and filled by the story title and text. This is all placed above the feedList widget in the scene’s layout.

Within the featureStoryDiv, define the splashScreen div to fill the space for the initial launch case where there are no stories to display. The update-image style will insert the News icon alongside a copyright notice for the application. We’ll hide this div when there are stories to display; each story title and text will be inserted in the following divs, identified as featureStoryTitle and featureStory:

<div id="feedListScene">
    <div id="feedListMain">

        <!--    Rotating Feature Story                           -->
        <div id="feedList_view_header" class="palm-header left">
            Latest News
            <div id="featureDrawer" class="featureFeed-close"></div>
        </div>
        <div class="palm-header-spacer"></div>
        <div x-mojo-element="Drawer" id="featureFeedDrawer">
            <div x-mojo-element="Scroller" id="featureScroller" >
                <div id="featureStoryDiv" class="featureScroller">
                    <div id="splashScreen" class="splashScreen">
                        <div class="update-image"></div>
                        <div class="title">News v0.8<span>#{version}</span>
                            <div class="palm-body-text">
                                Copyright 2009, Palm®
                            </div>
                        </div>
                    </div>
                    <div id="featureStoryTitle" class="palm-body-title">
                    </div>
                    <div id="featureStory" class="palm-body-text">
                    </div>
                </div>
            </div>
        </div>

        <!--    Feed List                                         -->
        <div class="palm-list">
            <div x-mojo-element="List" id="feedListWgt"></div>
        </div>
    </div>
</div>

Set up the Drawer and the Scroller in the feedList-assistant.js. Set up a listener for taps to the new tap target in the header and then the Drawer widget with the state defined by a new global, News.featureFeedEnable. In Chapter 6, we’ll add saved preferences and we will retain the drawer’s state at that time.

The featureScrollerModel defines a single property, the scroller mode, in this case set to vertical, and the Scroller is set up with just its ID and model as arguments. The scroller simply responds to vertical swipes to scroll the content, and will generate scrolling events if you want to receive them, although you don’t normally need to:

// Setup header, drawer, scroller and handler for feature feeds

    this.featureDrawerHandler = this.toggleFeatureDrawer.bindAsEventListener(this);
    this.controller.listen("featureDrawer", Mojo.Event.tap,
        this.featureDrawerHandler);

    this.controller.setupWidget("featureFeedDrawer", {},
      this.featureFeedDrawer = {open: News.featureFeedEnable});

    this.featureScrollerModel = {
        mode: "vertical"
        };

    this.controller.setupWidget("featureScroller", this.featureScrollerModel);
    this.readFeatureStoryHandler = this.readFeatureStory.bindAsEventListener(this);
    this.controller.listen("featureStoryDiv", Mojo.Event.tap,
        this.readFeatureStoryHandler);

    // If feature story is enabled, then set the icon to open
    if (this.featureFeedDrawer.open === true) {
        this.controller.get("featureDrawer").className = "featureFeed-open";
    } else  {
        this.controller.get("featureDrawer").className = "featureFeed-close";
    }

A listener is set up to handle taps in the feature story div, but it is unrelated to the Scroller. If users see a story they want to read further, they can scroll the story or tap it to go to the storyView scene with that story. And we’ll finish by setting the featureDrawer element’s className to match the state of the drawer; this is also a future provision for using saved preferences when the scene could be activated with the drawer in the open state.

The CSS completes the implementation by fixing the size of the Scroller div and formatting the contents. The first two rules support the tap target, either open or closed, and the drawer background. You’ll have to add the appropriate images to the images folder of your application to reproduce this:

/* FeedList Header styles for feature drawer and selection */

.featureFeed-close {
    float:right;
    margin: 8px −12px 0px 0px;
    height:35px;
    width: 35px;
    background: url(../images/details-open-arrow.png) no-repeat;
}

.featureFeed-open {
    float:right;
    margin: 8px −12px 0px 0px;
    height:35px;
    width: 35px;
    background: url(../images/details-closed-arrow.png) no-repeat;
}

.palm-drawer-container {
    border-width: 20px 1px 20px 1px;
    -webkit-border-image: url(../images/palm-drawer-background-2.png)
        20 1 20 1 repeat repeat;
    -webkit-box-sizing: border-box;
    overflow: visible;
}

.featureScroller {
    height: 100px;
    width: 280px;
    margin-left: 20px;
}

The palm-drawer-container selector sets the drawer container’s dimensions and applies a white background to contrast with the scene’s main background. The featureScroller style bounds the scroller’s height because we have a vertical scroller; in the case of a horizontal scroller, we’d bound the width. If you aren’t careful with your CSS, the scroller will not behave as expected.

The rest of the sample is related to handling the feature story. Create a new method, toggleFeatureDrawer(), to open or close the drawer. If the drawer is open, the method will close it by setting the drawer’s model to false, and setting the div class to featureFeed-close. If the drawer is closed, the actions are reversed to open the drawer and we’ll start the story rotation if it’s not already running. The controller’s modelChanged() method is called to signal the model changes to the drawer widget:

// toggleFeatureDrawer - handles taps to the featureFeed drawer. Toggle
//   drawer and icon class to reflect drawer state.
FeedListAssistant.prototype.toggleFeatureDrawer =  function(event) {
       var featureDrawer = this.controller.get("featureDrawer");
       if (this.featureFeedDrawer.open === true) {
           this.featureFeedDrawer.open = false;
           News.featureFeedEnable = false;
           featureDrawer.className = "featureFeed-close";
       } else {
           this.featureFeedDrawer.open = true;
           News.featureFeedEnable = true;
           featureDrawer.className = "featureFeed-open";

           // If there's some stories in the feedlist, then start
           // the story rotation even if the featureFeed is disabled as we'll use
           // the rotation timer to update the DB
           if(this.feeds.list[this.featureIndexFeed].stories.length > 0) {
               var splashScreenElement = this.controller.get("splashScreen");
               splashScreenElement.hide();
               this.showFeatureStory();
           }
       }
       this.controller.modelChanged(this.featureFeedDrawer);
 };

Create the showFeed() method to present the feature story and set up the timer for the next story. We set the timer default to 5 seconds (5000 milliseconds) in News.featureStoryInterval, another new global variable, added to stage-assistant.js. If the timer has been set, rotate the story by taking the next story in the current feed or the first story in the next feed if it’s at the end of the current feed. We will also add some logic to strip URLs and other HTML from the title and text:

// -------------------------------------------------------------------
// Feature story functions
//
// showFeatureStory - simply rotate the stories within the
// featured feed, which the user can set in their preferences.
FeedListAssistant.prototype.showFeatureStory = function() {

    // If timer is null, either initial story or restarting. Start with
    // previous story..
    if (News.featureStoryTimer === null)    {
    // ** These next two lines are wrapped for book formatting only **
        News.featureStoryTimer = this.controller.window.setInterval(
          this.showFeatureStory.bind(this), News.featureStoryInterval);
    }

    else {
        this.featureIndexStory = this.featureIndexStory+1;
        // ** These next two lines are wrapped for book formatting only **
        if(this.featureIndexStory >=
          this.feeds.list[this.featureIndexFeed].stories.length) {
            this.featureIndexStory = 0;
            this.featureIndexFeed = this.featureIndexFeed+1;
            if (this.featureIndexFeed >= this.feeds.list.length)    {
                this.featureIndexFeed = 0;
            }
        }
    }

    var summary = this.feeds.list[this.featureIndexFeed].stories[
       this.featureIndexStory].text.replace(/(<([^>]+)>)/ig,"");
    summary = summary.replace(/http:S+/ig,"");
    var featureStoryTitleElement = this.controller.get("featureStoryTitle");
    // ** These next two lines are wrapped for book formatting only **
    featureStoryElement.innerHTML = 
        unescape(this.feeds.list[this.featureIndexFeed].stories[
          this.featureIndexStory].title);
    var featureStoryElement = this.controller.get("featureStory");
    featureStoryElement.innerHTML = summary;

    // Because this is periodic and not tied to a screen transition, use
    // this to update the db when changes have been made

    if (News.feedListChanged === true)    {
        News.feedListChanged = false;

        this.feedWgtModel.items = this.feeds.list;
        this.controller.modelChanged(this.feedWgtModel, this);
    }

};

// readFeatureStory - handler when user taps on feature story;
//   will push storyView with the current feature story.
FeedListAssistant.prototype.readFeatureStory = function() {
    Mojo.Controller.stageController.pushScene("storyView",
      this.feeds.list[this.featureIndexFeed],
      this.featureIndexStory);
};

Following showFeatureStory() is readFeatureStory(), which simply pushes the storyView scene with the current feature story.

When you run the application, you’ll see a different look, with the feature story now filling the top third of the feedList scene, as shown in Figure 5-5.

News with scrolling feature feed

Figure 5-5. News with scrolling feature feed

There’s still some cleanup needed. We must maintain this.featureIndexFeed during list reordering, and delete. You can see the changes made to the listDeleteHandler() and listReorderHandler() in the feedList-assistant.js in the News source listing in Appendix D.

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

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