Gadgets: Write Once, Run Everywhere

In this article, I will show you how to develop a web gadget (i.e. a gadget designed to run on Live.com or Windows Live Spaces) that will run on Windows Vista Sidebar without modification to the JavaScipt or CSS! This is something that Microsoft has hinted at ever since Sidebar gadgets were first announced. Using techniques described in this article, that dream could become a reality.

Of the two environments, a web gadget places the most requirement on your JavaScript. You must design your code to be encapsulated into a JavaScript class and use Atlas bindings, etc. So lets start off by first writing a simple web gadget.

Come on Donavon, another clock gadget?

SampleGadgetScreen Developer As my example gadget, I chose to write a simple digital clock that displays the time using the current locale’s conventions. It’s very basic, but actually makes quite a good looking gadget. 

Let’s walk through each of the web gadget’s components; first the XML manifest:

<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:binding="http://www.live.com">
    <channel>
        <title>Sample Gadget</title>
        <description>A sample web gadget</description>
        <language>en-us</language>
        <generator>Gadget Project Template by LiveGadgets.net</generator>
        <pubDate>Tue, 19 Nov 2006 00:00:00 GMT</pubDate>
        <binding:type>SampleNamespace.SampleGadget</binding:type>
        <item>
            <link>SampleGadget.js</link>
        </item>
        <item>
            <link binding:type="css">style.css</link>
        </item>
    </channel>
</rss>

Nothing crazy here. It’s a pretty standard web gadget manifest. A web gadget manifest nothing more than an standard RSS feed with some special binding elements. The gadget framework will each if the item elements relative the the manifest URL. After all items are loaded, the frmework will instantiate the class specified in the element binding:type and call its initialize() function.

The RSS purists reading this will notice that our item elements are incomplete. The RSS 2.0 Specification states “All elements of an item are optional, however at least one of title or description must be present“. The gadget framework is forgiving of this omission.

Now for a look at the JavaScript code:

registerNamespace("SampleNamespace");

SampleNamespace.SampleGadget = function(p_el, p_args, p_namespace) {
    SampleNamespace.SampleGadget.initializeBase(this, arguments);

    var timer;

    this.initialize = function(p_objScope) {
        SampleNamespace.SampleGadget.getBaseMethod(this,
            "initialize", "Web.Bindings.Base").call(this, p_objScope);

        var lastSecs = 0;

        function displayTime() {
            var now = new Date().getTime(); //get the current time in msecs
            var nowSecs = Math.floor(now/1000); //round to nearest second
            if (nowSecs > lastSecs) { //don't display if same as last time
                lastSecs = nowSecs;
                p_el.innerHTML =  new Date(now).toLocaleTimeString();
            }
        }
        timer = setInterval(displayTime,100);
        displayTime();
    };
    SampleNamespace.SampleGadget.registerBaseMethod(this, "initialize");

    this.dispose = function(p_blnUnload) {
        SampleNamespace.SampleGadget.getBaseMethod(this,
            "dispose", "Web.Bindings.Base").call(this, p_blnUnload);
        clearInterval(timer);
        timer = null;
    };
    SampleNamespace.SampleGadget.registerBaseMethod(this, "dispose");


};
SampleNamespace.SampleGadget.registerClass("SampleNamespace.SampleGadget",
    "Web.Bindings.Base");

Again, standard web gadget JavaScript. If you are familiar with Microsoft web gadgets (and you probably are if you’re reading this article) then this should be petty basic stuff for you. Even so, let’s review.

Encapsulation

We start by registering a namespace that we will use. This was Microsoft’s attempt to encapsulate each gadget’s code into a relatively unique class so that if multiple gadgets from multiple vendors appeared on the same page, the the code would not override on another. C# developers are very familiar with namespaces, but this is something rather new concept to JavaScript developers. Let’s look at what could happen if namespaces weren’t used. 

Consider this: Company A has written a calculator gadget and named the class “Calculator”. Company B has also written a calculator gadget and guess what.. they named their class “Calculator” too. Gee. What do you suppost the odds of that happening were?

But… what if both companies had used namespaces to create their calculator gadgets? Company A would then likely have used the class “CompanyA.Calculator” and Company B would have use “CompanyB.Calculator”. This time there are no conflicts. Both companies’ calculator gadgets will live in harmony on the same HTML page.

The Windows Live Gadget Developer’s Guide puts it this way:

