Creating stock apps (Windows Store apps)

This article walks you through the Stocks app sample.

Application layout and styling

Before we take a look at the code, let’s take a shallow dive into the application UI to give you an idea of the various application components that the code controls. We’ll start with a look at the application’s splash screen and default tile. Then we'll look at the HTML that is used to lay out the application, and then we'll take a look at how CSS3 is used to style the app and handle the various app views.

Setting the default app tile and splash screen

The application tile and the splash screen are the first things that customers see when they load the Stocks example app. This screen shot shows the application splash screen displayed within the simulator.

Application splash screen displayed within the simulator

The next screen shot shows the default tile for the Stocks app on the Start screen, displayed among other tiles.

Default till for the Stocks app, along with other tiles

These visuals are set from within Microsoft Visual Studio by double-clicking the application’s package.appmanifest file and setting the values for Splash Screen and Tile: Logo / Wide Logo / Small Logo. The next screen shot shows the values within the Visual Studio interface.

Package.appmanifest settings in Visual Studio interface

In addition to using the default tiles, you can create a live tile that will show updates to the user when they are not in the app.

Laying out the app in HTML

When the application starts, default.html is loaded. It contains the app UI including three important div elements:

  • The contentHost, which is used for navigation and containing the app UI
  • The AppBar, which is used for containing the app controls
  • The addStockFlyout div for adding stocks

This image shows how these elements are rendered on the page.

Rendering of the default page with contentHost, appBar, and addStockFlyout

Note  The contentHost is specified in a separate file. This is done to separate the content view from the application.

You see the dashboard view because when the app starts, the code in default.js uses the hierarchical navigation pattern to load the dashboard view, specified in dashboard.html. This image shows the layout that is specified by default in the dashboard view:

Default layout for the dashboard view

This layout is set up within a Cascading Style Sheets, Level 3 (CSS3) content region that is displayed by default. The content region in the dashboard view contains three high-level sections specified in div elements:

  • The header div, which contains app branding and a logo
  • The stockPanel div, used for holding the images for the selected stock
  • The marketNews div, used for displaying information about the selected stock

We’ll take a closer look at the stockPanel div and marketNews div because these contain additional UI elements that have additional elements.

stockPanel

The stockPanel div contains the following elements:

  • The myStocks div, which contains a styled unordered list (ul) that lists the current stocks set up in the app
  • The mainChart div, which uses a HTML Canvas 2D Context to render the stock chart for the currently selected stock
  • The range div, which contains a styled radio control with various date ranges
  • The stockInfo div, which contains an HTML5 grid that contains statistics about the currently selected stock

marketNews

The marketNews div contains the following elements:

  • The marketNewsTitle div, which lists the current stock title
  • The marketNewsList div, which hosts a ListView control with news pulled for the currently selected stock
An additional CSS3 region, allInOne, is specified for snap view. This region will be described in further detail in the section, Styling for various view states.

Basics of styling the app in CSS3

Now that we’ve seen how the content is laid out in the app, we’ll take a closer look at how the various UI elements are styled in CSS3. This image shows the various application components and their CSS3 labels:

Application components and their CSS labels

At a high level, various elements are styled globally within the content div. The following example shows how the content block, header, and header title are styled in dashboard.css:



#content
{
    height:100%;
    width:100%;
    display: -ms-grid;
    -ms-grid-columns: 1fr;
    -ms-grid-rows: auto 1fr;
    margin-top:-5px;
}

#content .header
{
    margin-top:42px;
    display:-ms-grid;
    -ms-grid-columns:100px 1fr;
    -ms-grid-rows:1fr;
}

#content .header .title
{
    font-size:42pt;
    line-height: 48pt;
    font-family:'Segoe UI Light';
    float:left;
}


The following code shows the HTML for the title that will have the #content .header .title style applied.



<div id="content">
     <div class="header">
          <div class="titleArea">
               <div class="title">Stocks</div>
          </div>
     </div>
</div> 

Beyond these conventional applications of CSS3 style are controls and other elements that use more complicated layout. This image shows details of the range control:

Detailed view of the control that sets date range in the app

This control uses custom glyphs, on-off state, and transparency for styling the radio buttons that modify the date range on the stock chart for a week, month, or year. The following CSS3 styles show how this is done:



#content .info .chart .range button
{
    padding:0;
    border:0;
    margin:0;
    display: inline-block; 
    width: 32px;
    height: 32px;
    min-width: 32px;
    min-height: 32px;
    margin-right:20px;
}

#content .info input[type="radio"].rangeOption, #content .info input[type="radio"].rangeOption::-ms-check, #content .info input[type="radio"].rangeOption:hover::-ms-check, #content .info input[type="radio"].rangeOption:active::-ms-check, #content .info input[type="radio"].rangeOption:hover:active::-ms-check, #content .info input[type="radio"].rangeOption:hover:active:disabled::-ms-check
{
    color: transparent;
    border: 0px;
    background-color: transparent;
    padding: 0px;
    margin: 0px;
}

#content .info .range input[type="radio"]
{
    width:32px;
    height:32px;
    margin-right:20px;
    margin-top:20px;
}

#content .info .range input[type="radio"].oneWeek
{
    background: transparent url('/images/stocks_1w_glyph.png') no-repeat; 
}


#content .info .range input[type="radio"].oneWeek:checked
{
    background: transparent url('/images/stocks_1w_active_glyph.png') no-repeat; 
}


#content .info .range input[type="radio"].oneMonth
{
    background: transparent url('/images/stocks_1m_glyph.png') no-repeat; 
} 
 
#content .info .range input[type="radio"].oneMonth:checked
{ 
    background: transparent url('/images/stocks_1m_active_glyph.png') no-repeat; 
}

