The Windows Libraries for JavaScript: Part I
DISCLAIMER: This post is targeted at the //build/ version of the Windows Developer Preview (aka Windows 8). Things are likely to change with future releases. On your head be it.
In the last post in this series, we looked at getting started building Metro style apps built using JavaScript (Metro/JS apps) with Microsoft Visual Studio 11 for the Windows Developer Preview (aka VS11) and Microsoft Expression Blend 5 Developer Preview (aka Blend).
In this installment, we’re going to take a look at the library that brings WinRT and the web platform together: the Windows Library for JavaScript (aka WinJS) and build an app while we’re doing it.
The Need for WinJS
While it is the case, as you’ll see, that a Metro/JS apps have the same access to the underlying Windows platform as any other language projection (like Metro/VB, Metro/C# and Metro/C++ apps), it’s also the case that HTML/JavaScript programmers have a certain style that they are used to programming in. In some cases, those habits need to change because Metro/JS apps aren’t web sites or even web applications; Metro/JS apps are native applications that happen to be built with HTML, JavaScript, CSS, SVG, etc.
However, in many cases, existing JavaScript habits are good ones and should be encouraged. Towards that end, Microsoft engineers have built the Windows Libraries for JavaScript (WinJS), which is a set of reusable JavaScript and CSS files which were created specifically to make it easier for you to build Metro/JS apps with the right Win8 “feel” to them.
The easiest way to bring WinJS into your project is to create an application using any of the Metro/JS project templates, since all of them include the WinJS files:
WinJS is defined by the files under the winjs folder. The Blank Application project template produces an application with an empty window, but it does it with style; specifically the style that makes a Metro/JS app look like a Win8 app. Of course, the styles alone can’t make everything right — you’ll also need the right layout, behavior, etc. As an example of how to build a Metro/JS apps that’s slightly more exciting than the one we’ve been looking at so far, let’s build one of the most famous members of the Windows team — Raymond Chen.
Mr. Chen is a developer on the Windows team that brought you the Windows Developer Preview (aka Win8). Further, he’s the author of the most excellent book “The Old New Thing” based on a blog of the same name:
Chen’s blog is famous for digging into the forgotten nooks and crannies of the Windows platform. And for our purposes, Chen’s blog is an excellent target for our sample because he’s chosen a blogging platform that exposes the full text of his posts in a programmatic form (both RSS and Atom) so that we can build a Win8 app to show them.
Any blog displayed in the browser is essentially the title, date and content from each of the latest blog posts. To start, we need to download the data for each of his posts. To do that, we first need to know where to put that code.
The WinJS.Application object
The Blank Application template-generated default.html loads a minimal set of WinJS JavaScript and CSS files:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>RssReader</title> <!-- WinJS references --> <link rel="stylesheet" href="/winjs/css/ui-dark.css" /> <script src="/winjs/js/base.js"></script> <script src="/winjs/js/wwaapp.js"></script> <!-- RssReader references --> <link rel="stylesheet" href="/css/default.css" /> <script src="/js/default.js"></script> </head> <body> </body> </html>
Updating the sample with some slightly more interesting HTML:
<!DOCTYPE html> <html> ...
<body> <h1>The Old New Thing</h1> <div id="downloadStatus"></div> <div id="posts"></div> </body> </html>
Here we’re setting the header text of our app and creating two div elements as placeholders: one for the status of our RSS download and one for the data from the RSS when we get it. Also, let’s replace the contents of default.css with styles to match Chen’s blog:
body { background-color: #fff; color: #000; font-family: Verdana; padding: 8pt; } a:link, a:visited, a:active { color: #700; font-weight: inherit; } h1 { text-transform: none; font-family: inherit; font-size: 22pt; } #posts { width: 99%; height: 100%; overflow: auto; } .postTitle { color: #700; font-size: 1.2em; font-weight: bold; } .postDate { color: #666; font-size: 11pt; } .postContent { font-size: medium; line-height: 18px; }
To support app lifetime, Win8 provides a set of app-level events. The generated default.js file subscribes to the app’s mainwindowactivated event and then starts the WinJS application:
(function () { 'use strict'; // Uncomment the following line to enable first chance exceptions. // Debug.enableFirstChanceException(true); WinJS.Application.onmainwindowactivated = function (e) { if (e.detail.kind ===
Windows.ApplicationModel.Activation.ActivationKind.launch) {
// TODO: startup code here } } WinJS.Application.start(); })();
The contents of the generated default.js file are contained within a self-executing anonymous function declaration (that is a mouthful — it’s fun to use a language with key concepts missing).
By default, any function or variable defined outside of a function in your JavaScript files is global across your Metro/JS app. Variables and functions defined within a function are only visible within that function. The template uses this outer function declaration to help you follow good JavaScript design patterns, ensuring that your functions and variables do not pollute the global namespace.
After grabbing the Application object from the WinJS namespace, the code subscribes to the event fired after the application and its resources (like default.html) have been loaded. This is a good place to do initialization, as we’ll see.
For any of the application events to fire, you need to let the application know you’re ready to receive them, which is what the call to the start method does.
The mainwindowactivated event handler is the perfect place for us to kick off the download of the data from Chen’s blog.
Async HTTP using WinJS.xhr
The xhr function in the WinJS namespaces provides a series of options for downloading data in both text and XML formats using HTTP. The name of the xhr function stands for XMLHttpRequest, which is the name of the object that sparked the AJAX/Web 2.0 revolution around 2005 (although the object has been part of Internet Explorer since version 5.0 released in 1999). The xhr function provided with WinJS is a wrapper that takes a number of options, including which HTTP verb to use (GET by default), which HTTP headers to include (none by default) and which URL to retrieve data from:
WinJS.Application.onmainwindowactivated = function (e) { // start the download var chen = "http://blogs.msdn.com/b/oldnewthing/rss.aspx"; downloadStatus.innerText = "downloading posts..."; WinJS.xhr({ url: chen }).then(processPosts, downloadError); }
Instead of providing the option to retrieve the data synchronously or asynchronously the way the XMLHttpRequest object does, xhr forces an asynchronous call so that the UI will not be blocked while data is retrieved. You’ll see this all over the Win8 programming model as reflected into Metro/JS and it helps you make more responsive programs. In our case, because we don’t know how long the request is going to take, we’re setting the text content of the downloadStatus div to indicate progress.
Asynchronous functions from WinJS return an object called a promise, which represents results to be provided at some time in the future. The promise object exposes the then method, which takes an optional three functions, one for success, one for failure and one for progress.
The call to the xhr function returns the promise immediately, at which point we set the functions to call in the case of success or failure, ignoring progress:
function processPosts(request) { // clear the progress indicator downloadStatus.innerText = ""; // parse the RSS var items = request.responseXML.selectNodes("//item"); if (items.length == 0) { downloadStatus.innerText = "error downloading posts"; } for (var i = 0, len = items.length; i < len; i++) { var item = items[i]; // append data to #posts div var parent = document.createElement("div"); appendDiv(parent, item.selectNodes("title")[0].text, "postTitle"); appendDiv(parent, item.selectNodes("pubDate")[0].text, "postDate"); appendDiv(parent, item.selectNodes("description")[0].text, "postContent"); posts.appendChild(parent); } } function appendDiv(parent, html, className) { var div = document.createElement("div"); div.innerHTML = html; div.className = className; parent.appendChild(div); } function downloadError() { downloadStatus.innerText = "error downloading posts"; }
When the xhr call has completed successfully, the processPosts function is called with a request object which has all the information as if we’d use the XMLHttpRequest object directly. The property we care about is responseXML, which contains the RSS data we requested. From the RSS, we use an XPath expression to select the set of item nodes, extracting the title, pubDate and description data to be used to create a div element per item, with div elements for each of the pieces of data we’re showing, styled appropriately.
For readers of The Old New Thing, the output should look pretty darn familiar:
At this point, you may worry about whether our use of the xhr function will actually work, since a page provided by a web server would fail. The reason this works is because Metro/JS apps run in a secure sandbox and are, by default, allowed the ability to make cross-domain HTTP requests even though web pages are not.
Actually, it’s not quite true that Metro apps are allowed access to the Internet by default — it’s just that Metro app project in VS11 are provided that access by default, as it’s such a common need.
Where Are We?
At this point, we’ve built a working Metro/JS app that does a real thing using WinJS. In the next installment, we’ll look at how WinJS provides features to make DOM manipulation better.
Portions Copyright © 2011 Microsoft Corporation. Reproduced with permission from Microsoft Corporation. All rights reserved.