Porting a W3C widget to webOS should not take a lot of effort, should it? webOS is built on top of web technologies, and in my mind the web is tagged “#universal”. But unfortunately, webOS isn’t universal. It’s different. I’m going to show you which differences you need to be aware of when doing cross-platform development and your application is supposed to run on webOS.

Background

Developing applications for mobile devices using HTML, CSS, and JavaScript is possible through different runtimes and frameworks. On Nokia devices, you can use the Vodafone Apps Manager (a runtime for W3C widgets) as well as Nokia Web Runtime (Nokia WRT). PhoneGap is a framework that allows building of applications for iPhone, Android, Blackberry, Windows Mobile, and Nokia WRT with web technologies. On all of this platforms you basically throw in a base HTML file (e.g. index.html) that contains references to scripts and stylesheets. Easy, isn’t it? But not on webOS …

webOS

webOS uses a concept of stages and scenes. A “stage” represents a card (similar to a window in desktop operating systems), whereas a “scene” can be imagined as a layer on a stage. Each stage consists of a stack containing at least one scene. While I really like this concept, I am of the opinion that using this model shouldn’t be obligatory. It provides an elegant possibility to develop webOS based GUIs, but gets in your way as soon as you want to deploy your app on different platforms. All porting issues pointed out in this post arise from the stage/scene model.

First Try

When trying to repackage a W3C widget for webOS for the first time, I used the palm-generate command line tool to create an empty app skeleton and copied the source code into it. After packaging with palm-package and deploying on the emulator I was happy to see that everything seemed to work just fine. Far from it!

Issue: Scrolling

When simply converting a W3C widget (or any browser-based application) to a native webOS app you will notice that everything works fine except for scrolling. If the contents of the application are longer than the viewport, they are simply cut off, and scrolling isn’t possible. To enable scrolling in webOS it is necessary to make use of a mojo-scrollerElement (“Mojo” is the name of palm’s framework). The easiest way to achieve that is to “push a scene onto the stack”:

  1. Include the mojo framwork into your HTML file by adding a script tag before you include any other scripts or stylesheets:
    <script src="/usr/palm/frameworks/mojo/mojo.js" type="text/javascript" x-mojo-version="1"></script>

    Mojo will add a whole bunch of webOS-specific stylesheets to your application. If you like, you can use the classes defined there to achieve a more “native” feeling by using palm-specific classes.

  2. If you created the app skeleton with palm-generate you will find the constructor StageAssistant in the file app/assistants/stage-assistant.js. An instance of StageAssistant will be generated automatically on application start, and its setup method will be called. Add the call this.controller.pushScene("main"); to the empty setup method of the prototype. This will create a new scene called “main” (which is a set of div-Elements inserted into document.body).
  3. Generate the scene skeleton using palm-generate:
    palm-generate -t new_scene -p "name=main" <app-dir>

    When “pushing” a scene, the corresponding assistant is being instantiated, and the contents of the scene’s main view file are made visible (while everything else is hidden behind the newly created scroller). The two files are app/assistants/main-assistant.js. and app/views/main-scene.html.

  4. The last step is to transfer the DOM/HTML of your application into the scene. My approach is to remove all DOM nodes from document.body and reinsert them into the scene element. It is also possible to use innerHTML, but already registered event handlers will be lost. Change the setup method of StageController to:
    StageAssistant.prototype.setup = function() {
        var body = this.controller.body,
            fragment = this.controller.document.createDocumentFragment();

        while (body.firstChild) {
            fragment.appendChild(body.firstChild);
        }

        this.controller.pushScene("main", fragment);
    }

    The second parameter passed to pushScene will be passed to MainAssistant as soon as it is instantiated.

    To reinsert the nodes into the DOM change main-assistant.js to:

    function MainAssistant(nodes) {
        this.nodes = nodes;
    }

    MainAssistant.prototype.setup = function() {
        this.controller.sceneElement.appendChild(this.nodes);
        this.nodes = null; // unset to release references
    }

    This way, your original nodes are transferred to the visible and scrollable “main” scene. Don’t forget that any CSS selector like body > .foo will stop working as intended, because your DOM is wrapped by two additional div elements!

After these steps, scrolling finally works in apps ported to webOS. The code shown here is very generic. It can be used for every application. You should be aware that scrolling via window.pageYOffset is not possible, because the scrollable element is the mojo scroller, not the body element.

Event listeners

Developers who like registering event listeners on document.body will notice that those quit working after cut’n’pasting the DOM nodes to the scene elements. The reason is simple: in webOS, events only bubble up to the scene element (the inner one of the DIVs mentioned above). Simply don’t do that. And if you are using jQuery’s live(), stop using it. Sorry. Don’t blame it on me.

Scrolling, Part Ⅱ

Everything seems to work pretty well now, right? Well, there’s one more thing …

When you scroll (or drag) your app, and stop dragging above a clickable element, that element will be clicked. It took me quite some time to figure out the reason, because the same HTML/JavaScript will work flawlessly in the browser built into webOS. You need to listen for the “mojo-tap” event rather than for the “click” event. The “click” event will be fired after a drag has ended, but the tap event will only be fired when a “true” tap has taken place. I really can’t understand why it has to be that way, and if it wouldn’t have been easier not to alter the default behavior of WebKit.

The method I’m using is to have a variable containing the name of the event to bind to. That way, an application can still run on different platforms:

var myapp = { CLICK_EVENT : "click" };

if (typeof Mojo != "undefined") {
    myapp.CLICK_EVENT = Mojo.Event.tap; // contains "mojo-tap"
}

/* snip */
somElm.addEventListener(myapp.CLICK_EVENT, function() { /* … */ }, false);

Why?

When I was hearing about webOS for the first time, I really looked forward to have “it [the Web] as the SDK” of the OS (quote from Dion Almaer). And while the SDK offers some nice features – like the stage/scene architecture – it really gets in your way as soon as you don’t want to code exclusively for it. Unfortunately, you don’t have any choice, say some sort of “compatibility mode” to the web (couldn’t resist this one). You have to bend to webOS and stick to its rules.

Apparently Palm is working on better compatibility. On Ajaxian you can find an article stating that “The company [Palm] is involved with the BONDI and W3C widgets standardisation effort” (quoting Ben Galbraith and Dion Almaer). Honestly: Shouldn’t somebody have considered that in the first place?

To me, the web is a universal platform. We’ve had many issues with proprietary “solutions” in the past millennium and still have them. I don’t like seeing proprietary features being introduced once more. It is obvious that functionality of the operating systems must be accessible in some way. But does that imply breaking things that have worked before?

I would love to see the possibility of making simple applications run on webOS without too much efforts. At least it should be possible to run W3C widgets without big hassles.

Sometimes one might be supposed to think that Palm isn’t really interested in compatibility. The acceptance criteria of their “App Catalog” state that “They [the applications] need to be written specifically for webOS and not delivered through the browser.” Whatever that may mean.