Building Cross-Platform Mobile Applications with HTML5 and Mango

One of the most exciting new features coming in Windows Phone 7.1, code-named “Mango,” is an HTML5 Web browser based on IE9. One by-product of this addition is that you can use Mango phones to browse HTML5 Web sites. But the greater implication is that developers can now use HTML5 to build cross-platform mobile applications that run on Windows phones as well as iPhones and Android phones.

HTML5 isn’t a perfect solution for building cross-platform mobile apps – you can’t, for example, leverage native features such as Mango’s Pivot and Panorama controls from HTML5, nor would you want to since those controls don’t exist on other platforms – but for apps that don’t need platform-specific features anyway, HTML5 offers a handy solution to the problem of having to write the same app multiple times to get it running on different devices.

To demonstrate the basic steps involved in writing Mango apps in HTML5 and packaging them and deploying them to a phone, I wrote the app pictured below. It’s a classic sliding-puzzle game, in which you scramble the picture and then try to get the pieces back in the right place. To move, simply touch a piece that’s adjacent to the empty (gray) square. Each puzzle piece is an HTML5 canvas, and I used jQuery animations to slide puzzle pieces up, down, and sideways. You can download the source code and play with it on your phone if your phone has been flashed for Mango. If not, you’ll have to use the emulator, but be aware that the app only works in the emulator if the PC it’s running on has sufficient GPU support.

HTML5Demo Screen

Writing an HTML5 app for a Windows phone – or any phone, for that matter – begins with the HTML assets. My app contains three such assets:

  • Puzzle.html, the page that comprises the app’s UI
  • jquery.js, which contains version 1.5 of the popular jQuery library
  • scene.jpg, which contains the 480 x 800 image from which puzzle pieces are generated

Puzzle.html is reproduced below. Nothing magical here – just 14 HTML5 canvases, or one for each puzzle piece; some JavaScript code that cuts up the 480 x 800 image and paints the puzzle pieces; and more JavaScript code to move a puzzle piece in response to click events. I tested this page in IE9 and other HTML5 browsers before incorporating it into my phone app.

 

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=480, height=800, user-scalable=no" />

<style type="text/css">

body {

    margin: 0;

    height: 100%;

    background-color: #808080;

}

#main {

    position: absolute;

    width: 480px;

    height: 800px;

}

</style>

 

<script type="text/javascript" src="jquery.js"></script>

 

<script type="text/javascript">

    var _row = 4; // Row containing empty square

    var _col = 2; // Column containing empty square

 

    $(document).ready(function () {

        // Load the puzzle image

        var image = new Image();

        image.src = "scene.jpg";

        image.onload = function () {

            // Draw sections of the image into the puzzle pieces

            for (var row = 0; row < 5; row++) {

                for (var col = 0; col < 3; col++) {

                    if (!(row == 4 && col == 2)) {

                        var dc = document.getElementById("c" +

                            row + col).getContext("2d");

                        dc.drawImage(image, col * 160, row * 160, 160, 160,

                            0, 0, 160, 160);

                    }

                }

            }

 

            // Wire event handlers to all the puzzle pieces

            $("canvas").click(onClick);

        }

    });

 

    function onClick(e) {

        var piece = $(this);

 

        // Get the row and column

        var row = piece.position().top / 160;

        var col = piece.position().left / 160;

 

        if (row == _row && col == _col + 1) {

            // Move left

            piece.animate({ left: "-=160px" }, 100);

            _col++;

        }

        else if (row == _row && col == _col – 1) {

            // Move right

            piece.animate({ left: "+=160px" }, 100);

            _col–;

        }

        else if (col == _col && row == _row + 1) {

            // Move up

            piece.animate({ top: "-=160px" }, 100);

            _row++;

        }

        else if (col == _col && row == _row – 1) {

            // Move down

            piece.animate({ top: "+=160px" }, 100);

            _row–;

        }

    }

</script>

 

</head>

<body>

 

