How to cook a complete Windows 8 application with HTML5, CSS3 and JavaScript in a week – Day 2

Today, the chief proposes you to delight yourself with the following dishes:

  • Internationalization
  • Managing the activation of the offline mode
  • Adding a flipView to the cards page
  • Handling snapped views
  • Supporting different form factors
  • Supporting different cards sizes

image_thumb2

 

As usual the complete solution is there : https://www.catuhe.com/msdn/urza/day2.zip

The complete series can be found here:

    

Internationalization

UrzaGatherer is intended to be present on all marketplaces supported by the Windows 8 store. It is then important to internationalize it. To do so, you just have to add resources.resjon files in a folder named with the language you want to support (for instance, “en” for English or “fr” for French):

 

image_thumb8

image_thumb11

This file contains a json description of key/value pairs used by the localization system of WinJS (the core JavaScript of Windows 8 Metro):

{
    "Settings"              : "Settings",
    "CardsZoomLevel"        : "Cards zoom level",
    "OfflineFiles"          : "Offline files",
    "OfflineFilesRestart"   : "(You must restart to apply this settings)",
    "On"                    : "On",
    "Off"                   : "Off",
    "DownloadInProgress"    : " download(s) in progress",
    "LoadingData"           : "Loading data...please wait",
    "ErrorConnection"       : "Error during connection to server. Please check you connectivity.",
    "All"                   : "All ",
    "Colors"                : "colors",
    "Authors"               : "authors",
    "Rarities"              : "rarities",
    "Types"                 : "types",
    "Cards"                 : " cards",
    "Number"                : "Number:",

    "Type"                  : "Type:",
    "Color"                 : "Color:",
    "Power"                 : "Power:",
    "Rarity"                : "Rarity:",    
    "Text"                  : "Text:",
    "Flavor"                : "Flavor:",
    "Author"                : "Author:",
    "AveragePrice"          : "Average price:",
    "ByNumber"              : "By number",
    "ByName"                : "By name",
    "OnlyMissing"           : "Only missing",
    "AllExceptMissing"      : "All except missing"
}

You can have this kind of file for each language you want to support. WinJS will then choose for you the right file according to the active user language.

To use the resource file in your HTML page, you have to add a data-win-res attribute:

<span data-win-res="{innerText: 'OfflineFilesRestart'}"></span>

Calling the following function will process the added attribute and will affect the resource string to the specified property:

WinJS.Resources.processAll();

