In a previous post I discussed the five different types of Metro style apps that are relevant for Windows 8. In this series I will walk you through building a basic data-snacking application.
Topics Covered
- Windows 8 Metro apps
- HTML & JavaScript Metro apps
- Using data from Web APIs
- Asynchronous calls
- JavaScript Promises
- ListView UI control
- Data binding HTML controls
Get the sample app.
The Scenario
For review, a data snacking app is one that enables users to consume small chunks of information in a hurry, as time permits. These apps are used regularly and repetitively by users during “down-time” (e.g. waiting in a lobby, on the metro (pun intended), before a meeting, etc.). For the purpose of this walk-through I will build a weather app, similar to the Weather app included in the Windows 8 Developer Preview release. This weather app, named “Seven Days,” will show the current weather conditions, plus the forecasts for the next six days (seven days in all, hence the name). In this post I will build the app to get weather data based on a static (aka hard coded) location, and in follow-up posts I will add the ability to get the weather info based on either the current location of the device the app is running on, or through user input of one or more locations.
Language Choice
The first consideration is whether to build a XAML/C# app or an HTML/JavaScript app. Ultimately I know that this application is going to revolutionize the weather-data-snacking market, so I want it to reach as many platforms as possible, so I am going with HTML5 and JavaScript. Of course, I know that this choice doesn’t automatically make my app portable, but as a long-time ASP.NET developer, HTML and JavaScript are more familiar to me than XAML (although I do love C#), and using HTML/JavaScript means that I will have some ability to reuse assets (e.g. HTML layouts, CSS styles, base-level JavaScript functions) across device platforms, rewriting only platform-specific code.
Its settled, I will use HTML and JavaScript.
Using Visual Studio 11 Express for Windows Developer Preview, I create a new Fixed Layout project and get to work.
Setting up the UI
I tend to think in user experience first, and prefer to start with my UI layout before anything else. Looking at the screenshot above, there is a basic structure I need to build – three rows (one in the middle row—outlined in a green dashed line below—for the current weather detail, and one in the bottom row—outlined in a red dashed line below—for the forecast) with two grids (the current weather detail grid—outlined in a red solid line—and a forecast grid—outlined in white).
Using DIV Elements as Placeholders
The first two rows are pretty straight forward – I’ll use a set of DIV elements for the current temperature and location in the top row, and a TABLE with DIV elements for the current weather detail in the middle row.
<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″ />
<meta name=”viewport” content=”width=1024, height=768″ />
<title>Seven Days by Doug Seven</title>
<!– WinJS references –>
<link rel=”stylesheet” href=”/winjs/css/ui-dark.css” />
<script src=”/winjs/js/base.js”></script>
<script src=”/winjs/js/ui.js”></script>
<script src=”/winjs/js/binding.js”></script>
<script src=”/winjs/js/controls.js”></script>
<script src=”/winjs/js/animations.js”></script>
<script src=”/winjs/js/uicollections.js”></script>
<script src=”/winjs/js/wwaapp.js”></script>
<!– SevenDays references –>
<script src=”/js/default.js”></script>
<link rel=”stylesheet” href=”/css/default.css” />
</head>
<body>
<div data-win-control=”WinJS.UI.ViewBox”>
<div class=”fixed-layout”>
<div id=”topRow”>
<div id=”currentWeather”>
<div id=”currentTemp”>
</div>
<div id=”currentLocation”>
</div>
</div>
</div>
<div id=”middleRow”>
<div id=”statusMessage”>
</div>
<div id=”currentWeatherData”>
<table>
<tr>
<td class=”currentWeatherElement”>
<div id=”currentTempRange”
class=”currentWeatherDataItem”>
</div>
High / Low
</td>
<td class=”currentWeatherElement”>
<div id=”humidity”
class=”currentWeatherDataItem”>
</div>
Humidity
</td>
<td class=”currentWeatherElement”>
<div id=”currentCondition”
class=”currentWeatherDataItem”>
</div>
Current condition
</td>
</tr>
<tr>
<td class=”currentWeatherElement”>
<div id=”feelLike”
class=”currentWeatherDataItem”>
</div>
Feels like
</td>
<td class=”currentWeatherElement”>
<div id=”uvIndex”
class=”currentWeatherDataItem”>
</div>
UV Index
</td>
<td class=”currentWeatherElement”>
<div id=”wind”
class=”currentWeatherDataItem”>
</div>
Wind
</td>
</tr>
</table>
</div>
<div id=”currentDateTime”>
</div>
</div>
<div id=”bottomRow”>
</div>
</div>
</div>
</body>
</html>
This is pretty straight forward, so I won’t go into any detail on it. I also spent some time on CSS definition, to make it look nice. Nothing surprising in the CSS, and it is available as part of the sample download.
Using a ListView
For the bottom row, I anticipate getting a feed of data from a weather service, and binding that data to a grid layout with a single row of six items. The ListVIew control (WinJS.UI.ListView) works in just that way. I can use the ListView to create a grid layout and use a template to define how the items in the grid will look. In this case, I want a grid where each item shows the date, a weather icon, the high and low expected temperatures and a brief forecast on the conditions of the day.
The ListView is a DIV element with a data-win-control attribute set to WinJS.UI.ListView.
<div id=”forecastGrid”
data-win-control=”WinJS.UI.ListView”
data-win-options=”{itemRenderer: template,
layout: {type: WinJS.UI.GridLayout, maxRows:1}}”>
</div>
I’ve also set the data-win-options attribute to define how I want the ListView to render (use an item template named “template”, layout as a grid (as opposed to a list), and have no more than one row). The use of the template is key; this is what enables me to define the look of each item that is rendered in the grid.
A template is a DIV with the data-win-control attribute set to WinJS.Binding.Template. This enables it to be the template used when data bound to a source (don’t worry, I’ll get to that soon).
<div id=”template”
data-win-control=”WinJS.Binding.Template”>
<div class=”dailyWeather”>
<div class=”dayTitle”>
<span data-win-bind=”innerText: Day” class=”dayPart”></span>
<span data-win-bind=”innerText: Date” class=”datePart”></span>
</div>
<div class=”weatherIcon”>
<img data-win-bind=”src: IconUrl; alt: IconAlt” />
</div>
<div data-win-bind=”innerHTML: HighLow”
class=”highLow”>
</div>
<div data-win-bind=”innerText: Condition”
class=”condition”>
</div>
</div>
</div>
Inside the template I have defined how I expect the data source to be bound to the UI elements. Using the data-win-bind attribute I define the element attribute to bind, and the data source item’s property to bind to.
<img data-win-bind=”src: IconUrl; alt: IconAlt” />
In the IMG element example, I have specified that the SRC attribute should be bound to the item.IconUrl value and the ALT attribute should be bound to the item.IconAlt value.
Getting Data from a Web API
The next step in building Seven Days is to get some real data. There are a lot of weather Web APIs to choose from, and I’m not here to tell you which one to use. For the purpose of this example I have chosen to use Weather Underground (Wunderground.com) because they provide their results in JSON format, have clear pricing structures (including a free Developer level) and will provide up to 10-days of weather forecasts (many other weather Web APIs only provide 2 or 3 days of forecasts).
The first thing I need to do is add some JavaScript to get the weather data for a predefined location (I won’t add fancy functionality like geolocation lookup, or pick-a-location until I get the basics working). To do this I can use WinJS.xhr, which is an asynchronous wrapper on the XMLHttpRequest object, to make the request, and return the response.
// Query Format: http://api.wunderground.com/api/KEY/FEATURE/%5BFEATURE…%5D/
// q/QUERY.FORMAT
function weatherLookup(loc) {
// Set the date and time info
var d = new Date();
id(“currentDateTime”).innerText = d.toLocaleDateString()
+ “, ”
+ d.toLocaleTimeString();
var apiUrl = “http://api.wunderground.com/api/”
+ apiKey
+ “/geolookup/conditions/forecast10day/q/”
+ loc
+ “.json”;
// WinJS.xhr is an async wrapper for the XMLHttpRequest class.
WinJS.xhr({ url: apiUrl }).then(
processWeather, // Called when the promise is complete
dataFetchingError // Called when there is an error
);
}
wunderground.weatherLookup = weatherLookup;
The apiKey value is provided to you when you create an account on wunderground.com, and the loc value is whatever predefined location string you want to use (I am using “WA/SAMMAMISH”).
Working with Promises
The WinJS.xhr object is a “promise” object. There is a proposed JavaScript standard for promises, which enable invoking methods which promise to return a value at a point in the future. The WinJS.Promise object exposes a “then” method (which I have used in this example) which identifies what to do when the promise returns a value. The first argument of “then” is the callback function to invoke when the promise (in this case, sending the HTTP request and getting back the response) completes successfully. The second argument is the callback function to invoke when the promise returns an error. There is a third option for invoking a progress callback, if the promise reports on progress (so far very few promise operations implement the progress callback option).
Anytime you are invoking a method that could take longer than 50-milliseconds, the guidance is to use an asynchronous method, which enables the UI to continue working while the asynchronous method executes. That is exactly what the “promise” object and “then” method enable. When the promise object is invoked, the code flow surrounding the promise continues (e.g. the next line of code is executed) and the asynchronous (promise) method is invoked on another thread. This enables long running operations (like retrieving data from a Web API) to be called without blocking the UI functionality, resulting in a much more responsive (aka “fast and fluid”) application.
Consider the following example, where two asynchronous calls are made to get data from Bing.com using the Promise/Then model. When the application runs, the progress callback is called every time the XMLHttpRequest object being used changes its ready state. Since each of the requests is running asynchronously, the results are intermixed, and the UI is refreshed without blocking any responsiveness.
(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) {
// Add an event listener for the button’s click event.
document.getElementById(“myButton”).addEventListener(
‘click’, onMyButtonClicked,false);
}
}
function onMyButtonClicked() {
getWebContent();
}
function getWebContent() {
var bingUrl =“http://www.bing.com/search?q=windows+8”;
id(“contentDiv”).innerHTML =“Calling xhr (First).<br/>”;
// WinJS.xhr is an async wrapper for the XMLHttpRequest class.
WinJS.xhr({ url: bingUrl }).then(
onComplete, // Called when the promise is complete
onError, // Called when there is an error
onProgress // Called when the promise reports progress
);
id(“contentDiv”).innerHTML +=“Called xhr (First).<br/>”;
id(“contentDiv”).innerHTML +=“Calling xhr (Second).<br/>”;
WinJS.xhr({ url: bingUrl }).then(
onComplete,
onError,
onProgress2 // Call a different function to show progress.
);
id(“contentDiv”).innerHTML +=“Called xhr (Second).<br/>”;
}
function onComplete(request) {
id(“contentDiv”).innerHTML +=“Request complete.<br/>”;
}
function onError(request) {
id(“contentDiv”).innerHTML +=“<p>!!!ERROR!!! “+request.status+“</p>”;
}
function onProgress(request) {
id(“contentDiv”).innerHTML +=“Progress on First.<br/>”;
}
functiononProgress2(request) {
id(“contentDiv”).innerHTML +=“Progress on Second.<br/>”;
}
functionid(elem) {
returndocument.getElementById(elem);
}
WinJS.Application.start();
})();
Connecting the Data to the UI
Now that I have data coming from a Web API, the last thing I need to do to get Seven Days working, so I can see the fruit of my labor, is to populate the UI controls with the appropriate data. The data from the WinJS.xhr() call is returned when the “complete” callback is invoked (i.e. processWeather()).
// WinJS.xhr is an async wrapper for the XMLHttpRequest class.
WinJS.xhr({ url: apiUrl }).then(
processWeather, // Called when the promise is complete
dataFetchingError // Called when there is an error
);
The processWeather function uses the JSOPN.parse method to create objects from the data returned. To make setting UI attributes easier, I create four objects from the JSON results returned by the Web API.
var forecastList;
function processWeather(request) {
// Clear the forecast list
forecastList = [];
// Parse the JSON response into navigatable objects
var weatherData = JSON.parse(request.responseText);
var currentObservation = weatherData.current_observation;
var currentForecast = weatherData.forecast.simpleforecast.forecastday[0];
var forecasts = weatherData.forecast.simpleforecast.forecastday;
Each of these objects will be used to populate different parts of the UI.
- weatherData represents the entire JSON result set.
- currentObservation represents the current conditions for the specified location, including an array of weather stations nearby and temperature, as well as some, but not all of the details (like UV index, wind chill, condition, humidity, wind direction and wind speed).
- currentForecast represents forecast data for the current day (the first item in the forecastday array), including expected high and low temperatures.
- forecasts is an array of forecasts for the current day and the following 9-days (the Web API request specified a 10-day forecast) and will be used to display the lower table of forecasts.
To populate the UI I am using a combination of explicit value setting and data binding. As the app develops, and I add the ability to get weather for multiple cities, I may move to an entirely data binding model, but that is overkill for the app right now.
To do the element value setting (for the top two rows), I am going to steal borrow an id() function from the Windows 8 SDK samples to make setting values easier.
function id(elemName) { return document.getElementById(elemName); }
This enables me to set an element’s innerText or innerHTML value (or any other property) in the following way:
id("currentTemp").innerHTML = tempString + "°";
Using this model, the next section of the processWeather function looks like this:
// Set the values of the current day weather
var tempString = currentObservation.temp_f.toString();
tempString = tempString.indexOf(‘.’) > 0 ?
tempString.substring(0, tempString.length – 2) :
tempString;
id(“currentTemp”).innerHTML = tempString + “°”;
id(“currentLocation”).innerText = currentObservation.display_location.full;
var todayHigh = currentForecast.high.fahrenheit;
var todayLow = currentForecast.low.fahrenheit;
id(“currentTempRange”).innerHTML = todayHigh + “° / ” + todayLow + “°”;
id(“feelLike”).innerHTML = currentObservation.windchill_f + “°”;
id(“uvIndex”).innerText = currentObservation.UV;
id(“currentCondition”).innerText = currentObservation.weather;
id(“humidity”).innerText = currentObservation.relative_humidity;
var windSpeed = currentObservation.wind_mph;
var windDir = currentObservation.wind_dir;
id(“wind”).innerText = windSpeed + ” mph ” + windDir;
id(“lastUpdated”).innerText = currentObservation.observation_time + “. Data provided by wunderground.com”;
With the top two rows populated its now time to build the data source for the lower grid and bind the ListView control to the data source. To build the data source I loop through the forecasts object that I created from the JSON results (skipping the first item, which is today’s forecast), and create an array of forecast items with specific properties that will be displayed. Once the loop has iterated six times, I break out of the loop (remember, this app is Seven Days, not Ten Days). The last step is to set the data source of the ListView control to the array that was just created.
// Build the forecast list for the next six days
for (var i = 1, len = forecasts.length; i < len; i++) {
var item = forecasts[i];
var itemHigh = item.high.fahrenheit;
var itemLow = item.low.fahrenheit;
var itemHighLow = itemHigh + “° / ” + itemLow + “°”;
var forecastItem = {
Day: item.date.weekday_short,
Date: item.date.day,
HighLow: itemHighLow,
IconAlt: item.icon,
IconUrl: item.icon_url,
Condition: item.conditions
};
if (i == 1) {
forecastItem.Day = “Tomorrow”;
forecastItem.Date = “”;
}
forecastList.push(forecastItem);
// Once there are six days of weather, stop building the list
if (i >= 6)
break;
}
// Populate the forecastGrid control
forecastGrid.winControl.dataSource = forecastList;
}
To trigger the binding of the UI elements to the data source, I need to invoke WinJS.UI.processAll() which processes all declarative controls so that any that are data bound get processed and the data is rendered on screen. I have this call in the default.js file as the last line of the initialization function.
WinJS.Application.onmainwindowactivated = function (e) {
if (e.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
// TODO: startup code here
wunderground.weatherLookup(“WA/SAMMAMISH”);
// process the declarative controls
WinJS.UI.processAll();
}
}
With that, I now have a functioning weather app (there is some CSS styling I didn’t show in this post, but is in the sample code).
Summary
In this post I showed you how to create a basic Metro style app for Windows 8 that asynchronously calls a Web API to get and display some data, both explicitly and with data binding. In follow up posts I will show you how to
- use the geolocation capabilities of Windows 8 to get actual position data to display weather for
- how to use local storage to save a list of favorite cities to get weather for
- how to use the AppBar to expose additional capabilities
- how change the UI when the device orientation changes.
- In the final set of posts I will show you how to port this application to the iPad and Android tablets to extend the reach of your application.
What About Video
On a final note, the app wouldn’t have the right level of polish without a cool HD video background running in a loop, which can be done easily with the following HTML and CSS (the video is not included in the sample download):
HTML
<video id="vidbg" src="/video/background.mp4" autoplay="autoplay" loop="loop" />
CSS
#vidbg { position: absolute; top: 0px; left: 0px; width: 100%; z-index: -1; }