This blog post shows how you can use PhoneGap to create Windows Phone 7 applications that are comprised of multiple, simple HTML pages, whilst meeting the Marketplace certification requirements.

Colin Eberhardt is a Scott Logic technical Evangelist and a Technical Architect for Visiblox, suppliers of high-performance WPF and Silverlight charts.

Readers of my blog will know that I have been working on, and writing about, the use of PhoneGap to create HTML5-based Windows Phone 7 applications. Along the way I have solved problems such as back-button support, tombstoning and suppressing browser pan and zoom. So far the applications I have discussed have all been single-page JavaScript applications, i.e. the WebBrowser control loads a single HTML page which links to the application JavaScript. All subsequent navigation involves updating the DOM dynamically rather than navigating to a new URL. However, I have had numerous questions to my previous blog posts from people who are using PhoneGap to create simple navigation-based applications. In this context PhoneGap is being used as a mechanism for packaging up a collection of relatively static HTML pages.

So why would you want to do this? This is a perfectly valid question! If you have static HTML based content why not simply put it on the web? There are a few reasons, one is to ensure that the content is always available, whether the phone is connected to a network or not. Another is monetization, the app-store model provides an easy way to charge for this content. Regardless of the pros and cons, based on the feedback my previous articles have received, people are very interested in using PhoneGap as a model for packaging static content.

In this blog post I will show how to solve the problems of back-button support and pan / zoom for a simple navigation-based HTML application. As an example, I have chosen a subject which I have a lot of interest in ... sandwiches! I have previously blogged about SandwichFlow, an example application which showcased my metro-in-motion series, here I have transformed the sandwich recipe HTML into a collection of static HTML pages.

Packaging the HTML

My first PhoneGap blog post detailed how to get up-and-running with PhoneGap, if you have never used this framework before I would recommend that you read that first. Packaging my sandwich recipe content into an application was simply a matter of copying the files (HTML and CSS) into the 'www' directory, running the T4 template that auto-generates the GapSourceDictionary.xml, building and running. The resulting application is as follows:

PhoneGap does a great job of making it very easy to package HTML content into an application, loading the files into isolates storage etc ... However, there are a few Windows Phone 7 specific concepts that have to be supported in order to ensure certification on submission to the marketplace.

Back-button Support

UPDATE: There are a few issues with the code below. See my more recent blogpost for an improved solution.

Correct back-button support is a mandatory requirement for marketplace certification. Hitting the back button should navigate back through the various screens of an application. Hitting the back-button on the first screen should terminate the application. The standard PhoneGap Visual Studio template does not support the back-button by default, this no-doubt reflects the fact that the other PhoneGap platforms (iPhone, Android, BlackBerry) do not share this same requirement, or lack a back-button altogether.

In my previous blog post on back-button support I described how a single-page JavaScript application can manage its own back-stack. This approach does not work for navingation-based HTML applications.

The WebBrowser control which PhoneGap uses to render its contents stores its navigation history in the same way that a desktop browser does. This history can be accessed via the JavaScript 'history' object. If the back-button is pressed after navigating to one of the packaged HTML pages, invoking history.go(-1) will cause the WebBrowser control to navigate backwards. All we need to do is to keep track of navigation events to determine whether to route the back-button too our HTML application or not.

The following utility class does just that:

/// <summary>
/// Handles the back-button for a PhoneGap application. When the back-button
/// is pressed, the browser history is navigated. If no history is present,
/// the application will exit.
/// </summary>
public class BackButtonHandler
{
  private int _browserHistoryLength = 0;
  private PGView _phoneGapView;

  public BackButtonHandler(PhoneApplicationPage page, PGView phoneGapView)
  {
    // subscribe to the hardware back-button
    page.BackKeyPress += Page_BackKeyPress;

    // handle navigation events
    phoneGapView.Browser.Navigated += Browser_Navigated;

    _phoneGapView = phoneGapView;
  }

  private void Browser_Navigated(object sender, NavigationEventArgs e)
  {
    if (e.NavigationMode == NavigationMode.New)
    {
      _browserHistoryLength++;
    }
  }

  private void Page_BackKeyPress(object sender, CancelEventArgs e)
  {
    if (_browserHistoryLength > 1)
    {
      _phoneGapView.Browser.InvokeScript("eval", "history.go(-1)");
      _browserHistoryLength -= 2;
      e.Cancel = true;
    }
  }
}

