Advanced Lists

Lists were introduced in Chapter 3 with several extensive examples. Even so, some major list features weren’t touched on. We’ll take a look at some more advanced features here.

With all list widgets, you can intervene in the middle of the list rendering to provide some intermediate formatting to list items or to insert dividers between rows. After a brief review of those features, we will add a Filter List widget to News to implement a search feature. This is a good example of a dynamic list, something you can use in many different types of applications.

Formatters

The formatters property is a hash of property names to formatter functions, like this:

{timeValue: this.myTimeFormatter, dayOfWeek: this.dayIndexToString, ... }

Before rendering the relevant HTML templates, the formatter functions are applied to the objects used for property substitution. The keys within the formatters hash are property names to which the formatter functions should be applied.

The original objects are not modified, and the formatted properties are given modified new names so that the unformatted value is still accessible from inside the HTML template. Formatted values have the text “Formatted” appended to their names. In the example above, the HTML template could refer to #{timeValueFormatted} in order to render the output from the myTimeFormatter() function. Formatter functions receive the relevant property value as the first argument, and the appropriate model object or items element as the second.

Dividers

You can add dividers to your lists; they are particularly useful for long lists. You will specify the function name to the dividerFunction property and a template to dividerTemplate. If no template is specified, the framework will use the default, a single-letter alpha divider (list-divider.html, styled with the palm-alpha-divider class).

The divider function works similar to a data formatter function. It is called with the item model as the sole argument during list rendering, and it returns a label string for the divider.

Filter Lists

Use a Filter List widget when your list is best navigated with a search field, particularly one where you would like to instantly filter the list as each character is typed into the field. It is intended to display a variable length list of objects, built by a special callback function.

The widget includes a text field displayed above a list, where the list is the result of applying the contents of the text field through an application-specific callback function against some off-screen data source. The text field is hidden when empty, but it is given focus as soon as any key input is received. At the first keystroke, the field is displayed with the key input (after a delay; specified via the delay attribute—default is 300ms), and the framework calls the function specified by filterFunction.

The framework calls the filterFunction, which is similar to the itemsCallback function, in the base List widget (see Chapter 3) when data is needed for displaying list entries. You provide the filterFunction, with arguments for the list widget element, offset, and count, similar to itemsCallback, plus an additional argument, filterString.

It is understood that the requested data may not be immediately available. Once the data is available, the given widget’s noticeUpdatedItems() method should be called to update the list. It’s acceptable to call the noticeUpdatedItems() immediately if desired, or any amount of time later. Lengthy delays may cause various scrolling artifacts, however.

The filter list will display a spinner in the text field while the list is being built, and it is replaced with an entry count when done. To set the count properly, call the widget’s setCount(totalSubsetSize), where totalSubsetSize is the number of entries in the list. To set the list length, call setLength(totalSubsetSize); the length is a dependency of some internal widget functions and needs to be set accurately.

Back to the News: Adding a search field

Search is one of the best applications for the Filter List widget. You can start typing on the feedList scene and quickly search all feeds for a keyword, viewing the results in the same list format used for the individual feed lists. It’s simple to access and powerful.

The Filter List is declared and set up conventionally, but requires a filter function called to process the keyword entries and returns a list for display. In the News design, we’re going to put the search results into a temporary list that is structured like the feed list. We will display it using the storyList assistant.

Because the Filter List search field is going into the feedList view, it will be necessary to hide the feed list and feature story when in search mode. Start by adding the Filter List declaration at the top of feedList-scene.html and wrap the rest of the scene in a div with feedListMain as its ID. This will be used later to hide the rest of the scene:

<!--    Search Field                                         -->
    <div id="searchFieldContainer">
        <div x-mojo-element="FilterList" id="startSearchField"></div>
    </div>

    <div id="feedListMain">
    <!--    Rotating Feature Story      -->
      .
      .
      .

Now set up the widget in feedList-assistant.js, reusing the storyList templates, preparing to format the search results in a storyList scene. Identify the filter function as this.searchList, and use a standard delay of 300 milliseconds. This is the default, so this step can be omitted, but you may need to tune the behavior, so it’s not a bad idea to add it at the beginning:

// Setup the search filterlist and handlers;
this.controller.setupWidget("startSearchField",
    {
        itemTemplate: "storyList/storyRowTemplate",
        listTemplate: "storyList/storyListTemplate",
        filterFunction: this.searchList.bind(this),
        renderLimit: 70,
        delay: 300
    },
    this.searchFieldModel = {
        disabled: false
    });

this.viewSearchStoryHandler = this.viewSearchStory.bindAsEventListener(this);
this.controller.listen("startSearchField", Mojo.Event.listTap,
    this.viewSearchStoryHandler);
this.searchFilterHandler = this.searchFilter.bindAsEventListener(this);
this.controller.listen("startSearchField", Mojo.Event.filter,
    this.searchFilterHandler, true);

Add two listeners, one for the tap event and the other for a filter event. The filter event is used on Filter Field and Filter List only. It is sent after the defined delay once the filter field is activated, on the first character entry, and immediately when the field is cleared. Listening for this event allows you to do some pre- and postprocessing with the widget or the scene.

