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

The Windows 8 Release Preview (RP) is now out and you can download it here:
https://windows.microsoft.com/en-US/windows-8/release-preview

It is then obvious that I must port my little UrzaGatherer for the Release Preview.

I also take this opportunity to talk about some upgrades I added like the data binding support.

The complete version can be found here:
https://www.catuhe.com/MSDN/urza/day5.zip

The complete series can be found here:

 

Porting to Release Preview

To help you porting your own app, we (at Microsoft) released a great document:
https://go.microsoft.com/fwlink/?LinkId=251943

Furthermore, I invite you to read the following blog where the Windows 8 developers team talk about the differences between CP and RP:
https://blogs.msdn.com/b/windowsappdev/archive/2012/05/31/what-s-changed-for-app-developers-since-the-consumer-preview.aspx

From my point of view here are the main concerns I encountered:

The new navigator.js

The first point is about the navigator.js file where I added some cool new things.

First of all I added animations during navigation process. I also added a new function in the pages called afterPageEnter. This function is called when the enterPage animation is played. This allows your code to be launched when everything is ready. But it also allows your animations to get all the power in order to keep them fluid even on small computers:

_navigated: function (args) {
    var that = this;
    var newElement = that._createPageElement();
    var parentedComplete;
    var parented = new WinJS.Promise(function (c) { parentedComplete = c; });

    args.detail.setPromise(
        WinJS.Promise.timeout().then(function () {
            if (that.pageElement.winControl && that.pageElement.winControl.unload) {
                that.pageElement.winControl.unload();
            }
            return WinJS.UI.Pages.render(args.detail.location, newElement, args.detail.state, parented);
        }).then(function parentElement(control) {
            that._previousPage = newElement.winControl;
            that.element.appendChild(newElement);
            that.element.removeChild(that.pageElement);

            parentedComplete();

            var offset = { top: "0px", left: "50px" };
            var enterPage = WinJS.UI.Animation.enterPage(newElement, offset);
            enterPage.then(function () {
                document.body.focus();
                that.navigated();

                setImmediate(function () {
                    if (that._previousPage.afterPageEnter) {
                        that._previousPage.afterPageEnter(newElement);
                    }
                });
            });
        })
    );
},

The complete new navigator.js file is available here:

https://www.catuhe.com/MSDN/Urza/Navigator.zip

The default page

The first page of your project must be something like that now:

(function () {
    "use strict";

    var app = WinJS.Application;
    var nav = WinJS.Navigation;
    var ui = WinJS.UI;

…
    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.search ||
            eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker) {

            eventObject.setPromise(WinJS.UI.processAll().then(function () {
                WinJS.Resources.processAll().then(function () {

if (nav.location) { nav.history.current.initialPlaceholder = true; return nav.navigate(nav.location, nav.state); } else { return nav.navigate(UrzaGatherer.navigator.home); } }); })); } }; app.oncheckpoint = function (args) { }; app.start(); })();

The application activation is now handled by a promise presented by the argument of the onactivated event. The navigation is also launched by your code with the nav.navigate function.

Multi-sized listView

Another important point is the new way used to define multi-sized listViews.

listView.layout= new ui.GridLayout({
        groupInfo: function () {
            return {
                enableCellSpanning: true,
                cellWidth: 28,
                cellHeight: 13
            };
        }
    });

This new naming was created in order to underline the fact that each item must have a proportional size relatively to a specified cell size (cellWidth / cellHeight).

Semantic Zoom bug workaround

The Release Preview is not the final version so some little bugs may be found.

One of them was really hard to fix so I decided to share the solution with you Sourire

On the main page, there is a semantic zoom. Under certain circumstances (linked to the power of the computer) the base listView just don’t display. Obviously this is not reproductible in debug mode (cool!) and not everytime (cool again !).

After fighting during a whole day, I finally find a workaround: After setting the datasources, you just have to launch again the layout process of the semantic zoom control:

var zoomedListView = element.querySelector(".zoomedList").winControl;

