Comment “cuisiner” une application Windows 8 avec HTML 5, CSS3 et JavaScript en une semaine–Jour 5 - Eternal Coding - HTML5 / Windows / Kinect / 3D development - Site Home - MSDN Blogs

Comment “cuisiner” une application Windows 8 avec HTML 5, CSS3 et JavaScript en une semaine–Jour 5


 

Comment “cuisiner” une application Windows 8 avec HTML 5, CSS3 et JavaScript en une semaine–Jour 5

  • Comments 4

La Release Preview (RP) de Windows 8 vient de sortir et vous pouvez la télécharger ici:
http://windows.microsoft.com/en-US/windows-8/release-preview

Du coup, il fallait bien que je porte mon petit UrzaGatherer vers la Release Preview avec comme objectif de le mettre sur le store au plus vite (afin de satisfaire les milliards de passionnés de Magic qui attendent la bave aux lèvres pour gérer leur collection de cartes !).

Je vais également en profiter pour parler de quelques améliorations que j’ai apporté comme le support du binding par exemple.

image

 

La version complète est donc disponible ici:
http://www.catuhe.com/MSDN/urza/day5.zip

L’intégralité de la série est disponible ici:

 

Porter vers la Release Preview

Il existe un document permettant de porter vers la RP que vous pourrez trouver ici:
http://go.microsoft.com/fwlink/?LinkId=251943

De plus, je vous invite à aller lire le blog des développeurs de Windows 8 qui reviennent sur les changements depuis la Consumer Preview:
http://blogs.msdn.com/b/windowsappdev/archive/2012/05/31/what-s-changed-for-app-developers-since-the-consumer-preview.aspx

Pour ma part, je reviendrai sur les sujets marquants de ma migration.

Le nouveau navigator.js

Le premier point que j’ai eu à modifier est le navigateur dans lequel j'ai ajouté quelques fioritures.

Tout d’abord j’ai modifié la navigation entre pages pour intégrer des animations. J’ai du coup rajouté le support d’une fonction que chaque page peut ajouter pour être appelée une fois l’animation jouée. Cela a le double avantage de lancer votre code une fois que tout est prêt et ceci sans prendre la puissance aux animations (ce qui pourraient les faire saccader).

Le bloc du navigator en question est le suivant:

_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);
                    }
                });
            });
        })
    );
},

Vous noterez donc la présence de la fonction WinJS.UI.Animation.enterPage et l’appel (si la fonction existe) à la fonction afterPageEnter de la page chargée.

Si vous voulez récupérer tout le fichier, c’est par ici:
http://www.catuhe.com/MSDN/Urza/Navigator.zip

La page default

La première page a également dû changer vers le code suivant:

(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(); })();

Le lancement passe désormais par une promise portée par l’argument du onactivated et c’est par le code que l’on lance la navigation.

La listView en mode multi-tailles

Un autre point clef du portage (bien que minime au final) s’est situé au niveau de mes listViews avec des éléments de tailles variables MAIS proportionnelles. Pour bien préciser cette notion de proportionnel, les propriétés ont été renommées pour ne pas parler de “size” mais de “cell”:

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

Je le redis ici, il s’agit bien de cellules c’est à dire que votre contenu peut occuper une taille proportionnelle (en entier) avec la taille de base des cellules.

Le bug du zoom sémantique

La Release Preview n’est pas la version définitive et forcément il reste certains petits bugs. J’ai eu droit à mon lot avec un particulièrement retors!

Sur la page principale nous avons un zoom sémantique qui contient deux listes. Sous certaines conditions qui semblent être liées à la puissance de la machine, la listView de base ne s’affichait pas. Ce n’était pas reproductible en debug (chouette) et pas à tout les coups (re-chouette!).

Heureusement pour moi, Pierre Lagarde (mon héros) passait par là et me voyant m’arracher les cheveux que je n’ai pas m’a glissé la solution à l’oreille! (Bon pour tout dire, il la tenait de Simon Ferquel!).

Pour compenser ce bug, il suffit de redéclencher un layout sur le zoom sémantique une fois que tout est branché:

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();

 

Et cela marche du tonnerre de zeus! Vous noterez juste la présence de la variable onlyOnce pour effectuer ce patch uniquement la première fois.

La magie du setImmediate

Un dernier point important lors de cette migration est basé sur la compréhension et l’utilisation de la méthode setImmediate (ou msSetImmediate):
http://msdn.microsoft.com/en-us/library/windows/apps/hh453394.aspx

Cette fonction a pour but de faire exécuter la fonction qu’elle prend en paramètre immédiatement après l’exécution du bloc courant. Le truc important a comprendre est que si il y a des opérations en attente lorsque le bloc courant sera fini, elles seront traitées AVANT votre fonction en paramètre.

Cela va par exemple permettre de traiter des problèmes tel que le clic frénétique sur le bouton Back. En effet, en faisant cela, la page va naviguée vers la page précédente sans forcément laisser l’interface se construire et du coup WinJS peut se retrouver à manipuler des objets qui viennent de passer à nul.

Pour résoudre cela, j’ai modifié dans le fichier Navigator.js le code de gestion du Back:

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

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

Je pourrai citer un autre exemple parmi tant d’autres au niveau de la gestion du la position du scrolling de mes listViews. En effet pour être sur que tout est bien affiché, j’exécute le code suivant pour positionner le scroll:

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

 

Ajout du binding avec WinJS.Binding.as

C’est un sujet que je n’avais pas encore couvert dans cette série et qui pourtant a son importance. J’y suis venu suite à ma volonté d’avoir sur la page principale une carte aléatoire qui changerait toutes les minutes:

image

Pour ce faire j’ai crée une liste contenant à la fois les données de la liste (les blocks et les extensions) et en premier lieu une carte. L’affichage se faisant simplement grâce à mes renderers.

Pour pouvoir changer la carte sans devoir tout reconstruire je suis passé par la classe WinJS.Binding.as:

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

En faisant juste cette opération, toute la chaine de binding est en place. Ainsi soit le template suivant:

<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">&#xE18A</div> </div> </div>

On peut voir que tout est basé sur du data-win-bind. Or dans le cas ou la data est encapsulé dans un objet as la moindre modification sur l’encapsuleur remet à jour l’affichage!! (oui comme en 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;
}

Du coup j’en ai profité pour changer mes templates un peu partout pour tout baser sur ce principe. Du coup, lorsque l’utilisateur bascule de local à distant ou lorsqu’il passe de français à anglais, tout cela se fait en live Sourire.

Exemple pour le mode offline:

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";
            }
    }

 

Préparer son application pour la publication sur le store

Pour finir, je vous invite à aller lire le mémo rédigé sur le sujet par Stéphanie Hertrich:
http://blogs.msdn.com/b/stephe/archive/2012/05/29/m-233-mo-win8-pr-233-parer-la-publication-sur-le-store.aspx

Si vous suivez ce document, vous pourrez publier sereinement !

image

Leave a Comment
  • Please add 5 and 1 and type the answer here:
  • Post
  • Article intéressant.

    J'ai juste relevé une petite faute d'orthographe.

    "J’ai du coup rajouter le support d’une fonction"

    ==> rajouté

  • Merci bcp!! C'est corrigé:)

  • Quand je lance l'application dans visual Express 2013 sur Windows 8.1 il me demande un reciblage ..

    Je suis complètement perdu :p

  • Effectivement car vs2013 est fait pour win8.1

    Il faut prendre vs2012 pour 8

Page 1 of 1 (4 items)