Dialogs

You can use a Dialog widget to create a modal view for almost any purpose. A custom dialog is a conventional dialog, but it requires a widget assistant and an HTML template file. The Dialog widget is dynamically instantiated within a scene assistant, so there is a bit of overhead in using it both for you as a developer and at runtime. For errors, you should use the Error dialog. For presenting some simple options, use an Alert dialog. The simple built-in dialogs will be presented first, followed by a discussion of how to build custom dialogs with showDialog.

Error Dialog

You can post error messages in a modal dialog box with a fixed title of “Error,” a customizable message, and a confirmation button. The Error dialog must be used only with errors, since you can’t change the title; an example is shown in Figure 4-1.

You can post an Error dialog box with a single call:

Mojo.Controller.errorDialog(“Invalid Feed”, this.controller.window);
An Error dialog box

Figure 4-1. An Error dialog box

Alert Dialog

You can display a short message using an Alert dialog, with one or more HTML buttons presenting the selection options. This is the best option if you have either a message for the user, other than an error message, or want to present options that can be selected in the form of button selections:

this.controller.showAlertDialog({
    onChoose: function(value) {
        this.outputDisplay.innerHTML = "Alert result = " + value;
    },
    title: "Filet Mignon",
    message: "How would you like your steak done?",
    choices:[
      {label: "Rare", value: "rare", type: "affirmative"},
      {label: "Medium", value: "medium"},
      {label: "Overcooked", value: "overcooked", type: "negative"},
      {label: "Nevermind", value: "cancel", type: "dismiss"}
    ]});

This example presents four choices, as shown in Figure 4-2. Each button is labeled, with an optional button type corresponding to a palm-button class, and returns a value string.

An Alert dialog box

Figure 4-2. An Alert dialog box

Custom Dialogs

If the two simple dialogs don’t meet your needs, you can use the showDialog function, which can display any type of content to the user in the form of a modal dialog box. You can put anything into a custom dialog that you’d put into a scene, meaning almost any web content or Mojo UI content.

Back to the News: Adding an Add Feed dialog

In the previous chapter, we added a Drawer in the FeedListAssistant to support the Add Feed feature. It would be better to put this type of feature in a dialog; we will create an Add Feed dialog with the showDialog function and move the code used in the Drawer into the dialog.

Begin by replacing the addNewFeed method in feedlist-assistant.js with a call to showDialog():

//  addNewFeed - triggered by "Add..." item in feed list
FeedListAssistant.prototype.addNewFeed = function() {

   this.controller.showDialog({
      template: 'feedList/addFeed-dialog',
      assistant: new AddDialogAssistant(this, this.feeds)
   });
};

The arguments specify the dialog template and a reference to the assistant that handles the dialog. We create a new instance of the AddDialogAssistant, passing a reference to the FeedListAssistant and this.feeds, the feed object, and pass that in along with a reference to our addFeed-dialog.html template. The dialog template is simply an HTML template, but you should make use of some of the standard dialog box styles such as palm-dialog-content, palm-dialog-title, palm-dialog-separator, and palm-dialog-buttons to format and style your dialog boxes to fit in with webOS UI guidelines.

Create the HTML for the addFeed-dialog template by moving the code used in the previous chapter from feedList-scene.html to a new file, views/feedList/addFeed-dialog.html:

<div id="palm-dialog-content" class="palm-dialog-content">
    <div id="add-feed-title" class="palm-dialog-title">
        Add Feed
    </div>
    <div class="palm-dialog-separator"></div>
    <div class="textfield-group" x-mojo-focus-highlight="true">
        <div class="title">
           <div x-mojo-element="TextField" id="newFeedURL"></div>
        </div>
    </div>
    <div class="textfield-group" x-mojo-focus-highlight="true">
        <div class="title">
           <div x-mojo-element="TextField" id="newFeedName"></div>
        </div>
    </div>

    <div class="palm-dialog-buttons">
        <div x-mojo-element="Button" id="okButton"></div>
        <div x-mojo-element="Button" id="cancelButton"></div>
    </div>
</div>

The changes from the HTML used previously include the removal of the Drawer and the addition of the various palm-dialog styles and replacing the palm-list styling with the textfield-group.

The dialog assistant should be defined like a scene assistant with a creator function and the standard scene methods: setup, activate, deactivate, and cleanup.

