Navigation is one of the core concepts of any Metro application, in this post I’ll show how can you implement it in a HTML based Metro application.

The easiest way is to use the same common approach used by millions of HTML pages: using an hyperlink.
Assuming you are in default.html page and wish to navigate to page2.html all you need to do is add something like:

image_thumb

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <meta charset="utf-8">
   5:      <title>DemoNavigation</title>
   6:   
   7:      <!-- WinJS references -->
   8:      <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
   9:      <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
  10:      <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
  11:   
  12:      <!-- DemoNavigation references -->
  13:      <link href="default.css" rel="stylesheet">
  14:      <script src="default.js"></script>
  15:  </head>
  16:  <body>
  17:      <a href="../page2/page2.html">Click here to navigate to Page 2</a>
  18:  </body>
  19:  </html>

default.html

When you run the app and click the hyperlink you move straight to page2.html.
This is approach is quite common, works fine (of course) but has a main drawback: you loose state when you move from default.html to page2.html, so if you need to maintain state, something that’s quite common in client application Smile, you’re on your own.

The problem is the same in web application that’s why technologies like ajax or single page applications are gaining more and more momentum.

Let’s now briefly see how you can embed an HTML fragment into default.html using WinJS’s HtmlControl, here’s the html you need to add to default.html page:

image_thumb1
   1:  <!--pageFragment.html-->
   2:  <div style="color: #b6ff00 ">
   3:      <p id="txt" class="win-type-x-large win-type-ellipsis">This is a html fragment</p>
   4:  </div>
 
   1:  <body>
   2:      <h1 class="win-title">This is main page</h1>
   3:      <div data-win-control="WinJS.UI.HtmlControl" 
   4:           data-win-options="{uri:'../fragments/pageFragment.html'}">
   5:      </div>
   6

Launching the app you’ll see exactly what expected but with some limitations:

image_thumb4

the fragment can’t reference any css nor javascript file, and you are not informed when the fragment is loaded, luckily there’s a way to get some javascript code executed when fragment is loaded into the DOM: WinJS.Pages.define.

Let’s assume we wish to dynamically change the text of the fragment after it has been loaded so let’s add a new pagedefine.js file (just for better understanding and separation) to our solution:

image_thumb5

   1:  /// <reference path="//Microsoft.WinJS.0.6//js/base.js" />
   2:   
   3:  (function () {
   4:      'use strict';
   5:   
   6:      WinJS.UI.Pages.define("../fragments/pageFragment.html",
   7:          {
   8:              ready:function(element,options)
   9:              {
  10:                  var text = document.getElementById("txt");
  11:                  text.innerText = new Date().toLocaleString();
  12:              }        
  13:          });
  14:   
  15:   
  16:  }());

   1:  <!-- WinJS references in default.html -->
   2:      <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
   3:      <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
   4:      <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
   5:      <script type="text/javascript" src="../../js/pageDefine.js"></script>

The define method available on Pages object accept the uri that uniquely identifies the page that is going to be loaded and an object exposing a set of properties, among these, ready is the function that will be called when page has been loaded into DOM, then the code just changes element content with current date:

image_thumb8

we’re now in a state where we can load a HTML fragment and run some code after it has been loades, before going further I need to point out that HmlControl can only load local html, if you need to load external content iFrame is your friend.

Problem now is: what if I need to load the fragment programmatically? (e.g. on a button click), let’s remove HtmlControl from default.html and let’s inject the fragment with a little help from WinJS:

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <meta charset="utf-8">
   5:      <title>DemoNavigation</title>
   6:   
   7:      <!-- WinJS references in default.html -->
   8:      <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
   9:      <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
  10:      <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
  11:      <script type="text/javascript" src="../../js/pageDefine.js"></script>
  12:   
  13:      <!-- DemoNavigation references -->
  14:      <link href="default.css" rel="stylesheet">
  15:      <script src="default.js"></script>
  16:  </head>
  17:  <body>
  18:      <h1 class="win-title">This is main page</h1>
  19:      <button id="btnLoad">Load fragment</button>
  20:      <div id="contentHost"></div>
  21:  </body>
  22:  </html>
   1:  //default.js
   2:  (function () {
   3:      "use strict";
   4:      var app = WinJS.Application;
   5:      app.onactivated = function (eventObject) {
   6:          if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
   7:              if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
   8:                  // TODO: This application has been newly launched. Initialize 
   9:                  // your application here.
  10:              } else {
  11:                  // TODO: This application has been reactivated from suspension. 
  12:                  // Restore application state here.
  13:              }
  14:              WinJS.UI.processAll().then(function (e) {
  15:                  WinJS.Utilities.id("btnLoad").listen("click", function (e) {
  16:                      //Load fragment
  17:                      var host=WinJS.Utilities.id("contentHost")[0];
  18:                      WinJS.UI.Pages.render("../fragments/pageFragment.html", host, { data: 'data2share' });
  19:                  });
  20:   
  21:              }).done();
  22:          
  23:          }
  24:      };    
  25:   
  26:      app.start();
  27:  })();

