Creating a Navigation App Project
We can create a new project using the Navigation App project template called NavigationApp. Now we can see a number of new files created. The first new file is the navigator.js file under the /js folder. There is also a new pages folder with a home subfolder. Inside the home subfolder are three more files: home.css, home.html, and home.js (see Figure 2.8).
Figure 2.8. The Navigation App project adds new files.
When using the web, we can easily add anchor tags and do a redirect to an entirely different page. However, when we simply redirect, we lose all our state. For apps, this can be bad. WinJS provides navigation functionality that helps maintain application state when moving among multiple pages inside an app.
Creating Single-Page Applications
Websites typically use either multipage navigation or single-page navigation. Websites that use single-page navigation are typically called Single Page Apps (SPAs). Multipage navigation is typical of most websites, with each hyperlink causing the entire page to be unloaded and a new page loaded. Because this is HTML, navigating to a new page with a top-level navigation causes a blank screen to appear. All state is lost unless explicitly saved with client- or server-side code. Figure 2.9 shows the multipage navigation model.
Figure 2.9. Multipage navigation unloads the existing page and loads the new page.
Single-page navigation, on the other hand, still allows for the app to be split into multiple physical page files, but when the user clicks a hyperlink, the new page content is loaded inside the main page. This way, the main page maintains all state and the new content is simply treated as dynamic data. Because the page is never unloaded, the scripts remain intact. This makes managing state easier and enables us to transition the new content with animations. For these reasons, single-page navigation is the recommended navigation model for Windows Store apps using JavaScript. See Figure 2.10.
Figure 2.10. Single-page navigation keeps the main page loaded and replaces some content with content from another page.
Understanding the WinJS Navigation Objects
Most apps need a consistent way of navigating to multiple pages and keeping track of where they are in the process. WinJS provides the Navigation object for this common need. This object exposes functions such as navigate, back, and forward. It also exposes the onbeforenavigate, onnavigating, and onnavigated events. In addition, the object exposes the following properties: canGoForward, canGoBack, history, location, and state. The WinJS.Navigation object simply maintains a stack of the URLs that the app has navigated to.
The next piece of the puzzle that WinJS provides is the pages themselves. The WinJS.UI.Pages.PageControl object enables us to define a modular unit of JavaScript, HTML, and CSS. We can navigate to this unit or use it as a custom WinJS control. We saw the built-in WinJS ViewBox control being used earlier. The NavigationApp project gives an example of this modular unit. In the /pages folder is a /home subfolder that contains the .html, .css, and .js files that make up the home PageControl. We cover how this PageControl is defined in the code shortly. We also look at WinJS built-in controls in detail during Hour3.
Even though WinJS provides these objects, we would need to write a lot of code to have our single-page apps navigate correctly. Fortunately, the Navigation App project template solves this problem with the navigator.js file. The navigator.js code uses the WinJS.Navigation object and the WinJS.UI.Pages object to accomplish the common needs of single-page applications.
Working with the PageControlNavigator
The navigator.js exposes a PageControlNavigator namespace. PageControlNavigator is not part of WinJS; it is boilerplate code that the Visual Studio project template adds to the project. We can thus customize it as needed. In this book, we use it as is because it meets our needs nicely.
The navigator.js code creates a WinJS control called PageControlNavigator. Again, this control is not part of WinJS, but our app code (albeit, boilerplate code) is creating this new control on-the-fly and making it a part of the WinJS namespace. This is the power of a dynamic language such as JavaScript. This means that any of our app codes can create a WinJS control. We cover how to create our own control during Hour 4, “Creating WinJS Namespaces, Classes, and Custom Controls.”
To see how the PageControlNavigator control is used, we can look at the default.html file:
<
div
id
="contenthost"
data-win-control
="Application.PageControlNavigator"
data-win-options
="{home: '/pages/home/home.html'}"></
div
>
This div has an id of contenthost. The actual id value is not important and can be anything. By using the data-win-control, we instantiate the Application.PageControlNavigator control. With the data-win-options attribute (another custom data attribute that Microsoft provides), we can pass parameters to the control’s constructor. It takes a JavaScript Object Literal. For the PageControlNavigator, we need to set the home property to the actual URL of the page we want to load inside default.html. If the control took more than one parameter, we would separate them with a comma just as in any JavaScript Object Literal.
Examining the Home Page Control
Listing 2.1 contains the markup of /pages/home/home.html. This page control is loaded into the default.html because we passed this page to the home parameter of the Application.PageControlNavigator constructor.
Listing 2.1. The Markup Portion of the Home Page Control
<!
DOCTYPE
html
>
<
html
>
<
head
>
<
meta
charset
="utf-8"
/>
<
title
>
homePage
</
title
>
<!-- WinJS references -->
<
link
href
="//Microsoft.WinJS.1.0/css/ui-dark.css"
rel
="stylesheet"
/>
<
script
src
="//Microsoft.WinJS.1.0/js/base.js"></
script
>
<
script
src
="//Microsoft.WinJS.1.0/js/ui.js"></
script
>
<
link
href
="/css/default.css"
rel
="stylesheet"
/>
<
link
href
="/pages/home/home.css"
rel
="stylesheet"
/>
<
script
src
="/pages/home/home.js"></
script
>
</
head
>
<
body
>
<!-- The content that will be loaded and displayed. -->
<
div
class
="fragment homepage">
<
header
aria-label
="Header content"
role
="banner">
<
button
class
="win-backbutton"
aria-label
="Back"
disabled
type
="button"></
button
>
<
h1
class
="titlearea win-type-ellipsis">
<
span
class
="pagetitle">
Welcome to NavigationApp!
</
span
>
</
h1
>
</
header
>
<
section
aria-label
="Main content"
role
="main">
<
p
>
Content goes here.
</
p
>
</
section
>
</
div
>
</
body
>
</
html
>
The code in Listing 2.1 is more than just a fragment of markup—it is an entire page! However, this is not a problem. Any scripts that our fragments want to load that are already loaded are ignored. This means that the WinJS script files (base.js and ui.js) and the default.js are loaded when the default.html page loads. Because default brings in content from the home.html file, home.js is also loaded. Both base.js and ui.js are ignored because they were already loaded by default.html. Having all the scripts and styles in the page controls is beneficial because it enables us to load the page control in Blend for Visual Studio and see exactly what it will look like. We talk about Blend for Visual Studio during Hour 5.
The JavaScript portion of the home page control is a self-executing anonymous function that registers itself with WinJS as a page control. This is done through the WinJS.UI.Pages.define function:
(
function
() {
"use strict"
;
WinJS.
UI.
Pages.
define(
"/pages/home/home.html"
, {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready:
function
(
element,
options) {
// TODO: Initialize the page here.
}
});
})();
The define function takes in the id of the page control (the URL) as the first parameter and takes an object literal as the second parameter. The object literal contains the page control’s functions. Take a minute and let this code sink in. We are telling WinJS that we want to define a page control. We give it an id (the relative URL of the markup) and then tell it to create some functions on-the-fly that we want it to add to the page control. Dynamic languages aren’t half bad! (Remind me I said that when we get to the debugging portion of the book in Hour 7, “Debugging, Securing, and Measuring Our Apps’ Performance.”)
If WinJS.UI.Pages.define is called multiple times with the same URL, the members are added to the existing members. If a member already exists, the last one defined replaces the one already present.
The last part of the page control is the style sheet. The contents of home.css are as follows:
.homepage section[role=main]
{
margin-left
:
120px
;
}@media
screen
and
(
-ms-view-state
:
snapped)
{
.homepage section[role=main]
{
margin-left
:
20px
;
}
}
@media
screen
and
(
-ms-view-state
:
portrait)
{
.homepage section[role=main]
{
margin-left
:
100px
;
}
}
This is standard CSS using the attribute selector to style the section content of the homepage class. The content is simply being spaced 120 pixels from the left. This is the correct positioning for Microsoft design style content. We discuss this more during Hour 5. The last two CSS rules are inside media queries (@media), which we discuss during Hour 13.
We can run the application and see a single page where the content was loaded from the home page. Figure 2.11 shows the result (after changing the style of default.html and home.html to use ui-light.css instead of ui-dark.css).
Figure 2.11. The NavigationApp with the ui-light style sheet applied.