#content .info .range input[type="radio"].oneYear
{
    background: transparent url('/images/stocks_1y_glyph.png') no-repeat; 
} 
 
#content .info .range input[type="radio"].oneYear:checked
{ 
    background: transparent url('/images/stocks_1y_active_glyph.png') no-repeat; 
}


The default HTML for radio buttons in the CSS3 styles is hidden and replaced with images that reflect the button states. Because you have a radio control, only one of the buttons will be in the selected state at a time. The following HTML shows how the radio buttons are used in practice.



<div class="range">
    <input type="radio" name="rangeGroup" value="1w" class="oneWeek rangeOption" />
    <input type="radio" name="rangeGroup" value="1m" class="oneMonth rangeOption"/>
    <input type="radio" name="rangeGroup" value="1y" class="oneYear rangeOption"/>
</div>


The next UI item of interest is the grid element that contains details about the currently selected stock. This image illustrates how the grid is laid out.

Grid layout for details of a selected stock

Within each of the outlined elements is both a layout and label. For example, the lastChange grid section contains two elements: changeLabel and change. The following code shows the HTML markup for the grid.



<div class="details">
    <div class="grid"></div>
    <div id="stockInfoTemplate" data-win-control="WinJS.Binding.Template">
        <div class="changeLabel">Change</div>
        <div class="change"><div class='changeValue'><span class='arrow' data-win-bind="textContent: changeArrow"></span><span data-win-bind="textContent: change"></span></div></div>
        <div class="lastTradeLabel">Last trade</div>
        <div class="value lastTrade" data-win-bind="textContent: lastSale"></div>
        <div class="lastTradeTimeLabel">Last trade time</div>
        <div class='value lastTradeTime' data-win-bind="textContent: lastSaleTime"></div>
        <div class="openLabel">Open</div>
        <div class="value open" data-win-bind="textContent: open"></div>
        <div class="daysRangeLabel">Day's range</div>
        <div class="value daysRange" data-win-bind="textContent: daysRange"></div>
        <div class="yearRangeLabel">52 week range</div>
        <div class="value yearRange" data-win-bind="textContent: fiftyTwoWeekRange"></div>
        <div class="volumeLabel">Vol. (Mil)</div>
        <div class="value volume" data-win-bind="textContent: volume"></div>
        <div class='value lastRefresh'>This sample uses test data for demonstration purposes.</div>
    </div>
</div>


The following CSS3 styles set up the grid for containing the various grid elements.



#content .info .stockPanel .details .grid
{
    font-size:15pt;
    font-family: 'Segoe UI Semilight';
    display: -ms-grid;
    -ms-grid-row: 1;
    -ms-grid-column-span: 2;
    -ms-grid-columns: 300px 50px 100px auto 40px 150px 200px 20px;
    -ms-grid-rows: auto 22px auto 7px auto;
}


The grid width and height are explicitly set in pixels to target a specific minimum width and height for the fill state (1024 x 768), and that will scale when the client resolution expands along the rows and columns that are set to auto. Next, the various cells within the grid are set up using CSS3. For example, the following CSS3 styles set up the label and data classes used for the lastTrade values.



#content .info .stockPanel .details .grid .lastTradeLabel
{
    -ms-grid-column: 3;
}

#content .info .stockPanel .details .grid .lastTrade
{
    -ms-grid-column: 4;
}


The final UI element of interest that receives styling is the marketNews div. This element uses the ListView, which internally uses a grid to lay out elements. The following markup shows the template used for items:



<div id="marketNewsItemTemplate" data-win-control="WinJS.Binding.Template">
    <div class="itemTitle" data-win-bind="textContent: title"></div>
    <div class="itemDescription" data-win-bind="textContent: snippet"></div>
</div>


Because the template is applied to every item in the ListView, the following styles, associated with the template, are applied to every item:



#content .info .marketNews .list .win-item {
    width: 252px;
    height: 252px;
    padding-right: 8px;
    padding-bottom: 8px;
    background-color:#418c0b;
    display: -ms-grid;
    -ms-grid-columns: 1fr;
    padding:2px;
    -ms-grid-rows: 1fr 50px;
}


The items within the ListView appear as squares because the width and height are the same, and the items are spaced using padding values. The items within the control are specified using the styles associated in the template, which apply the following CSS3 styles:



#content .info .marketNews .win-item .itemTitle
{
    font-family:'Segoe UI Semibold';
    font-size:20pt;
    line-height: 24pt;
    padding:10px;
    overflow:hidden;
    text-overflow: ellipsis;
    -ms-grid-row:1;
    -ms-grid-column:1;
}

#content .info .marketNews .win-item .itemDescription
{
    color:rgba(255, 255, 255, 0.7);
    margin:4px 4px 6px 4px;
    padding-left:6px;
    padding-right:6px;
    padding-top:1px;
    padding-top:2px;
    height:44px;
    overflow:hidden;
    text-overflow: ellipsis;
    -ms-grid-row:2;
    -ms-grid-column:1;
    background-color:#418c0b;
}


That summarizes the basic way that the application UI elements are styled and positioned. Let’s take a closer look at how the application UI is updated to reflect changes in the application view state.

Styling for different view states

Through CSS3 styling, regions, and media queries, the Stocks app will resize, reposition, show, or hide various elements that you see on the screen when the view state changes from landscape to portrait or the view changes from filled to snap view. In this section, we’ll cover what happens when the user switches between the following views:

  • Snapped
  • Fullscreen-portrait
  • Side-by-side to snapped or filled

Snapped view

The following screen shot shows the Stocks app running in snapped view alongside the desktop.

Stocks app running in snapped view