Within a dialog assistant, you can set up widgets, push scenes, and generally do anything that you can do within a scene assistant. There is one major difference: the dialog assistant’s controller is a widget controller so it doesn’t have direct access to scene controller methods; instead the dialog assistant must use the calling scene assistant’s scene controller methods such as setupWidget. To facilitate this, the assistant property in the showDialog argument object passes the keyword this as an argument when calling the dialog’s creator function.

To create the AddDialogAssistant, we’ll move the code we used in the last chapter to generate the small form in the Drawer widget. Here that code is presented with some modifications in the AddDialogAssistant:

// ------------------------------------------------------------------------
// AddDialogAssistant - simple controller for adding new feeds to the list
//   when the "Add..." is selected on the feedlist. The dialog will
//   allow the user to enter the feed's url and optionally a name. When
//   the "Ok" button is tapped, the new feed will be loaded. If no errors
//   are encountered, the dialog will close otherwise the error will be
//   posted and the user encouraged to try again.
//
function AddDialogAssistant(sceneAssistant, feeds) {
    this.feeds = feeds;
    this.sceneAssistant = sceneAssistant;

    this.title = "";
    this.url = "";
    this.feedIndex = null;
    this.dialogTitle = "Add News Feed Source";
}

AddDialogAssistant.prototype.setup = function(widget) {
        this.widget = widget;

        // Set the dialog title to either Edit or Add Feed
// ** These next two lines are wrapped for book formatting only **
        var addFeedTitleElement =
          this.sceneAssistant.controller.get("add-feed-title");
        addFeedTitleElement.innerHTML = this.dialogTitle;

        // Setup text field for the new feed's URL
        this.sceneAssistant.controller.setupWidget(
            "newFeedURL",
            {
                  hintText: "RSS or ATOM feed URL",
                  autoFocus: true,
                  autoReplace: false,
                  textCase: Mojo.Widget.steModeLowerCase,
                  enterSubmits: false
            },
            this.urlModel = {value : this.url});

        // Setup text field for the new feed's name
        this.sceneAssistant.controller.setupWidget(
            "newFeedName",
            {
                    hintText: "Title (Optional)",
                    autoReplace: false,
                    textCase: Mojo.Widget.steModeTitleCase,
                    enterSubmits: false
            },
            this.nameModel = {value : this.title});

        // Setup OK  & Cancel buttons
        //   OK button is an activity button which will be active
        //   while processing and adding feed. Cancel will just
        //   close the scene
        this.okButtonModel = {label: "OK", disabled: false};
        this.sceneAssistant.controller.setupWidget("okButton",
          {type: Mojo.Widget.activityButton},
            this.okButtonModel);
        this.okButtonActive = false;
        this.okButton = this.sceneAssistant.controller.get("okButton");
        this.checkFeedHandler = this.checkFeed.bindAsEventListener(this);
        this.sceneAssistant.controller.listen("okButton", Mojo.Event.tap,
          this.checkFeedHandler);

        this.cancelButtonModel = {label: "Cancel", disabled: false};
        this.sceneAssistant.controller.setupWidget("cancelButton",
          {type: Mojo.Widget.defaultButton},
          this.cancelButtonModel);
        this.sceneAssistant.controller.listen("cancelButton", Mojo.Event.tap,
          this.widget.mojo.close);
};

// checkFeed  - called when OK button is clicked
AddDialogAssistant.prototype.checkFeed = function() {

        if (this.okButtonActive === true)  {
            // Shouldn't happen, but log event if it does and exit
            Mojo.Log.info("Multiple Check Feed requests");
            return;
        }

        // Check entered URL and name to confirm that it is a valid feedlist
        Mojo.Log.info("New Feed URL Request: ", this.urlModel.value);

        // Check for "http://" on front or other legal prefix; any string of
        // 1 to 5 alpha characters followed by ":" is ok, else
             prepend "http://"
        var    url = this.urlModel.value;
        if (/^[a-z]{1,5}:/.test(url) === false)    {
            // Strip any leading slashes
            url = url.replace(/^\/{1,2}/,"");
            url = "http://"+url;
        }

        // Update the entered URL & model
        this.urlModel.value = url;
        this.sceneAssistant.controller.modelChanged(this.urlModel);

        this.okButton.mojo.activate();
        this.okButtonActive = true;
        this.okButtonModel.label = "Updating Feed";
        this.okButtonModel.disabled = true;
        this.sceneAssistant.controller.modelChanged(this.okButtonModel);

        var request = new Ajax.Request(url, {
            method: "get",
            evalJSON: "false",
            onSuccess: this.checkSuccess.bind(this),
            onFailure: this.checkFailure.bind(this)
        });

};

