If you’ve seen Windows 8 yet, you know it’s got a tile-based start menu.  One of the cool things (or annoying things) you can do is post values on your app’s tile.  This is called a “live tile” in Windows 8 world.  Your app users can turn them on or off with a right click on the tile. 

image

Here’s how I built one with JavaScript.

Figure out what you want to display on your live tiles

This is what my tile looks like before it’s “live” as both a smaller (square) and a larger (wide) tile:

small tile          wide tile

The live tile concept is that you pick a style template, build up some XML, then push that XML to the tile through the notification API.  These guidelines show you the different tile templates you can pick.  Find a square tile and a wide tile you like, and pay attention to the name of that template, since you’ll use that soon.

In this case, I decided I like TileSquareText01:

square sample

<tile>
  <visual>
    <binding template="TileSquareText01">
      <text id="1">Text Field 1</text>
      <text id="2">Text Field 2</text>
      <text id="3">Text Field 3</text>
      <text id="4">Text Field 4</text>
    </binding>  
  </visual>
</tile>

and TileWideText01:

wide sample

<tile>
  <visual>
    <binding template="TileWideText01">
      <text id="1">Text Header Field 1</text>
      <text id="2">Text Field 2</text>
      <text id="3">Text Field 3</text>
      <text id="4">Text Field 4</text>
      <text id="5">Text Field 5</text>
    </binding>  
  </visual>
</tile>

The plan is to show the users current altitude as a number on the top, larger font line, then show the units (feet or meters) under that in the smaller font.

There are several rules about what you can and can’t show on your tiles, so check the guidelines carefully to be sure your idea will not get rejected by the Windows Store during the certification process.

Tests for the correct live tile XML

Since the live tile notification is all about sending the right XML, let’s start with a test of the XML we want at the end.  Here I’m using QUnit to check the XML.

test("tile", function () {
    var altitude = 6071,
        altitudeUnits = "feet",
        result,
        expected;
   
    result = tile.buildSquareTileXml(altitude, altitudeUnits);
    expected = '<tile>' +
        '<visual>' +
        '<binding template="TileSquareText01" branding="name">' +
        '<text id="1">6071</text>' +
        '<text id="2">feet</text>' +
        '<text id="3"></text>' +
        '<text id="4"></text>' +
        '</binding>' +
        '</visual>' +
        '</tile>';
    equal(result.getXml().toString(), expected, "should get correct xml for square tile");

    result = tile.buildWideTileXml(altitude, altitudeUnits);
    expected = '<tile>' +
        '<visual>' +
        '<binding template="TileWideText01" branding="name">' +
        '<text id="1">6071</text>' +
        '<text id="2">feet</text>' +
        '<text id="3"></text>' +
        '<text id="4"></text>' +
        '<text id="5"></text>' +
        '</binding>' +
        '</visual>' +
        '</tile>';
    equal(result.getXml().toString(), expected, "should get correct xml for wide tile");
});

These tests fail since we haven’t written the functions yet, but we’ll do that next.  Ignore the “branding” attribute for now.  We’ll come back to that.

Build up the live tile XML

Here’s the code to get the correct XML and get those tests passing.  See those calls the Windows.UI.Notifications.TileTemplateType?  That’s where you plug in the name of the square and wide template you picked from the catalog. 

I’m building up an array of values for the different lines of text.  Use any XML or string manipulation technique you like.  I got this one from the Windows 8 SDK samples.

    function buildSquareTileXml(altitude, altitudeUnits) {
        var squareTemplate = Windows.UI.Notifications.TileTemplateType.tileSquareText01,
                squareTileLines = [
                    altitude,
                    altitudeUnits
                ],
                squareTileXml = Windows.UI.Notifications.TileUpdateManager.getTemplateContent(squareTemplate),
                squareTileTextAttributes = squareTileXml.getElementsByTagName("text");

        for (var i = 0; i < squareTileLines.length; i++) {
            squareTileTextAttributes[i].appendChild(squareTileXml.createTextNode(squareTileLines[i]));
        }

        var squareTileBinding = squareTileXml.getElementsByTagName("binding");
        if (squareTileBinding[0]) {
            squareTileBinding[0].setAttribute("branding", "name");
        }

        return squareTileXml;
    }
    
    function buildWideTileXml(altitude, altitudeUnits) {
        var wideTemplate = Windows.UI.Notifications.TileTemplateType.tileWideText01,
                wideTileLines = [
                    altitude,
                    altitudeUnits
                ],
                wideTileXml = Windows.UI.Notifications.TileUpdateManager.getTemplateContent(wideTemplate),
                wideTileTextAttributes = wideTileXml.getElementsByTagName("text");

        for (var i = 0; i < wideTileLines.length; i++) {
            wideTileTextAttributes[i].appendChild(wideTileXml.createTextNode(wideTileLines[i]));
        }

        var binding = wideTileXml.getElementsByTagName("binding");
        if (binding[0]) {
            binding[0].setAttribute("branding", "name");
        }

        return wideTileXml;
    }

Combine the square and wide XML

Since users of your tile can toggle the smaller (square) or larger (wide) version of your tile, you want to combine the square and wide XML and send down both.  Here’s how to do that.  Again, this is just XML manipulation, use any technique you like.

    function combineTileXml(wideTileXml, squareTileXml) {
        var node = wideTileXml.importNode(squareTileXml.getElementsByTagName("binding").item(0), true);
        wideTileXml.getElementsByTagName("visual").item(0).appendChild(node);

        return wideTileXml;
    }