You just have to not forget to call it. For UrzaGatherer I call it in the ready function of my pages:

 ui.Pages.define("/pages/card/card.html", {
     ready: function (element, options) {
         WinJS.Resources.processAll();

Furthermore, you can also call the resource system from your own code to get the value associated to a given key:

var resourceLoader = new Windows.ApplicationModel.Resources.ResourceLoader();
var getString = function (name) {
    if (!name)
        return "";

    return resourceLoader.getString(name);
};

 

Managing the activation of the offline mode

Some users may not want to use the offline files mode (to prevent UrzaGatherer to use too much disk space for instance). To handle that, you can add a new property in the settings:

image_thumb5

To do so, you just have to add the following HTML code in the default.html page:

<div id="settingsDiv" data-win-control="WinJS.UI.SettingsFlyout" data-win-options="{width:'narrow'}">
    <div class="win-header">
        <button type="button" onclick="WinJS.UI.SettingsFlyout.show()" class="win-backbutton">
        </button>
        <div class="win-label" data-win-res="{innerText: 'Settings'}"></div>
    </div>
    <div class="win-content">
        <h4 data-win-res="{innerText: 'CardsZoomLevel'}"></h4>
        <input type="range" id="zoomRange" min="20" max="80" value="50" />
        <fieldset class="controlGroup">
            <legend class="controlGroupName"><span data-win-res="{innerText: 'OfflineFiles'}"></span>
                <br />
                <i><span data-win-res="{innerText: 'OfflineFilesRestart'}"></span></i>
            </legend>
            <label class="radioLabel horizontalLabelLayout">
                <input type="radio" name="offlineMode" checked id="offlineModeOn"/>
                <span data-win-res="{innerText: 'On'}"></span>
            </label>
            <label class="radioLabel horizontalLabelLayout">
                <input type="radio" name="offlineMode" id="offlineModeOff"/>
                <span data-win-res="{innerText: 'Off'}"></span>
            </label>
        </fieldset>
    </div>
</div>

Each radio controls the state of the offlineMode roaming setting:

// Offline mode
var on = document.getElementById("offlineModeOn");
var off = document.getElementById("offlineModeOff");
var offlineMode = Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"];

if (offlineMode)
    on.checked = true;
else
    off.checked = true;

on.addEventListener("change", function () {
    Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"] = true;
});

off.addEventListener("change", function () {
    Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"] = false;
});

The image loading code is then modified to switch between a local mode (ms-appdata) et un mode distant (http):

 var offlineMode = Windows.Storage.ApplicationData.current.roamingSettings.values["offlineMode"];
 if (offlineMode) {
     block.logo = "ms-appdata:///local/blocks/" + block.name.replace(":", "_") + ".png";
     block.banner = "ms-appdata:///local/blocks/" + block.name.replace(":", "_") + "_banner.png";
 }
 else {
     block.logo = root + "/blocks/" + block.name.replace(":", "_") + ".png";
     block.banner = root + "/blocks/" + block.name.replace(":", "_") + "_banner.png";
 }

And voila! Sourire.

Adding a flipView to the cards page

The next stop is for the card page which can be easily improved by adding a flipView control to switch between cards without having to go back to the expansion page.

The flipView is a touch aware control which allows you to slide between items but it also allows you to use your mouse with the navigation buttons on the left and right edges of the control:

image_thumb14

By the way, the data have to be bound now and not directly set by code:

<body>
    <!--Template-->
    <div class="itemTemplate" data-win-control="WinJS.Binding.Template">
        <div class="item-root">
            <header aria-label="Header content" role="banner">
                <button class="win-backbutton" aria-label="Back"></button>
                <h1 class="titlearea win-type-ellipsis"><span class="pagetitle" 
                    data-win-bind="innerText: name">
                </span></h1>
            </header>
            <section aria-label="Main content" role="main">
                <h3 class="item-number-label" data-win-res="{innerText: 'Number'}"></h3>
                <div class="item-number" data-win-bind="innerText: number"></div>
                <h3 class="item-type-label" data-win-res="{innerText: 'Type'}"></h3>
                <div class="item-type" data-win-bind="innerText: type"></div>
                <h3 class="item-color-label" data-win-res="{innerText: 'Color'}"></h3>
                <div class="item-color" data-win-bind="innerText: color"></div>
                <h3 class="item-power-label" data-win-res="{innerText: 'Power'}"></h3>
                <div class="item-power" data-win-bind="innerText: power"></div>
                <h3 class="item-rarity-label" data-win-res="{innerText: 'Rarity'}"></h3>
                <div class="item-rarity" data-win-bind="innerText: rarity"></div>
                <h3 class="item-text-label" data-win-res="{innerText: 'Text'}"></h3>
                <div class="item-text" data-win-bind="innerText: text"></div>
                <h3 class="item-flavor-label" data-win-res="{innerText: 'Flavor'}"></h3>
                <div class="item-flavor" data-win-bind="innerText: flavor"></div>
                <h3 class="item-author-label" data-win-res="{innerText: 'Author'}"></h3>
                <div class="item-author" data-win-bind="innerText: author"></div>
                <h3 class="item-price-label" data-win-res="{innerText: 'AveragePrice'}"></h3>
                <div class="item-price" data-win-bind="innerText: price UrzaGatherer.Tools.PriceConverter">
                </div>
                <div class="item-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader"
                    data-win-options="{root: 'cards'}">
                    <img class="item-image-container" data-win-bind="src: logo" />
                </div>
                <div class="expansion-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader">
                    <img class="expansion-picture" src="#" data-win-bind="src: expansion.banner" />
                </div>
            </section>
        </div>
    </div>
    <!--Content-->
    <div class="card fragment">
        <div id="flipView" data-win-control="WinJS.UI.FlipView" 
            data-win-options="{ itemTemplate : select('.itemTemplate') }">
        </div>
    </div>
</body>

You can notice the presence of data-win-res for the localization and the presence of data-win-bind for the binding.

To set up the connection between the flipView and the data, you can faily use the same code you used for the listView:

var flipView = document.getElementById("flipView").winControl;
ui.setOptions(flipView, {
    itemDataSource: options.cards.dataSource,
    currentPage: options.cardIndex
});

 

Handling snapped views

To be able to pass store certifications, your application must support the snapped view. This mode is activated when your application is set side by side with another application. The space allowed for a snapped application is 320px wide.

You have to define a new design for the snapped view for each screen of your application.

For UrzaGatherer, I decided to provide a complete experience when in snapped view. You can of course choose to remove non relevant features but for UrzaGatherer, all features are dispatched in the snapped view.

The main screen evolves like this:

image_thumb18

The expansion screen in snapped view is like this:

image_thumb21

And the card screen:

image_thumb24

 

The magic thing here is that all these modifications are done using only one feature : the media queries (https://msdn.microsoft.com/en-us/library/windows/apps/hh453556.aspx). This feature allows you to select new CSS rules based on specific queries (for instance, the application is in snapped mode).

Here is an example for the expansion page:

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

    #cardsCount {
        display: none;
    }

    #cardsPrice {
        display: none;
    }

    .expansion section[role=main] {
        -ms-grid-rows: 0px 1fr;
    }
}

 

These rules will replace (or complete) the original rules. For example, the base rules for “.expansion section[role=main]” is the following:

.expansion section[role=main] {
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 50px 1fr;
    display: -ms-grid;
}

In snapped mode, according to our media query it will become:

.expansion section[role=main] {
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 0px 1fr;
    display: -ms-grid;
}

Your job is then to use media queries to move your HTML elements from a large horizontal layout to a small vertical layout (thanks to CSS3 Grids!).

Supporting different form factors

Media queries can also be used to adapt your application to different form factors. For example, the card page can use a media query to adapt its display to small resolutions:

@media screen and (min-height: 1024px) {
    .card section[role=main] {
        -ms-grid-columns: auto 1fr 960px;
    }

    .card .item-image-container {
        margin-top: 0px;
    }
}

On a large resolution the display is like this:

image_thumb31

 

And on a smaller resolution:

image_thumb28

You can see that the columns are smaller and the picture is moved upwards to have enough room to display.

Supporting different cards sizes

The last point I want to talk today is about handling multi-templates for the listView. Indeed, some cards in my collection are twice wider than the standard ones (mainly in the Planechase expansion):

image_thumb34

To do so, instead of giving a static template, you can provide a JavaScript function that builds dynamically the template:

var listView = element.querySelector(".cardsList").winControl;

ui.setOptions(listView, {
    itemTemplate: this.itemRenderer,
    oniteminvoked: this.itemInvoked.bind(this)
});

The itemRenderer function is responsible for rendering each item. So you have the full control on how every element is produced! In this case you can check if the card is large or not and accordingly you can modify the styles:

itemRenderer: function (itemPromise) {
    return itemPromise.then(function (currentItem, recycled) {
        var template = document.querySelector(".itemTemplate").winControl.renderItem(itemPromise, recycled);
        template.renderComplete = template.renderComplete.then(function (elem) {

            if (currentItem.data.isLarge) {
                var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];

                if (!zoomLevel)
                    return;

                var level = zoomLevel / 100.0;
                elem.querySelector(".item-container").style.width = (480 * level * 2) + "px";
            }
        });
        return template.element;
    })
},

You can note that the code take the local zoom in account to compute the correct size.

However, it is not sufficient. Indeed, the listView must be in “multisize” mode to support different sizes items. To achieve this, it is necessary to add a “groupInfo” parameter:

ui.setOptions(listView, {
    itemDataSource: filtered.dataSource,
    layout: new ui.GridLayout({
        groupHeaderPosition: "top",
        groupInfo: function () {
            var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];

            if (!zoomLevel)
                zoomLevel = 50;

            var level = zoomLevel / 100.0;

            return {
                multiSize: true,
                slotWidth: Math.round(480 * level),
                slotHeight: Math.round(680 * level)
            };
        }
    })
});

It is clearly important to provide a correct cell size (slotWidth et slotHeight) because every item in the listView must have a size proportional to cell size **!**

To be continued

Our next stop will be focused on integrating with Windows 8:

  • Search contract 
  • Share contract
  • FileOpenPicker
  • Live tiles
  • Secondary tiles