// checkSuccess - Ajax request failure
AddDialogAssistant.prototype.checkSuccess = function(transport) {
        Mojo.Log.info("Valid URL - HTTP Status", transport.status);

        // DEBUG - Work around due occasion Ajax XML error in response.
        if (transport.responseXML === null && transport.responseText !== null) {
                Mojo.Log.info("Request not in XML format - manually converting");
// ** These next two lines are wrapped for book formatting only **
                transport.responseXML = new    DOMParser().
                  parseFromString(transport.responseText, "text/xml");
         }

        var feedError = News.errorNone;

        //  If a new feed, push the entered feed data on to the feedlist and
        //  call processFeed to evaluate it.
        if (this.feedIndex === null) {
            this.feeds.list.push({title:this.nameModel.value,
                url:this.urlModel.value, type:"", value:false, numUnRead:0,
                stories:[]});
            // processFeed - index defaults to last entry
            feedError = this.feeds.processFeed(transport);
        }
        else    {
            this.feeds.list[this.feedIndex] = {title:this.nameModel.value,
                url:this.urlModel.value, type:"", value:false, numUnRead:0,
                stories:[]};
            feedError = this.feeds.processFeed(transport, this.feedIndex);
        }

        // If successful processFeed returns errorNone
        if (feedError === News.errorNone)    {
            // update the widget, save the DB and exit
            this.sceneAssistant.feedWgtModel.items = this.feeds.list;
// ** These next two lines are wrapped for book formatting only **
            this.sceneAssistant.controller.modelChanged(
              this.sceneAssistant.feedWgtModel);
            this.widget.mojo.close();
        }
        else    {
            // Feed can't be processed - remove it but keep the dialog open
            this.feeds.list.pop();
            if (feedError == News.invalidFeedError)    {
                Mojo.Log.warn("Feed ",
                    this.urlModel.value, " isn't a supported feed type.");
                var addFeedTitleElement = this.controller.get("add-feed-title");
                addFeedTitleElement.innerHTML = "Invalid Feed Type - Please Retry";
            }

            this.okButton.mojo.deactivate();
            this.okButtonActive = false;
            this.okButtonModel.label = "OK";
            this.okButtonModel.disabled = false;
            this.sceneAssistant.controller.modelChanged(this.okButtonModel);
        }
};

// checkFailure  - Ajax request failure
AddDialogAssistant.prototype.checkFailure = function(transport) {
        // Log error and put message in status area
        Mojo.Log.info("Invalid URL - HTTP Status", transport.status);
        var addFeedTitleElement = this.controller.get("add-feed-title");
        addFeedTitleElement.innerHTML = "Invalid Feed Type - Please Retry";
};

// cleanup  - close Dialog
AddDialogAssistant.prototype.cleanup = function() {
        // TODO - Cancel Ajax request or Feed operation if in progress
        this.sceneAssistant.controller.stopListening("okButton",
          Mojo.Event.tap, this.checkFeedHandler);
        this.sceneAssistant.controller.stopListening("cancelButton",
          Mojo.Event.tap, this.widget.mojo.close);
};

There were several changes made to the previous version with the Drawer widget to create this version in a dialog:

Scene Assistant Methods

Change this.controller.* references to this.sceneAssistant.controller.* references, because the AddDialogAssistant must use the passed reference to the scene assistant for any scene controller methods.

Close

Add this.widget.mojo.close() after successfully adding the feed in checkOk. You will have to directly close the dialog by calling the close() method on the dialog widget. Notice that the widget element is passed as an argument to the dialog assistant’s setup method.

TextField Cleanup

Remove the code that explicitly cleared the text fields on exit; it isn’t needed, as the dialog scene is removed from the DOM entirely.

Swiping back in a default dialog box will close the dialog box, but a Cancel button is recommended for most dialog boxes to help novice users who may be confused by its absence. You can set the optional preventCancel to true in the showDialog call arguments to stop the back gesture from canceling the dialog box; by default, preventCancel is set to false. Figure 4-3 shows the results of these changes and the Add Feed dialog.

An Add Feed dialog box

Figure 4-3. An Add Feed dialog box

Get Palm webOS now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.