Note: I have had a number of requests from readers who do not have any C# / Silverlight experience, so are unsure how to use code snippets like the one above. In this blog post I have included the full sourcecode. Just grab the code and replace the contents of the 'www' directory with your own content, and you are good to go!

With the above code in place the application now supports the back-button

With this feature in place you should be able to successfully submit a PhoneGap based application to the Windows Phone 7 marketplace. The other bits of code that follow are all about improving the cosmetics.

Suppressing Pan and Zoom

Whilst the viewport metadata settings can be used to prevent scaling, the cosmetics of this feature are not terribly good with Windows Phone 7, the user can still 'pinch' your HTML page, however, it snaps back to its original scale when the manipulation ends. I previously published a simple class which can intercept manipulation events in order to suppress the pinch zoom behaviour.

This class can also 'lock' the browser windows entirely, supressing scroll as well as zoom. However, this is only appropriate for use on pages where you can be sure the content fits entirely within a single page. In order to 'activate' this behaviour we need to send a message from the HTML / JavaScript to the C# utility class. The 'about' page of this application does just that. When it is loaded the following JavaScript is executed:

<script type="text/javascript" charset="utf-8">
  // ensure that this page does not scroll
  window.external.Notify("noScroll");
</script>

The utility class (described in the earlier blog post) is instantiated in code-behind, with the above Notify being used to 'lock' the page:

private WebBrowserHelper _browserHelper;

// Constructor
public MainPage()
{
  InitializeComponent();

  new BackButtonHandler(this, PGView);
  _browserHelper = new WebBrowserHelper(PGView.Browser);

  PGView.Browser.ScriptNotify += Browser_ScriptNotify;
  PGView.Browser.Navigated += Browser_Navigated;
}

private void Browser_Navigated(object sender, NavigationEventArgs e)
{
  // when we first navigate to a page, we assume that it can be scrolled
  _browserHelper.ScrollDisabled = false;
}

private void Browser_ScriptNotify(object sender, NotifyEventArgs e)
{
  // if a page notifies that it should not be scrollable, disable
  // scrolling.
  if (e.Value == "noScroll")
  {
    _browserHelper.ScrollDisabled = true;
  }
}

Again, the code above is included in the download at the end of this article. To 'lock' the scroll on any of your HTML pages, simply add the JavaScript 'Notify' above.

Splashscreen

One final cosmetic issue is the application startup. The application splashcreen is hidden automatically when your application starts up. This is fine for a native application, however a PhoneGap application isn't quite ready at this point. After the native wrapper starts, the code is loaded into the WebBrwoser control and the HTML / JavaScript application starts. This results in a rather ugly white screen appearing for ~ 0.5 second when the application starts up.

The start screen duration is not configurable, however, there is a simple solution to this problem, when the application first starts, render the splashscreen as the application content, then hide this image when the PhoneGap application starts.

To do this, create an image and position it in front of the PhoneGap browser control:

<Grid x:Name="LayoutRoot" Background="Transparent">

  <my:PGView HorizontalAlignment="Stretch"
                  Margin="0,0,0,0"
                  Name="PGView"
                  VerticalAlignment="Stretch"/>

  <Image Source="/SplashScreenImage.jpg"
          x:Name="splashImage">
    <Image.Resources>
      <Storyboard x:Name="fadeOut"
                  BeginTime="0:0:0.5"
                  Completed="fadeOut_Completed">
        <DoubleAnimation
                Storyboard.TargetName="splashImage"
                Storyboard.TargetProperty="Opacity"
                From="1.0" To="0.0" Duration="0:0:0.3"/>
      </Storyboard>
    </Image.Resources>
  </Image>
</Grid>

This image has a storyboard that fades-out the image.

This animation is triggered when the browser navigates to the first age of the application:

EventHandler<NavigationEventArgs> hideSplashScreen = null;
hideSplashScreen = (s, e ) =>
  {
    fadeOut.Begin();
    PGView.Browser.Navigated -= hideSplashScreen;
  };
PGView.Browser.Navigated += hideSplashScreen;

Finally, the Completed event handler removes the image altogether when the animation ends:

private void fadeOut_Completed(object sender, EventArgs e)
{
  splashImage.Visibility = Visibility.Collapsed;
}

Conclusions

Hopefully this blog post will help people who are new to WP7 and wish to release PhoneGap applications comprised of multiple HTML pages.

You can download the sourcecode: HTML5SandwichFlow.zip

Regards, Colin E.