When the user switches to the snapped view, the allInOne region is enabled and the content region is disabled. The allInOne region has similar content to what is displayed in the content region except that it resizes and reflows the content to fit into the snapped resolution of 320 x 768 pixels. Let’s start by taking a closer look at HTML for the allInOne region.



<div id="allInOne">
    <div class="header">
        <div class="titleArea">
            <div class="title">Stocks</div>
            <img class="appglyph" src="/images/appglyph.png" alt="Stocks" />
        </div>
    </div>
    <div class="info">
    </div>
    <div id="allInOneItemTemplate" data-win-control="WinJS.Binding.Template">
        <div class="item">
            <div class="nameLabel" data-win-bind="innerText: stockName"></div>
            <div class="chart" data-win-bind="id: chartID"></div>
            <div class="changeLabel">Change</div>
            <div class="change" data-win-bind="innerText: changeArrow"></div>
            <div class="value changeValue" data-win-bind="innerText: change"></div>
            <div class="lastTradeLabel">Last trade</div>
            <div class="value lastTrade" data-win-bind="innerText: lastSale"></div>
            <div class="percentageLabel">% change</div>
            <div class="value percentage" data-win-bind="innerText: percent"></div>
        </div>
  </div>
</div>


In this HTML, content is put into a template and is rendered for all data passed in. We’ll go behind the scenes in a later section describing how the data is passed to the template and how the displayed content is updated. Next, let’s take a look at how the content is styled by taking a look at the CSS3 for the allInOne region. This CSS3 shows how the media query is formed for the snapped view:



@media screen and (-ms-view-state: snapped)


This means that when the application’s view state changes to snapped, the CSS3 will change to the enclosed CSS3. The following code shows the CSS3 for two example elements within the media query for the allInOne region:



    #allInOne .item .chart
    {
        width:290px;
        margin-left:-7px;
        height:330px;
        -ms-grid-column-span: 4;
        -ms-grid-row: 3;
    }

    #allInOne .item .changeLabel
    {
        font-size: 16pt;
        -ms-grid-column: 1;
        -ms-grid-row: 5;
    }


In the first example, the chart class for the item div (used in the item template) is positioned and spaced to look great in the snapped layout. In the second example, the label for the changeLabel class is set up to appear appropriately for the snapped view.

Fullscreen portait

This screen shot shows the app running in portrait mode:

Stocks app running in portrait mode

Note how the elements, most notably the news items, have been reflowed and resized to fit the layout. This is done entirely through CSS3 changes for a media query. The following code shows how the media query is formed for the fullscreen portrait view:



@media screen and (-ms-view-state: fullscreen-portrait)


Let’s take a look at how the marketNews div is reflowed to address the new resolution. First, the info div, which contains the marketNews div, is updated to have a new grid layout, like this:



#content .info
{
    -ms-grid-columns: 40px 1fr 40px;
    -ms-grid-rows: 1px 1fr 240px 2fr;
    overflow-y: hidden;
    overflow-x: hidden;
}


Next, the win-item object from the marketNews data template is changed from having a 252 x 252 pixel width and height to something more appropriate for the new layout. The margin for the ListView control is also updated to reflect better spacing for this view.



    #content .info .marketNews .list .win-item {
        width: 698px;
        height: 90px;
    }
    
    #content .info .marketNews .list
    {
        margin-top:-10px;
    }


Finally, the marketNews div is set to the new grid cell for the new grid layout.



    #content .info .marketNews
    {
        -ms-grid-row: 4;
        -ms-grid-column: 2;
    }


Various other elements are updated and styled in a similar manner to fit well into the template.

Side-by-side with snapped and filled views

When the app is running side-by-side with a snapped app, it looks like this:

Stocks app running side-by-side with a snapped app

When the app is in this state, the app resolution can change to indicate a state other than filled. This code shows the media query for capturing this state:



@media screen and (min-width: 800px) and (max-width: 1024px)
{
    #content .info
    {
        -ms-grid-columns: 40px 900px 80px 900px;
    }
    
    #content .header
    {
        -ms-grid-columns:40px 1fr;
    }
}


In this view, the grid columns for the info block in the content region are set to their default values and the header is set to its default value. The following media query shows how the filled state is captured and handled:



@media screen and (-ms-view-state: filled)
{
    #content .info
    {
        -ms-grid-columns: 40px 900px 80px 900px;
    }
    
    #content .header
    {
        -ms-grid-columns:40px 1fr;
    }
}


This will render and flow the content in the same way as when the app is rendered next to snapped view. Now that we’ve gone over the basic application layout, let’s take a look at the code behind the UI.

App logic and code

At a high level, the app is broken down into 9 JavaScript files:

  • api.js, used to abstract the data collection from the UI logic
  • chart.js, used to configure the charts
  • dashboard.js, used to manage the application logic and UI for the dashboard view
  • default.js, loaded when the application starts to handle app initialization
  • helper.js, used to help with various application and UI tasks on the dashboard view
  • mock.js, used to mock up data as a placeholder for live data
  • news.js, used to parse and display data for the news section of the dashboard
  • pinning.js, used to pin specific stocks to the start screen
  • tile.js, used to create and send tile updates

In this section, we'll first cover, at a high level, how the application renders its content and handles user input. Later, we'll take a look at how various Windows features light up behind the scenes. Let’s start with how the application operates overall and how data is connected to the UI.

Application startup and initialization

As mentioned in Laying out the app in HTML, default.js is the first piece of code executed when the application starts. The code that runs inside default.js initializes the application. The following code highlights a few things that we’ll talk about now. Some of the code has been removed and replaced with ellipses.