The namespace you use in the registerNamespace() call for your Gadget is used to uniquely identify your Gadget on the platform. To avoid collision with other Gadgets, you should follow the guidelines below: 

  • Use Pascal casing, no underscores.
  • Namespace should be in the following format: CompanyName.TeamName[.Feature][.SubFeature]
  • For independent developers, namespace should be in the following format: FirsNameLastName.MiddleName[.Feature][.SubFeature]
  • Some examples of namespaces following these conventions are: “Microsoft.Live.GadgetSDK“, “eBay.Auction.Search“, “TimSullivan.George.Weather

It should be said that this is less of an issue than it was, say a year ago. Microsoft now sandboxes each “non-approved” gadget within its own iframe, so the only thing you may interfere with is the framework itself and not another gadget.

Enough ranting about encapsulation. Lets get back to our example.

Initialize

This is what the framework calls immediately after the class is instantiated. This is normally where you put meat of your gadget code. In our case, we simply setup a timer to periodically call displayTime(), the function that actually outputs the time.

Dispose

dispose() is called by the framework when the gadget exits (i.e. you close the gadget or navigate to another web page). Our code doesn’t have to do much here by shut down the timer.

Making it work on Sidebar

How can we take the code you see above and make it work on the Windows Vista Sidebar? Well the first thing you will need is a Sidebar XML manifest.

<?xml version="1.0" encoding="utf-8" ?>
<gadget>
  <name>Sample Gadget</name>
  <namespace>SampleNamespace</namespace>
  <version>1.0.0.0</version>
  <description>A sample Sidebar gadget</description>
  <hosts>
    <host name="sidebar">
      <base type="HTML" apiVersion="1.0.0" src="gadget.htm" />
      <permissions>full</permissions>
      <platform minPlatformVersion="0.3" />
    </host>
  </hosts>
</gadget>

Unlike a web gadget, a Sidebar gadget must specify an HTML file. This is because web gadgets are simple snippets that run on someone else’s page (i.e. Live.com or Spaces). When running on the Sidebar, your gadget is the entire web page.

Notice that unlike a web gadget, a Sidebar gadget’s class is not specified in the manifest. We’ll do that in the HTML file itself.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <link href="style.css" rel="stylesheet" type="text/css" />
    <script src="WebGadgetEmulation.js" type="text/javascript"></script>
    <script src="SampleGadget.js" type="text/javascript"></script>
    <style type="text/css">
    html, body {
        margin:0;
        padding:0;
    }
    </style>
</head>
<body>
    <div id="application" class="SampleNamespace_SampleGadget"></div>
</body>
</html>

You should all recognize this rather simple HTML. There are however, two things I would like to point out. The first is the value of the class attribute of the application div and the other is the script tag that loads the JavaScript file WebGadgetEmulation.js.

The value you specify for the application class is the JavaScript class of our gadget; the same value set in the binding:type of the web gadget manifest shown earlier. Notice, however, that instead of periods, we use underscores. This is because this is a CSS class which can not contain underscores. All underscores in the class name will be converted to periods to determine the JavaScript class. Remember that Microsoft’s first recommendation for naming your namespace stated that it does not contain any underscores? This is why.

The Brains of the Operation

WebGadgetEmulation.js is the magic behind the whole operation. When running as web gadget on Live.com or Spaces the Atlas framework is provided for us. We could package up the entire Atlas framework but this would severely add unnecessary bloat to our gadget. Instead, I’ve decided to emulate the half dozen or so functions associated with Atlas bindings.

Now lets go through each section of WebGadgetEmulation.js.

registerNamespace()

First we emulate the registerNamespace() function that the web gadget performs. It seperates the passed namespace string into an array using “.” as a seperator. Each iten in the array is tested to see if a class (i.e. object) exists starting under the base class “window”. If not, am empty object is created. The base class is set to this current class and this process continues untill all classes are created.

function registerNamespace(ns){
    var base=window;
    var nss=ns.split(".");
    for (var i=0; i < nss.length; i++){
        if (!base[nss[ i ]]){
            base[nss[ i ]]={};
        }
        base=base[nss[ i ]];
    }
}

Inheritence Functions

Next we “stub out” all of the inheritence functions provided bt Atlas Runtime. Unless you specifically use this functionality in your gadget, you won’t need these. If you’re curious as to what each of these functions actually do, take a look at the Atlas Runtime Reference.

Function.prototype.registerBaseMethod=function(){};
Function.prototype.initializeBase=function(){};
Function.prototype.registerClass=function(){};
Function.prototype.getBaseMethod=function(){
  return function() {
    return;
  };
};

Kicking it all off

Next is the page load function and the corresponding attachment. Being the nice citizens that we are, we don’t just want to name our page load function something like “page_load”. This could be used by someone else (even though we are the only code in town, but play along). To accomplish this we create a singleton class LiveGadgets.WebGadgetEmulation and within it the method onPageLoad().