var groupDataSource = UrzaGatherer.ExpansionsListWithCardOfTheDay.createGrouped(
this.groupKeySelector, this.groupDataSelector, this.groupCompare); listView.itemDataSource = groupDataSource.dataSource; listView.groupDataSource = groupDataSource.groups.dataSource; zoomedListView.itemDataSource = groupDataSource.groups.dataSource; if (onlyOnce)
element.querySelector(".zoomControl").winControl.forceLayout();

 

Please note the usage of the onlyOnce variable to be sure that you apply the patch only the first time.

The magic of setImmediate

A last important point of this migration is the understanding of the setImmediate (or msSetImmediate) function:

https://msdn.microsoft.com/en-us/library/windows/apps/hh453394.aspx

This function is used to execute your code right after the current block and also right after any remaining operations (animations, UI,etc.).

This will be really useful to handle some specifics problems like for instance the “click-of-the-dead” when your users click too quickly the back button. Indeed, in this case, using a setImmediate allows you to go back to the previous page but after the completion of the page rendering (if you don’t do that, WinJS can try to manipulate some disposed UI elements).

To take this problem in account, I updated the navigator.js file in order to change the back button click handler:

if (backButton) {
    backButton.onclick = function () {
        setImmediate(function () {
            nav.back();
        });
    };

    if (nav.canGoBack) {
        backButton.removeAttribute("disabled");
    } else {
        backButton.setAttribute("disabled", "disabled");
    }
}

Another example can be found in the code used for the scrolling:

setImmediate(function () {
    if (currentIndex)
        listView.indexOfFirstVisible = currentIndex;
});

 

Adding binding with WinJS.Binding.as

I didn’t covered data binding during the previous episodes. It is indeed an important point and finally I used it broadly inside UrzaGatherer.

For instance, I wanted to add a random card in the home screen and I wanted to change the card every two minutes:

image_thumb9

To do so, I created a binding list with all expansions. I added to this list a specific object used to represent the random card.

To make this card bindable (I mean: every change applied to the card is automaticaly synchronized with the UI), it is really simple because you just have to to use a WinJS.Binding.as class:

 var bindableCard = new WinJS.Binding.as(card);
 expansion.cardsList.push(bindableCard);

Using this class, you create a completely bindable object.

<div class="cardOfTheDayTemplate" data-win-control="WinJS.Binding.Template">
    <div class="card-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader"
        data-win-options="{root: 'cards'}">
        <img class="card-image" data-win-bind="src: url" src="#" />
    </div>
    <div class="card-overlay">
    </div>
    <h4 class="card-title" data-win-bind="textContent: name"></h4>
    <div class="item-border">
    </div>
    <div class="item-check" 
data-win-bind="style.display: card.isChecked UrzaGatherer.Tools.BoolToDisplayConverter"> <div class="checkBackground"></div> <div class="checkmark"></div> </div> </div>

Everything is based on the data-win-bind. The templates used by the listviews are fully compatible with this object and when a property of the object change, the UI responds automatically (like in XAML!!):

var updateCardOfTheDay = function () {
    var card;

    do {
        var cardNumber = Math.floor(Math.random() * cards.length);
        card = cards[cardNumber];
    }
    while (card.size.isPlane);

    if (cardOfTheDayBinding.element) {
        WinJS.Utilities.query("img", cardOfTheDayBinding.element).forEach(function (img) {
            WinJS.Utilities.removeClass(img, "loaded");
        });
    }

    cardOfTheDayBinding.url = card.logo;
    cardOfTheDayBinding.name = card.name;
    cardOfTheDayBinding.card = card;
}

With this superb technology, I decided to update all my templates. So for instance, when the user chooses to use local or distant files, I just have to change the according properties of my objects:

var updateBlockOfflineState = function (block, offline) {
            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";
            }
    }

 

image_thumb6

That’s it!!

That’s it! You’re done. The application can be published on the store (already done for UrzaGatherer: https://apps.microsoft.com/webpdp/app/urzagatherer/92adce33-8490-4af7-9392-9c35c91d8a37).

Now, feel free to use my tutorials as a basis for your project. You’re almost ready now Clignement d'œil