With News, we want to hide the rest of the scene when the first character is typed, and restore it when the field is cleared. To do this, add this new method in the feedList assistant; it simply calls the Prototype methods hide() and show() of the div element, feedListMain, in feedlist-scene.html:

// searchFilter - triggered by entry into search field. First entry will
//  hide the main feedList scene - clearing the entry will restore the scene.
//
FeedListAssistant.prototype.searchFilter = function(event)    {
    Mojo.Log.info("Got search filter: ", event.filterString);
    var feedListMainElement = this.controller.get("feedListMain");
    if (event.filterString !== "")    {
        //    Hide rest of feedList scene to make room for search results
        feedListMainElement.hide();
    }    else    {
        //    Restore scene when search string is null
        feedListMainElement.show();
    }

};

After the filter event is sent, the framework calls the function assigned to the filterFunction property in the Filter List widget’s attributes; in this case, this.searchList(), which is added also to feedList-assistant.js and shown here:

// searchList - filter function called from search field widget to update the
//  results list. This function will build results list by matching the
//  filterstring to the story titles and text content, and then return the
//  subset of the list based on offset and size requested by the widget.
//
FeedListAssistant.prototype.searchList = function(filterString,
  listWidget, offset, count)    {

    var subset = [];
    var totalSubsetSize = 0;

    this.filter = filterString;

    // If search string is null, return empty list, otherwise build results list
    if (filterString !== "")    {

        // Search database for stories with the search string; push matches
        var items = [];

        // Comparison function for matching strings in next for loop
        var hasString = function(query, s) {
            if(s.text.toUpperCase().indexOf(query.toUpperCase())>=0) {
                return true;
            }
            if(s.title.toUpperCase().indexOf(query.toUpperCase())>=0) {
                return true;
            }
            return false;
        };

        for (var i=0; i<this.feeds.list.length; i++) {
            for (var j=0; j<this.feeds.list[i].stories.length; j++) {
                if(hasString(filterString, this.feeds.list[i].stories[j])) {
                    var sty = this.feeds.list[i].stories[j];
                    items.push(sty);
                }
            }
        }

        this.entireList = items;
        Mojo.Log.info("Search list asked for items: filter=",
            filterString, " offset=", offset, " limit=", count);

        // Cut down list results to just the window asked for by the widget
        var cursor = 0;
        while (true) {
            if (cursor >= this.entireList.length) {
                break;
            }

            if (subset.length < count && totalSubsetSize >= offset) {
                subset.push(this.entireList[cursor]);
            }
            totalSubsetSize++;
            cursor++;
        }
    }

    // Update List
    listWidget.mojo.noticeUpdatedItems(offset, subset);

    // Update filter field count of items found
    listWidget.mojo.setLength(totalSubsetSize);
    listWidget.mojo.setCount(totalSubsetSize);

};

The function definition is filterFunction (filterString, listWidget, offset, limit), using the arguments shown in Table 5-1.

Table 5-1. FilterFunction arguments

Argument

Type

Description

filterString

String

The contents of the filter field or the search string to be used

listWidget

Object

The DOM node for the list widget requesting the items

offset

Integer

Index in the list of the first desired item model object (zero-based)

limit

Integer

Count of the number of item model objects requested

Assuming the filterString isn’t empty, which it shouldn’t be, the first part of the method will do a primitive match against all the story titles and text content across all feeds. The results are pushed into the items array and assigned to this.entireList when complete.

The list is cut down to just the portion of the list that was requested by the offset and the count, and is assigned to subset, which is returned with the offset by calling listWidget.mojo.noticeUpdatedItems(offset, items), using the arguments shown in Table 5-2. This is a method of listWidget, an argument passed by the framework.

Table 5-2. NoticeUpdatedItems arguments

Argument

Type

Description

offset

Integer

Index in the list of the first object in items; usually the same as the offset passed to the itemsCallback

items

Array

An array of the list item model objects that have been loaded for the list

Finish up with calls to listWidget.mojo.setLength(totalSubsetSize) and listWidget.mojo.setCount(totalSubsetSize) to set the list length and the results count for the counter displayed in the filter field.

With these changes, users can type at any time into the feedList scene to see the filter field display and the results presented in list form below, similar to what is shown in Figure 5-11.

News with a search filter list

Figure 5-11. News with a search filter list

After the list is built, the tap event indicates a user selection of a list entry, just as it does for a conventional list. Since the search list is built as a stories array, News responds to a tap by creating a temporary feed and pushing the storyView scene with that feed. Here’s the viewSearchStory() method:

// viewSearchStory - triggered by tapping on an entry in the search
//   results list  will push the storyView scene with the tapped story.
//
FeedListAssistant.prototype.viewSearchStory = function(event)    {
    var searchList = {
      title: "Search for: "+this.filter, stories: this.entireList
    };
    var storyIndex = this.entireList.indexOf(event.item);

    Mojo.Log.info("Search display selected story with title = ",
        searchList.title, "; Story index - ", storyIndex);
    Mojo.Controller.stageController.pushScene("storyView",
      searchList, storyIndex);

};

As shown in Figure 5-12, as well as viewing the selected story, users can tap Next and Previous to view each story in the results list.

News with a search story view

Figure 5-12. News with a search story view

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

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