In default.html I added a button and a “contentHost” div whose role is to be the container for for dynamically loaded content, default.js subscribes button’s click event (using WinJS.Utilities methods) and invokes WinJS.Pages.render method.
WinJS.Pages.render method accepts the uri of the fragment to load, the host element where fragment will be appended and optional data to pass to loaded fragment.
Here’s how I changed fragment’s code to handle passed parameter:

   1:  /// <reference path="//Microsoft.WinJS.0.6//js/base.js" />
   2:   
   3:  (function () {
   4:      'use strict';
   5:      WinJS.UI.Pages.define("../fragments/pageFragment.html",
   6:          {
   7:              ready:function(element,options)
   8:              {
   9:                  var text = document.getElementById("txt");
  10:                  text.innerText = options.data;
  11:              }        
  12:          });
  13:  }());

result, after clicking “Load fragment” button, is:

image_thumb10

We now know how to dynamically load content into live DOM.

About page navigation: the best way to include it in a Html5 Metro app is to start with the Navigation Application template that creates all the infrastructure required to support navigating between pages while maintaining state, here’s what get’s created once you select the template:


image_thumb11

 
   1:  <!--default.html-->
   2:  <!DOCTYPE html>
   3:  <html>
   4:  <head>
   5:      <meta charset="utf-8">
   6:      <title>DemoNavigation2</title>
   7:      <!-- WinJS references -->
   8:      <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
   9:      <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
  10:      <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
  11:   
  12:      <!-- DemoNavigation2 references -->
  13:      <link href="/css/default.css" rel="stylesheet">
  14:      <script src="/js/default.js"></script>
  15:      <script src="/js/navigator.js"></script>
  16:  </head>
  17:  <body>
  18:      <div id="contenthost" 
  19:          data-win-control="DemoNavigation2.PageControlNavigator" 
  20:          data-win-options="{home: '/html/homePage.html'}">
  21:      </div>    
  22:  </body>
  23:  </html>

As you can see homePage.html (the starting page) contains a PageControlNavigator control that is defined inside navigator.js that basically uses WinJS.Pages.render to append other pages to contenthost div and adds typical navigation features like backstack etc… the control is configured via data-win-options to display homePage.html as initial content.
To add a page that can be navigated, let’s add a new folder named details and inside it a new Page control named details.html (see below):

image_thumb14

   1:  //detail.js
   2:  (function () {
   3:      "use strict";
   4:   
   5:      // This function is called whenever a user navigates to this page. It
   6:      // populates the page elements with the app's data.
   7:      function ready(element, options) {
   8:          // TODO: Initialize the fragment here.
   9:      }
  10:   
  11:      function updateLayout(element, viewState) {
  12:          // TODO: Respond to changes in viewState.
  13:      }
  14:   
  15:      WinJS.UI.Pages.define("/html/details/details.html", {
  16:          ready: ready,
  17:          updateLayout: updateLayout
  18:      });
  19:  })();

Adding a new page control results in three files: the html, the css and the javascript code behind that, as you see below, it includes the necessary define invocation so that, inside ready handler, we can run page’s initialization code.
The only thing missing is triggering navigation from homePage.html to details.html, let’s assume this would happen, again, via a button:

   1:  <!--homePage.html-->
   2:  <!DOCTYPE html>
   3:  <html>
   4:  <head>
   5:      <meta charset="utf-8">
   6:      <title>homePage</title>
   7:      
   8:      <!-- WinJS references -->
   9:      <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
  10:      <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
  11:      <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
  12:      
  13:      <link href="/css/default.css" rel="stylesheet">
  14:      <link href="/css/homePage.css" rel="stylesheet">
  15:      <script src="/js/homePage.js"></script>
  16:  </head>
  17:  <body>
  18:      <!-- The content that will be loaded and displayed. -->
  19:      <div class="fragment homepage">
  20:          <header aria-label="Header content" role="banner">
  21:              <button class="win-backbutton" aria-label="Back" disabled></button>
  22:              <h1 class="titlearea win-type-ellipsis">
  23:                  <span class="pagetitle">Welcome to DemoNavigation2!</span>
  24:              </h1>
  25:          </header>
  26:          <section aria-label="Main content" role="main">
  27:              <button id="btnLoad">Details</button>
  28:          </section>
  29:      </div>
  30:  </body>
  31:  </html>

   1:  //homePage.js
   2:  (function () {
   3:      "use strict";
   4:      // This function is called whenever a user navigates to this page. It
   5:      // populates the page elements with the app's data.
   6:      function ready(element, options) {
   7:          document.getElementById("btnLoad").addEventListener("click", function (e) {
   8:              WinJS.Navigation.navigate("/html/details/details.html");
   9:      });
  10:      }
  11:   
  12:      WinJS.UI.Pages.define("/html/homePage.html", {
  13:          ready: ready
  14:      });
  15:  })();

as you see, application navigation is handled globally through WinJS.Navigation object that exposes methods like navigate (together with back, canGoBack, canGoForward…) that navigates (thus, loads page into default.html’s DOM) to details.html, this way, since default.html isn’t discarded, state is preserved and experience is similar to any other client application development.

Quite cumbersome to understand at the beginning, but easy to implement thanks to Visual Studio 11 templates

Technorati Tags: ,