(function () {
    "use strict";

    var app = WinJS.Application;

    app.onactivated = function (eventObject) {
        var argument = null;
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
          (…)
          eventObject.setPromise(WinJS.UI.processAll().then(function () {
                // Using the WinJS navigation pattern (even though this sample only has one view) to make
                // it easier to expand the app's functionality (e.g. add search view, compare stocks view, etc).
                WinJS.Navigation.navigate("/html/dashboard.html", argument).then(function () {
          (…)
                });
            }));
        }
    };


The first thing that happens is that a local variable, app, is created from the global WinJS.Application namespace. Next, the onactivated event is set to a new function that takes the event object. This is done so that when the application is activated, the various things that happen when the app starts are done. In the case of the Stocks app, the hierarchical navigation pattern is used to display the dashboard page. This next piece of code implements the navigation handler so that when navigation events occur, the application will move to the correct page.



    WinJS.Navigation.addEventListener("navigated", function (eventObject) {
        var url = eventObject.detail.location;
        WinJS.Utilities.empty(contentHost);
        WinJS.UI.Pages.render(url, contentHost, eventObject.detail.state);
    });


After the user is sent to the dashboard.html page, the application initializes various local variables and then initializes a special variable, page, that is created using the WinJS.UI.Pages.define method. This object, which represents the page, then will have its ready function set to run the braced code after the page is loaded. This code shows where this is done.



    var page = WinJS.UI.Pages.define("/html/dashboard.html", {
            ready: function (element, options) {
                selectedStock = options;
                Helper.animateTitle();

                // Configure the chart range options (week/month/year) based on user's settings
                initializeChartRangeOption();

                // Initialize the list of user stocks
                initializeUserStocks();

                // Configure the appbar with this page's settings
                configureAppBar();

                // Initialize the Market News Listview
                News.initialize();

                // Display all the stock information for the current stock
                showStockInfo(selectedStock, range);
                                
                window.addEventListener("resize", onLayoutChanged);

                // Set the chart's canvas size given the portrait/landscape state.
                setChartCanvasSize();
            }
    });


As described in the code comments, this code initializes various components of the application relevant to the dashboard. At the end of the code, you can see where the function handlers are added to support changes in how the application is rendered.

Rendering the default view of the app

A handler for when the window is changed to portrait view is added, as well as a handler for when the layout changes. Let’s take a look at the onLayoutChanged method from helper.js to see how the application responds to reflows for snapped view and filled view.


      
    function onLayoutChanged() {
        var layoutState = Windows.UI.ViewManagement.ApplicationViewState;
        var appLayout = Windows.UI.ViewManagement.ApplicationView;
        var isSnapped = appLayout.value === layoutState.snapped;
        if (isSnapped) {
            Helper.displayAllInOneInfo();
        } else {
            showStockInfo(selectedStock, range, true);
        }
    }


The displayAllInOneInfo function from the Helper class is invoked when the application is in a snapped state and the showStockInfo function is called when the app is running beside another app in snapped state. We’ll take a look at the showStockInfo function because this is what you'll see when you run the app for the first time. First, the application will style the selected stock class. This code shows how that is done:


        
    function showStockInfo(stock, chartRange, keepNews, keepStockInfo) {
        selectedStock = stock;
        roamingSettings.values["selectedStock"] = selectedStock;

        var i=0, len=0;
        var targetElementsToAddClass = WinJS.Utilities.query("#content .info .myStocks li");
        for (i=0, len=targetElementsToAddClass.length; i < len; i++) {
            WinJS.Utilities.removeClass(targetElementsToAddClass[i], selectedItemCssClass);
        }

        var targetElementsToDelClass = WinJS.Utilities.query("#content .info .myStocks li[data-stock='" + stock + "']");
        for (i = 0, len = targetElementsToDelClass.length; i < len; i++) {
            WinJS.Utilities.addClass(targetElementsToDelClass[i], selectedItemCssClass);
        }


Next, the news is updated by using the news library. This code shows how it is done:


 
if (!keepNews) {
    News.requestNews(stock);
}


Next, the various stats for the currently selected stock are retrieved. This is done by taking the example XML data and then using the mock data class defined in mock.js to randomize the data to simulate real-time changes.


 
        if (!keepStockInfo) {
            Api.getStockInfoData(stock).then(function (data) {


The getStockInfoData function of the API class parses XML that is syntactically similar to what would be returned by a web service. The following function shows how the API class is currently hard-coded to use local XML data:


 
    function getStockInfoData(stock) {
        var url = "/data/stock-info.xml";
        return WinJS.xhr({ url: url }).then(function (response) {
            return parseInfoData(response);
        });
    }


The “then” promise for the WinJS.xhr (XML http request) calls parseInfoData when the request returns. Let’s take a quick look at some of the test XML data that is stored in stock.xml.



<?xml version="1.0" encoding="utf-8"?>
<root>
  <result>
    <DynamicSymbology>
      <Symbol>{stock}</Symbol>
(…)
    </DynamicSymbology>
    <Dynamic>
(…)
      <TimeOfLastSale>1/18/2012 6:30:52 PM</TimeOfLastSale>
      <Yield></Yield>
      <IsNewsAvailable></IsNewsAvailable>
    </Dynamic>
  </result>
</root>


When the promise is returned from WinJS.xhr, the returned object can have its XML queried using the querySelector function. This returns an array of all nodes that match the string that is passed in, such as the “DynamicSymbology” nodes that are used in the following example.



    function parseInfoData(response) {
        var result = [],
            xmlDoc = response.responseXML;
        var allCompanyInfoNodes = xmlDoc.querySelectorAll("DynamicSymbology");
        var allStockDataNodes = xmlDoc.querySelectorAll("Dynamic");

        var result = new Array();
        for (var i = 0, len = allStockDataNodes.length; i < len; i++) {
            var stockDataNodes = allStockDataNodes[i];
            var companyInfoNodes = allCompanyInfoNodes[i];

            var marketcapOrig = formatNumber(stockDataNodes.querySelector("MarketCap").textContent),
                openValue = formatNumber(stockDataNodes.querySelector("Open").textContent);
            var openStr = parseFloat(openValue),
                lastSale = parseFloat(formatNumber(stockDataNodes.querySelector("Last").textContent)),
                low = parseFloat(stockDataNodes.querySelector("Low").textContent),
                high = parseFloat(stockDataNodes.querySelector("High").textContent),
                volume = Helper.formatDecimal((parseInt(formatNumber(stockDataNodes.querySelector("Volume").textContent)) / 1000000), 2),
                closeValue = parseFloat(parseFloat(stockDataNodes.querySelector("Close").textContent)),
                yearLow = parseFloat(stockDataNodes.querySelector("Low52Week").textContent),
                yearHigh = parseFloat(stockDataNodes.querySelector("High52Week").textContent);
            volume = volume > 0 ? volume : "N/A";

            result[i] =
            {
                name: companyInfoNodes.querySelector("LocalCompanyName").textContent,
                open: openValue === 0 ? "N/A" : openValue,
                close: closeValue,
                marketcap: parseInt(marketcapOrig.substr(1, marketcapOrig.length)),
                fiftyTwoWeekRange: yearLow + " - " + yearHigh,
                lastSale: lastSale,
                lastSaleTime: Helper.getFriendlyTime(stockDataNodes.querySelector("TimeOfLastSale").textContent),
                change: closeValue === 0 ? 0 : Helper.formatDecimal(parseFloat(lastSale - closeValue), 2),
                percent: openValue === 0 ? "N/A" : ((Math.abs(lastSale - openValue) * 100) / openValue),
                volume: volume,
                low: low,
                high: high,
                daysRange: openValue === 0 ? "N/A" : (low + " - " + high),
                symbol: companyInfoNodes.querySelector("Symbol").textContent.toUpperCase()
            };
        }

        return result;
    }


In this particular function, the specific nodes that contain the values that are useful for the stocks app are placed in a structure that is defined using JavaScript Object Notation (JSON). That object is then returned, and all of the relevant values for the stock can be queried. If you go back to the showStockInfo function in the API class, you can see how the object that is returned from the getStockInfo function is used. The following code shows what happens:



var item = data[0];
if (item) {
    item = Mock.randomizeItem(item);
    currentStockInfo = item;
    Helper.displayStockInfo(item);


In this code, only the first item that was retrieved is selected. The dummy data from the XML file is randomized using the Mock class. Finally, the stock is selected as current, and the Helper class for displaying the stock information is called. The following code shows how the Helper class renders the current stock to the screen.



    function displayStockInfo(item) {
        WinJS.UI.process(stockInfoTemplate).then(function (templateControl) {
            // Remove the old stock content
            var infoContainer = WinJS.Utilities.query("#content .details .grid");            
            WinJS.Utilities.empty(infoContainer[0]);

            // Add the current stock content
            item.stockName = "";
            item.chartID = "";
            item.changeArrow = (item.change >= 0 ? "\u25B2" : "\u25BC");
            templateControl.render(item, infoContainer[0]);
        });
    }


The WinJS.UI.process method is used to retrieve the stockInfoTemplate that was specified in the HTML. The grid that is used in the template is selected from the content region so that its values can be set. Next, various fields are cleared in the item object, and the changeArrow value is set to a Unicode character for the down or up arrow. Finally, the templateControl renders the item into the grid by binding the info object data.

Now that the stock information is displayed, the stock can be graphed. This code shows how it is done:


        
    Api.getHistoryData(chartRange).then(function (data) {
        if (data && data.stockValues && data.stockValues.length > 0) {                
            data.stockValues = Mock.randomizeChart(data.stockValues);
            chartData = new Array();
            chartData[0] = data.stockValues;
            CanvasChart.drawChart(document.getElementById("chartCanvas"), chartData, Chart.getChartOptions(false));
        }
    });


Now the chart has been updated and the stock is rendered as shown here.

Default page with updated data

Now that we’ve seen how the stock is updated and how the app works at a high level, let’s take a look at the Windows features implemented to ensure a great user experience (UX).

Programming for Windows 8 features

In this section, we’ll take a close look at how the Stocks app targets Windows 8 features. The following areas are key concepts demonstrated in this app:

  • app bar and Flyout
  • Application animations
  • Roaming settings
  • Tiles and notifications

App bar and Flyout

The following screen shot shows the app bar with the add-stock Flyout enabled.

Default page with app bar and flyout for adding a stock

The following code shows how the app bar and its Flyout are added in default.html.



    <div id="appBar" data-win-control="WinJS.UI.AppBar">
        <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdAdd',label:'Add',icon:'add',section:'global',tooltip:'Add', type:'flyout', flyout:'addStockFlyout'}"></button>
        <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdDelete',label:'Remove',icon:'remove',section:'global',tooltip:'Delete', onclick: Dashboard.deleteStock}"></button>
        <hr data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdSeparator',type:'separator',section:'global'}" />
        <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdPin',label:'Pin Stock',icon:'pin',section:'global',tooltip:'Pin Stock', onclick: Pinning.pin}"></button>
        <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmdUnpin',label:'Unpin Stock',icon:'unpin',section:'global',tooltip:'Unpin Stock', onclick: Pinning.unpin}"></button>
    </div>
<div id="addStockFlyout" data-win-control="WinJS.UI.Flyout">
        <div id="addStockFlyoutMessage"></div>
        <input id="addStockText" type="text" maxlength="8" placeholder="Enter a symbol (e.g, MSFT)" /><br /><br />
        <button id="addStockButton">Add</button><br /><br />
    </div>


The app bar can be configured differently, depending on the state of the app. The app bar is set up in the dashboard when the page is loaded. The following code shows how the code in dashboard.js configures the app bar for the dashboard page:


      
function configureAppBar() {
    var appBar = document.getElementById("appBar").winControl;
    appBar.addEventListener("beforeshow", function () {
        var myStocks = Helper.getFavoriteStocks(),
            layoutState = Windows.UI.ViewManagement.ApplicationViewState,
            appLayout = Windows.UI.ViewManagement.ApplicationView,
            isSnapped = appLayout.value === layoutState.snapped;
        if (isSnapped) {
             appBar.hideCommands([cmdPin, cmdUnpin, cmdSeparator, cmdDelete]);
        } else {
            appBar.showCommands([cmdPin, cmdUnpin, cmdSeparator, cmdDelete, cmdAdd]);

            // Configure the pin/unpin buttons visibility
            if (Pinning.isPinned(selectedStock)) {
                appBar.hideCommands([cmdPin]);
                appBar.showCommands([cmdUnpin]);
            } else {
                appBar.hideCommands([cmdUnpin]);
                appBar.showCommands([cmdPin]);
            }

            // Clear the add stock field
            addStockText.value = "";
        }

        // Configure the add/delete appbar buttons based on the favorites limit
        if (myStocks.length === 1) {
            cmdDelete.disabled = true;
            cmdAdd.disabled = false;
        } else if (myStocks.length >= 5) {
            cmdDelete.disabled = false;
            cmdAdd.disabled = true;
        } else {
            cmdDelete.disabled = false;
            cmdAdd.disabled = false;
        }
    }, false);

    var addStockButton = document.getElementById("addStockButton").addEventListener("click", function () {
        var newStock = addStockText.value;
        newStock = escape(newStock.substr(0, 8).toLocaleUpperCase());
        if (Helper.isStockInFavorites(newStock)) {
            showStockInfo(newStock, range);
        } else {
            Helper.addStock(newStock);
            initializeUserStocks();
            showStockInfo(newStock, range);
        }
        Helper.hideAppBar();
        var layoutState = Windows.UI.ViewManagement.ApplicationViewState;
        var appLayout = Windows.UI.ViewManagement.ApplicationView;
        var isSnapped = appLayout.value === layoutState.snapped;
        if (isSnapped) {
            Helper.displayAllInOneInfo();
        }
        Tile.sendUpdatesToMainTile();
    });
}


The app’s green app bar and Flyout are styled using the following CSS3:



.win-flyout
{
    border-color: #316a09;
    background-color: #367709;
    height: 100%;
}

#appBar
{
    background-color:#367709;
}

#appBar hr {
    background-color:#808080;
}

#addStockFlyout
{
    height:120px;
    width:260px!important;
    max-width:280px;
    overflow:hidden;
}

#addStockText
{
    width:218px;
    background-color:rgba(255,255,255, 1)
}

#deleteLimitFlyout
{
    height:20px;
    color:rgba(255, 255, 255, 1.0);
}

#addStockFlyoutMessage
{
    color:rgba(255, 255, 255, 1.0);
    padding:5px 0;
    height:15px;
    width:218px;
    text-overflow:ellipsis;
}


To learn more about the app bar, read App bars.

To learn more about Flyouts, read Adding Flyouts and menus.

Application animations

Application animations make the app have a consistent feel with the overall Windows 8 experience. You may have noticed when the application starts, the title slides in. This uses the animateTitle function from helper.js and is called in the ready function for the dashboard page. This code shows the call to animateTitle from dashboard.js:


      
	var page = WinJS.UI.Pages.define("/html/dashboard.html", {
	        ready: function (element, options) {
	            selectedStock = options;
	            Helper.animateTitle();


And this code shows the animation call in helper.js:


        
    function animateTitle() {
        var titleArea = WinJS.Utilities.query('#content .header .titleArea')[0];

        titleArea.style.display = 'block';
        WinJS.UI.Animation.enterPage([titleArea], { top: '0px', left: '500px' });        
    }


The code simply retrieves the title area, stores it in a variable, shows it, and then passes the variable to the enterPage function with an offset to animate from. As a result, the application has the feel of coming alive when it starts.

In addition to the animation used at startup, some of the controls provide animations as the data changes. For example, the ListView control, used to show the news when stocks change, provides animations as items are added and removed from it. The app bar also provides various animations when it’s shown or hidden and when the Flyout is enabled or disabled.

To learn more about animations, read Animating your UI.

Roaming settings

Settings in the app roam from device to device to make the application experience seamless—starting the app on a new device will make the app behave as the user has configured it. For example, the stocks that you have set up on one device will roam to other devices. This is done using the roaming settings feature that ships with the Windows API. In dashboard.js, a variable is set up for accessing roaming settings as shown here:




var roamingSettings = Windows.Storage.ApplicationData.current.roamingSettings;



Now, the settings can be set or retrieved where they should be. The first place that you can see roaming settings being used is in the showStockInfo function on the dashboard. The following code shows where the roaming settings are retrieved.




function showStockInfo(stock, chartRange, keepNews, keepStockInfo) {
        selectedStock = stock;
        roamingSettings.values["selectedStock"] = selectedStock;



By saving the selected stock into roaming settings, the stock that was last selected will open regardless of which device the user is on. If you look at the initializeUserStocks function, you can see the settings pulled from the roaming store:




function initializeUserStocks() {
  var myStocks = Helper.getFavoriteStocks();

  if (!selectedStock) {
      selectedStock = roamingSettings.values["selectedStock"] || Mock.defaultSelectedStock;
  }



The stock range is also saved to roaming settings, as shown here:




function initializeChartRangeOption() {
    // Get the current value for the chart range
    range = roamingSettings.values["range"] || "1m";
    // Check the radio button for the current range to roam settings
    var targets = WinJS.Utilities.query("input[value='" + range + "']");
    targets[0].checked = true;        
    targets = WinJS.Utilities.query("#content .info .range input[type='radio']");
    for (var i = 0, len = targets.length; i < len; i++) {
        targets[i].onclick = (function (eventObject) {
            range = eventObject.target.value;
            roamingSettings.values["range"] = range;
            showStockInfo(selectedStock, range, true, true);
        });            
    }
} 



The addStock and deleteStock functions also reflect changes in roaming settings:




function addStock(stock) {
    var myStocks = getFavoriteStocks();
    myStocks.push(stock);
    Windows.Storage.ApplicationData.current.roamingSettings.values["mystocks"] = myStocks.join(",");
}

function deleteStock(stock) {
    var myStocks = getFavoriteStocks(),
        myNewStocks = [];
    myStocks.forEach(function (value) {
        if (value !== stock) {
            myNewStocks.push(value);
        }
    });
    Windows.Storage.ApplicationData.current.roamingSettings.values["mystocks"] = myNewStocks.join(",");
}



Note  The stock data is serialized using the JavaScript string join function.

To learn more about roaming settings, read about the Settings contract.

Tiles and notifications

Notifications enable you to send updates to the user when they are not in your app. The least intrusive way to send notifications is through Live tiles. In the Stock sample, notifications are sent to the app tile to show updates on the stock prices. All of the notifications code for tiles is encapsulated in the file tile.js. To trigger notifications, the app must retrieve the notifications and then enable a notification queue for the tiles that are to be updated. This code shows how the app sets up and enables the notification queue:



  var Notifications = Windows.UI.Notifications;

  function initialize () {
    var tileUpdateManager = Notifications.TileUpdateManager,
    tileUpdater = tileUpdateManager.createTileUpdaterForApplication();
    tileUpdater.enableNotificationQueue(true);
  }


The next step is to set up the actual notifications to be sent to the tiles. For this step, the app first retrieves the tile data using the API class. This code shows how to do that:



    function fetchDataAndSendTileUpdate(stockName, isSecondary) {
        Api.getStockInfoData(stockName).then(function (data) {
            var item = data[0];
            if (item) {
                item = Mock.randomizeItem(item);
                Tile.sendTileUpdate(stockName, item.change, item.lastSale, item.lastSaleTime, item.open, isSecondary);
            }
        });
    }   


After the data is retrieved using the API class, the data is passed to a function that updates the actual tile. First, strings are created that will store lines of text to be passed to the tile template.



    function sendTileUpdate(symbol, change, lastTrade, lastTradeTime, openValue, isSecondaryTile) {
        var changeDisplay = parseFloat(change) === 0 ? "0.00" : Helper.formatDecimal(parseFloat(change), 2),
            lines = [
                symbol.toUpperCase() + " " + (parseFloat(change) >= 0 ? "\u25B2 +" : "\u25BC ") + changeDisplay,
                "Last Trade " + Helper.formatDecimal(parseFloat(lastTrade), 2),
                "Last Trade Time " + lastTradeTime,
                "Open " + openValue,
                ""
            ],
            tileXml = Notifications.TileUpdateManager.getTemplateContent(Notifications.TileTemplateType.tileWideText01),
            tileTextAttributes = tileXml.getElementsByTagName("text");

        for (var i = 0; i < 5; i++) {
            tileTextAttributes[i].appendChild(tileXml.createTextNode(lines[i]));
        }

        var binding = tileXml.getElementsByTagName("binding");
        if (binding[0]) {
            binding[0].setAttribute("branding", isSecondaryTile ? "name" : "logo");
        }


Next, a tile template, either tileSquareText01 or tileWideText01, is retrieved. This template is then formatted with the text content that was configured as lines representing the stock information. The content of the tile template is set up using document object model (DOM) methods. This code shows how the template is retrieved and the content is set in the tile:



        var squareTileXml = Notifications.TileUpdateManager.getTemplateContent(Notifications.TileTemplateType.tileSquareText01),
            squareLines = [
                symbol.toUpperCase(),
                (parseFloat(change) >= 0 ? "\u25B2 +" : "\u25BC ") + changeDisplay,
                lastTradeTime,
                openValue
            ],
            squareTileTextAttributes = squareTileXml.getElementsByTagName("text");

        for (i = 0; i < 4; i++) {
            squareTileTextAttributes[i].appendChild(squareTileXml.createTextNode(squareLines[i]));
        }

        var squareTileBinding = squareTileXml.getElementsByTagName("binding");
        if (squareTileBinding[0]) {
            squareTileBinding[0].setAttribute("branding", "none");
        }        


Finally, the tile nodes are selected and the notifications are passed to the tiles, as shown here:



        var node = tileXml.importNode(squareTileXml.getElementsByTagName("binding").item(0), true);
        tileXml.getElementsByTagName("visual").item(0).appendChild(node);

        var tileNotification = new Notifications.TileNotification(tileXml);
        tileNotification.tag = symbol.replace(/[^a-zA-Z0-9]+/g, "Z");

        if (isSecondaryTile) {
            Notifications.TileUpdateManager.createTileUpdaterForApplication(symbol.toLowerCase()).update(tileNotification);
        } else {
            Notifications.TileUpdateManager.createTileUpdaterForApplication().update(tileNotification);
        }
    }



For convenience, the following functions are used to manage updates to all of the tiles:



    function sentUpdatesToAllTiles() {
        sendUpdatesToMainTile();
        sendUpdatesToSecondaryTiles();
    }

    function sendUpdatesToMainTile() {
        Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication().clear();

        var myStocks = Helper.getFavoriteStocks();
        myStocks.forEach(function (value) {
            Tile.fetchDataAndSendTileUpdate(value);
        });
    }

    function sendUpdatesToSecondaryTiles() {
        // Get secondary tile ids for all applications in the package and list them out:
        Windows.UI.StartScreen.SecondaryTile.findAllAsync().then(function (tiles) {
            if (tiles) {
                tiles.forEach(function (tile) {
                    var stockName = tile.tileId;
                    Tile.fetchDataAndSendTileUpdate(stockName, true);
                });
            }
        });
    }    


The tile notifications are initialized in default.js when the app is initialized as shown here:



            eventObject.setPromise(WinJS.UI.processAll().then(function () {
                // Using the WinJS navigation pattern (even though this sample only has one view) to make
                // it easier to expand the app's functionality (e.g. add search view, compare stocks view, etc).
                return WinJS.Navigation.navigate("/html/dashboard.html", argument).then(function () {
                    // Retrieve and send tile updates after the navigation completes to get the app UX up
                    // as quickly as possible.
                    Tile.initialize();
                    Tile.sentUpdatesToAllTiles();
                });
            }));


To learn more about tile notifications, read Quickstart: Sending a tile update and How to use the notification queue with local notifications.

Extending the stock app sample

Now that you've learned how the app works, let's talk about how it can be changed to suit your needs. This section suggests a few enhancements for the app and provides some links to help you get going. The following topics are covered in this section:

  • Connecting data to live sources
  • Applying aspects of this sample to other apps

Connecting data to live sources

Data in the sample is set to a fixed XML data source and is randomized using the Mock class. To connect the app to a live data source, first remove the hooks within the app that randomize the data sources. The following code shows changes to showStockInfo that will remove the data randomization code.



  if (!keepStockInfo) {
      Api.getStockInfoData(stock).then(function (data) {
          var item = data[0];
          if (item) {
              item = Mock.randomizeItem(item);
              currentStockInfo = item;
              Helper.displayStockInfo(item);
              Tile.sendTileUpdate(stock, item.change, item.lastSale, item.lastSaleTime, item.open);
          }
      });
  }
  
        
  Api.getHistoryData(chartRange).then(function (data) {
      if (data && data.stockValues && data.stockValues.length > 0) {                
          //data.stockValues = Mock.randomizeChart(data.stockValues);
          chartData = new Array();
          chartData[0] = data.stockValues;
          CanvasChart.drawChart(document.getElementById("chartCanvas"), chartData, Chart.getChartOptions(false));
      }
  });


Now, when you navigate through the app and select stocks, the chart and data will be the same because the data isn’t being randomized by the Mock class. Next, you could replace the URL targets for stock information retrieval with a live source. The same could be done with the news data. We’ll briefly discuss how you can connect the news feed to the Bing news API.

First, you must get an API key and an AppID. You can sign up from the Bing developer site. From the Bing developer site, click Sign up to use Bing Search API and after you sign up, create an AppID. After filling out the form, the Bing developer dashboard page will have the information that you need to perform queries to the Bing API. Because we’ll be using the News SourceType, you must use the following format to run your queries:



http://api.bing.net/xml.aspx?AppId=Insert your AppId here&Query=msn%20moneycentral&Sources=News&Version=2.0&Market=en-us&Options=EnableHighlighting&News.Offset=0&News.SortBy=Relevance    


The query that we’ll be running will be against the currently selected stock, so the following changes to the getNewsData function would enable results from Bing:



function getNewsData(stock) {
        //var url = "/data/stock-news.xml";
        var bingAppID = "yourappid";

        var url = "http://api.bing.net/xml.aspx?AppId="+ bingAppID + "&Query=" + stock + "&Sources=News&Version=2.0&Market=en-us&Options=EnableHighlighting&News.Offset=0&News.SortBy=Relevance"

        return WinJS.xhr({ url: url });
}


Now, live news will be pulled down from Bing when the stock changes.

Applying aspects of this sample to other apps

This sample demonstrates various ways of incorporating news and other data into a compelling experience. If you wanted to create a news reader, you could incorporate a layout similar to what is done in the stock news section. Naturally, you would want to invest more time in expanding the functionality for displaying news articles in the list view control, and you'd also want to incorporate Semantic Zoom for showcasing the the new Windows UI in your app.

If you wanted to create a new financial app based on this one, you would first want to consider the things that make your app unique. If your app is too similar to other apps in the store, you will not be successful. The following suggestions are ways that you could differentiate your app but are intended only as suggestions—the things that would make your app successful could be completely different, and fulfilling these suggestions in no way guarantees a successful app submission or promotion.

  • Add support for more stocks. Currently, the app is limited to supporting only five stocks at a time, in order to prevent scrolling within the stock selector. By using a Flyout, you could enable support for monitoring more stocks.
  • Replace the chart control. The chart control that ships with the sample is primarily included in the sample as decoration. You could replace this control with a number of charting solutions built on HTML and JavaScript.
  • Add more intensive analysis for the stock values. Your app could compare similar stocks and then educate the user on why one stock could be a more compelling buy than another. If your company excels at performing stock analysis, this could be a great way to make your app stand out.
  • Incorporate your own data sources. If you provide financial data to your users, creating an app for letting users quickly access their data would be a great way to differentiate your product from your competitors.
  • Incorporate additional Windows design features such as Semantic Zoom and Sharing contracts so that your application is even more deeply integrated with Windows.
  • Turn it into a learning experience. You could show users information about interesting points in the stock market history and could incorporate a reading experience that explains what was going on economically when the market was in this state.

Related topics

Stocks example app
Developing reader apps
UX guidelines for Windows Store apps
Roadmap for Windows Store apps using JavaScript

 

 

Build date: 10/15/2012

Show:
© 2014 Microsoft