Pass the combined live tile XML to the Windows 8 API

Now that you’ve got the final XML you want, create a Tile Notification object and pass it to the Tile Update Manager:

    function sendTileUpdate(altitude, altitudeUnits) {
        var wideTileXml,
            squareTileXml,
            combinedTileXml,
            tileNotification;

        if (altitude && altitudeUnits) {
            wideTileXml = buildWideTileXml(altitude, altitudeUnits);
            squareTileXml = buildSquareTileXml(altitude, altitudeUnits);
            combinedTileXml = combineTileXml(wideTileXml, squareTileXml);
            
            tileNotification = new Windows.UI.Notifications.TileNotification(combinedTileXml);
            Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication().update(tileNotification);
        }
    }

The last two lines are doing the real work here, by using the Windows 8 notifications API.  The rest is calling the other helper functions.

Calling the notification code

So who notifies the notifier code that we’ve got some new stuff to show on the line tile?  You do!  Figure out when something interesting happens in your app, and send a notification to the live tile then.  In this case, that looks like:

tile.sendTileUpdate(altitude, altitudeUnits);

Putting it all together

Here’s the result for the square and wide tiles:

smaill live tile          wide live tile

Here’s the combined JavaScript.  I put it all in a file named it tile.js and used the WinJS namespacing function.  This is pretty much the same as the revealing module pattern, and it means I can call tile.<whatever> from the tests and other JavaScript files.

(function () {
    "use strict";
    
    function sendTileUpdate(altitude, altitudeUnits) {
        var wideTileXml,
            squareTileXml,
            combinedTileXml,
            tileNotification;

        if (altitude && altitudeUnits) {
            wideTileXml = buildWideTileXml(altitude, altitudeUnits);
            squareTileXml = buildSquareTileXml(altitude, altitudeUnits);
            combinedTileXml = combineTileXml(wideTileXml, squareTileXml);
            
            tileNotification = new Windows.UI.Notifications.TileNotification(combinedTileXml);
            Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication().update(tileNotification);
        }
    }
    
    function buildSquareTileXml(altitude, altitudeUnits) {
        var squareTemplate = Windows.UI.Notifications.TileTemplateType.tileSquareText01,
                squareTileLines = [
                    altitude,
                    altitudeUnits
                ],
                squareTileXml = Windows.UI.Notifications.TileUpdateManager.getTemplateContent(squareTemplate),
                squareTileTextAttributes = squareTileXml.getElementsByTagName("text");

        for (var i = 0; i < squareTileLines.length; i++) {
            squareTileTextAttributes[i].appendChild(squareTileXml.createTextNode(squareTileLines[i]));
        }

        var squareTileBinding = squareTileXml.getElementsByTagName("binding");
        if (squareTileBinding[0]) {
            squareTileBinding[0].setAttribute("branding", "name");
        }

        return squareTileXml;
    }
    
    function buildWideTileXml(altitude, altitudeUnits) {
        var wideTemplate = Windows.UI.Notifications.TileTemplateType.tileWideText01,
                wideTileLines = [
                    altitude,
                    altitudeUnits
                ],
                wideTileXml = Windows.UI.Notifications.TileUpdateManager.getTemplateContent(wideTemplate),
                wideTileTextAttributes = wideTileXml.getElementsByTagName("text");

        for (var i = 0; i < wideTileLines.length; i++) {
            wideTileTextAttributes[i].appendChild(wideTileXml.createTextNode(wideTileLines[i]));
        }

        var binding = wideTileXml.getElementsByTagName("binding");
        if (binding[0]) {
            binding[0].setAttribute("branding", "name");
        }

        return wideTileXml;
    }
       
    function combineTileXml(wideTileXml, squareTileXml) {
        var node = wideTileXml.importNode(squareTileXml.getElementsByTagName("binding").item(0), true);
        wideTileXml.getElementsByTagName("visual").item(0).appendChild(node);

        return wideTileXml;
    }
    
    WinJS.Namespace.define("tile", {
        sendTileUpdate: sendTileUpdate,
        buildWideTileXml: buildWideTileXml,
        buildSquareTileXml: buildSquareTileXml
    });
})();

Branding

In the bottom left corner of the tile, I am showing the name of the app, in this case “Altitude”, on the live tile. 

smaill live tile          wide live tile

If you like, you can show your logo here instead.  This comes from the 30x30 logo in your package.appxmanifest on the Application UI tab, under Tile > Small Logo. 

image

Change these lines:

binding[0].setAttribute("branding", "name");

to:

binding[0].setAttribute("branding", "logo");

And you’ll get this instead:

small live tile logo          wide live tile logo

In fact, you can take out all the code around branding if you like the logo there, since that’s the default behavior.  If you want neither your app name nor your logo there, you can set the branding attribute to “none”.

Hey, my Live Tile doesn’t work!

I had this experience the first couple times I ran my code, too.  The most common mistake I’ve seen is invalid XML, so be sure you add those test first to avoid that.

The other snag was using the Simulator in Visual Studio 2012 that simulates a tablet with touch and rotating.  I don’t know what the issue was, but my live tile didn’t update when I ran it through the Simulator.  Other live tiles could be turned on and off, but not the one for my app.  When I ran on Local Machine instead, the live tile worked fine.  So if you’re not seeing your live tile work in the Simulator, switch to Local Machine and see if that fixes it for you.