<div id="main">

    <canvas id="c00" width="160px" height="160px" style="position: absolute;

        left: 0px; top: 0px; border: 1px solid gray"></canvas>

    <canvas id="c01" width="160px" height="160px" style="position: absolute;

        left: 160px; top: 0px; border: 1px solid gray"></canvas>

    <canvas id="c02" width="160px" height="160px" style="position: absolute;

        left: 320px; top: 0px; border: 1px solid gray"></canvas>

    <canvas id="c10" width="160px" height="160px" style="position: absolute;

        left: 0px; top: 160px; border: 1px solid gray"></canvas>

    <canvas id="c11" width="160px" height="160px" style="position: absolute;

        left: 160px; top: 160px; border: 1px solid gray"></canvas>

    <canvas id="c12" width="160px" height="160px" style="position: absolute;

        left: 320px; top: 160px; border: 1px solid gray"></canvas>

    <canvas id="c20" width="160px" height="160px" style="position: absolute;

        left: 0px; top: 320px; border: 1px solid gray"></canvas>

    <canvas id="c21" width="160px" height="160px" style="position: absolute;

        left: 160px; top: 320px; border: 1px solid gray"></canvas>

    <canvas id="c22" width="160px" height="160px" style="position: absolute;

        left: 320px; top: 320px; border: 1px solid gray"></canvas>

    <canvas id="c30" width="160px" height="160px" style="position: absolute;

        left: 0px; top: 480px; border: 1px solid gray"></canvas>

    <canvas id="c31" width="160px" height="160px" style="position: absolute;

        left: 160px; top: 480px; border: 1px solid gray"></canvas>

    <canvas id="c32" width="160px" height="160px" style="position: absolute;

        left: 320px; top: 480px; border: 1px solid gray"></canvas>

    <canvas id="c40" width="160px" height="160px" style="position: absolute;

        left: 0px; top: 640px; border: 1px solid gray"></canvas>

    <canvas id="c41" width="160px" height="160px" style="position: absolute;

        left: 160px; top: 640px; border: 1px solid gray"></canvas>

</div>

 

</body>

</html>

 

Once the HTML assets are finalized, the challenge is to package them into a native phone app – one that can be deployed through the Windows Phone Marketplace like any other app. Here were the steps involved in this example:

  1. Create copies of Puzzle.html, jquery.js, and scene.jpg in isolated storage
  2. Create a WebBrowser control and point it to Puzzle.html

Here’s the corresponding code in MainPage.xaml.cs. “Browser” is the name assigned to the WebBrowser control I declared in MainPage.xaml. (Be sure to set the control’s IsScriptEnabled property to true; otherwise, your JavaScript won’t work very well, to put it mildly.)

 

// Constructor

public MainPage()

{

    InitializeComponent();

 

    // If the HTML assets aren’t in isolated storage, put them there

    using (IsolatedStorageFile store =

        IsolatedStorageFile.GetUserStoreForApplication())

    {

        if (!store.FileExists("Puzzle.html"))

            CreateFile(store, "Puzzle.html", Application.GetResourceStream

                (new Uri("Assets/Puzzle.html", UriKind.Relative)).Stream);

 

        if (!store.FileExists("jquery.js"))

            CreateFile(store, "jquery.js", Application.GetResourceStream

                (new Uri("Assets/jquery.js", UriKind.Relative)).Stream);

 

        if (!store.FileExists("scene.jpg"))

            CreateFile(store, "scene.jpg", Application.GetResourceStream

                (new Uri("Assets/scene.jpg", UriKind.Relative)).Stream);

    }

 

    // Once the WebBrowser control has loaded, point it to Puzzle.html

    Browser.Loaded += (s, e) => Browser.Source =

        new Uri("Puzzle.html", UriKind.Relative);

}

 

private void CreateFile(IsolatedStorageFile store, string file, Stream content)

{

    using (IsolatedStorageFileStream stream =

        new IsolatedStorageFileStream(file, FileMode.Create, store))

    {

        using (BinaryReader reader = new BinaryReader(content))

        {

            byte[] data = reader.ReadBytes((int)content.Length);

 

            using (BinaryWriter writer = new BinaryWriter(stream))

            {

                writer.Write(data);

            }

        }

    }

}

 

I added Puzzle.html, jquery.js, and scene.jpg to the phone project and set each file’s build action to Content. (I placed these files in a folder named Assets, which explains the “Assets” in the path names passed to Application.GetResourceStream. For reference, see the solution structure below.) Each time the app starts up, it checks to see if these files are in isolated storage. If they’re not, it puts them there. I chose to put all three files in the root directory of the application’s isolated storage, but you could just as easily segregate them into subdirectories and modify the URIs referencing them in Puzzle.html accordingly.

image

The net result is an app that looks like a native Mango app, but is in fact an HTML5 app that is easily ported to other platforms. Is this the future of mobile development? You be the judge. What’s important is that it’s finally an option on Windows phones, and that the presence of HTML5 support will only bolster the variety of apps available for these devices.

Stay Informed

Sign up for the latest blogs, events, and insights.

We deliver solutions that accelerate the value of Azure.
Ready to experience the full power of Microsoft Azure?

Atmosera is thrilled to announce that we have been named GitHub AI Partner of the Year.

X