First up within onPageLoad() is  to define our “exit strategy” (i.e. setup a function to call when the gadget exits). Obviously George Bush didn’t write this code. ;)

registerNamespace("LiveGadgets");
LiveGadgets.WebGadgetEmulation = new function() {

    this.onPageLoad = function() {

        var tmp, app, appEl, AppFn;

        //this is called as the page is unloading
        function onPageUnload() {
            //call the gadget's dispose() function
            try {app.dispose(true);} catch(ex){}
            window.detachEvent("onunload", onPageUnload);
            app = appEl = AppFn = tmp = null; //clean up
            LiveGadgets.WebGadgetEmulation = null;
        }

onPageUnload() is a private function that will be setup so that it is called when the page (i.e. the Sidebar gadget) unloads.

Next we get a pointer to the DIV elements where our gadget will live. This is the application DIV from the HTML. As Sidebar gadgets need to define the height, width and background image, we copy these attributes of the application element’s currentStyle to the body’s style.

        //this is where our gadget will "live"
        appEl = document.getElementById("application");

        //transfer the background image, height and width
        //from the application DIV to the body of the HTML
        tmp = appEl.currentStyle.backgroundImage;
        appEl.style.backgroundImage = "none";
        document.body.style.backgroundImage = tmp;
        tmp = appEl.currentStyle.height;
        document.body.style.height = tmp;
        tmp = appEl.currentStyle.width;
        document.body.style.width = tmp;

Here is where we actually call the gadget’s initialize() method for he first time. But before we do, we get a pointer to the class in the variable AppFn. If you remember, I said that the JavaScript class is derived from the CSS class specified in the HTML. We convert all underscores inthe CSS class name to periods as CSS names can not contain periods. This is why the Windows Live Gadget Developer’s Guide said we can’t use underscores in namespaces. After we have a pointer to our gadget class, we instantiate an instance of the class and then call initialize().

        //create an instance of the gadget
        AppFn=eval(appEl.className.replace(/_/g,"."));

        //app = new SampleNamespace.SampleGadget(appEl, {}, null);
        app = new AppFn(appEl, {}, null);

        //call the gadget's initialize() function
        app.initialize(null);

Next we detatch from the onload function and attache the page unload function. And last but not least, we attach our page load function to window.onload.

        window.detachEvent("onload", LiveGadgets.WebGadgetEmulation.onPageLoad);
        window.attachEvent("onunload", onPageUnload); //so we can tear down
    };
};

window.attachEvent("onload", LiveGadgets.WebGadgetEmulation.onPageLoad);

Design Considerations

When designing a cross platform gadget, here are some thing to keep in mind.

  • Size – A Sidebar gadget has a maximum width of 130 pixels when docked. It appears to also have a minimum height of 57 pixels, although I haven’t found this officially documented by Microsoft. Web gadgets normally have a floating size that varies with the width of the area that they are on. This could be anything, but generally web gadgets are wider than 130 pixels. A good cross platform gadget must be able to adapt. It should be noted that Sidebar gadgets also have an undocked mode that allows them to be larger that 130 pixels wide.
  • Background – A Sidebar gadget sits atop of an unknown background (i.e. your desktop) and you must specify a background image for the body. Most web gadgets simply inherit the background or don’t specify one at all. You will need to specify a background image to create a cross platform gadget.
  • Alpha layer PNG – Sidebar run on Windows Vista, thus it uses Internet Explorer 7. Web gadget can be ran on many different web browsers including IE7  and Firefox (both have native support for alpha layer PNG files). Earlier versions of Internet Explorer do not and will display the image against a gray background.
  • Use of Start.* classes – Live.com loads a set of classes in the Start namespace. Although they expose a function rich set of classes, Microsoft warns people about using said classes as they are undocumented and could change at any time. Obviously if you’ve written a web gadget that uses one of these classes, it will not port over to Sidebar as easily.
  • Web.* and other Atlas Framework classes – The WebGadgetEmulation code presented on this article is really just a bootstrap. If you use any of other Atlas classes, including the Web classes, you will need to emulate those yourself.

Get the Code and/or Install the Gadget

You can download the complete source code and/or insall the gadget on your Sidebar or Live.com or Spaces (of course).

Conclusion

Obviously this won’t work for complex web gadget unless WebGadgetEmulation.js is expanded dramatically to provide support for other Altas functions. But, if you must abide by certain restrictions when designing your web gadget, it will go a long way to writing a gadget that runs on Live.com, Spaces and Windows Vista Sidebar.