Voici donc la dernière partie de notre série. En fait, ce n’est pas tout à fait exact car je me permettrai de rajouter un post pour le portage vers la Release Preview (disponible ici: http://blogs.msdn.com/b/eternalcoding/archive/2012/06/08/comment-cuisiner-une-application-windows-8-avec-html-5-css3-et-javascript-en-une-semaine-jour-5.aspx). De plus, un certain Pierre Lagarde pourrait également apporter sa pierre à l’édifice dans un futur post Sourire.

Toutefois en ce qui concerne les fonctionnalités, nous pouvons considérer que cette version est complète :

image_thumb7

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

Et bien sur vous pouvez d’ores et déjà télécharger la version complète ici : http://www.catuhe.com/msdn/urza/day4.zip

Au cours de cette article nous allons donc découvrir comme utiliser Skydrive (grâce au SDK Live) pour sauvegarder l’état de la collection de l’utilisateur.

En effet, l’intégralité de la liste de cartes est téléchargée depuis un fichier json (voir jour 0). L’état de la collection de l’utilisateur quand à lui sera sauvé dans un autre fichier json pour permettre de mettre à jour la liste globale sans perdre quoique ce soit. Ce fichier sera alors sauvegardé sur Skydrive.

Ajouter un fichier pour gérer l’état de la collection de l’utilisateur

Dans la page d’extension, nous allons pouvoir ajouter une barre d’application contextuelle afin de permettre à l’utilisateur d’indiquer quelle carte est présente dans sa collection :

image_thumb10

Les nouveaux boutons de la barre d’application sont les suivants :

<button data-win-control="WinJS.UI.AppBarCommand" 
data-win-options="{id:'checkButton',section:'selection'}"> </button> <button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'uncheckButton',section:'selection'}"> </button> <hr data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{type:'separator',section:'selection'}" /> <button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'checkAllButton',section:'selection'}"> </button> <button data-win-control="WinJS.UI.AppBarCommand"
data-win-options="{id:'uncheckAllButton',section:'selection'}"> </button>

Chaque bouton est réglé pour apparaitre dans la section ‘selection’ afin de n’être visible que lorsque des éléments de la listView sont sélectionnés. Pour ce faire, vous pouvez cliquer droit sur un élément ou glisser avec le doigt vers le bas sur un élément. Si aucun élément n’est sélectionné la barre d’application contiendra uniquement les éléments de la section ’global’.

De plus, n’oubliez pas d’activer la sélection multiple sur la listView :

listView.selectionMode = WinJS.UI.SelectionMode.multi;

Par la suite en cliquant sur ces boutons, vous allez exécuter l’une des deux fonctions suivantes :

var removeFromCollection = function (tab) {
    if (!UrzaGatherer.UserData) {
        showWarning();
        return;
    }

    for (var index = 0; index < (tab ? tab.length : filtered.length); index++) {
        var card = filtered.getAt(tab ? tab[index] : index);

        card.isChecked = false;

        if (card.checkElement)
            WinJS.Utilities.addClass(card.checkElement, "hidden");

        delete UrzaGatherer.UserData[card.id];
    };

    var listView = document.querySelector(".cardsList").winControl;
    listView.selection.clear();

    if (document.getElementById("checkFilter").selectedIndex > 0)
        that.updateLayout(document, appView.value);

    UrzaGatherer.Skydrive.UploadUserFile();

    document.querySelector(".appBar").winControl.hide();
};

var addToCollection = function (tab) {
    if (!UrzaGatherer.UserData) {
        showWarning();
        return;
    }

    for (var index = 0; index < (tab ? tab.length : filtered.length); index++) {
        var card = filtered.getAt(tab ? tab[index] : index);

        card.isChecked = true;

        if (card.checkElement)
            WinJS.Utilities.removeClass(card.checkElement, "hidden");

        UrzaGatherer.UserData[card.id] = true;
    };

    var listView = document.querySelector(".cardsList").winControl;
    listView.selection.clear();

    if (document.getElementById("checkFilter").selectedIndex > 0)
        that.updateLayout(document, appView.value);

    UrzaGatherer.Skydrive.UploadUserFile();

    document.querySelector(".appBar").winControl.hide();
};

Chaque fonction va d’abord vérifier que la propriété UrzaGatherer.UserData est bien présente. En effet cette dernière sert à stocker l’état de la collection.

De plus, si un paramètre est passé aux fonctions, elles ne travaillent que sur le paramètre en question sinon elles travaillent sur toutes les cartes de l’extension (pour gérer le “Ajouter tous” / ”Supprimer tous”).

Comme nous pouvons le voir, ajouter une carte à la collection revient à ajouter une propriété nommée comme l’identifiant unique de la carte à l’objet UrzaGatherer.UserData :

UrzaGatherer.UserData[card.id] = true;

Ainsi la collection de l’utilisateur n’est qu’un simple objet avec une propriété par carte. Du coup il est aisé de le sérialiser avec un petit JSON.stringify.

Pour faciliter l’usage dans le code, vous pouvez aussi rajouter une proprété isChecked sur les cartes.

Se connecter à Skydrive

Afin de garder la collection de l’utilisateur nous allons à la fois la sauver localement mais également sur Skydrive.

Pour ce faire, vous devez d’abord télécharger et référencer le SDK Live dans votre projet :

image_thumb1

Il faut également ajouter une référence dans le code (par exemple dans default.html) :

    <script src="///LiveSDKHTML/js/wl.js"></script>

Puis nous allons pouvoir nous connecter aux services de Live :

var init = function () {
    var onStatusChange = function (e) {
        WL.getLoginStatus(function (response) {
            if (response.status == "notConnected") {
                connect();
            }
        });
    };

    WL.Event.subscribe("auth.login", getUserFile);
    WL.Event.subscribe("auth.statusChange", onStatusChange);

    WL.init({
        scope: ["wl.signin", "wl.skydrive_update"]
    });
};

Ce code doit être appelé depuis un bouton de votre application par exemple (je vous recommande de le mettre dans un coin pour ne pas pervertir l’expérience METRO ou carrément dans les réglages de votre application (dans le charm)).

Comme vous pouvez le voir, le code s’abonne à deux événements : auth.login et auth.statusChange. Quand l’utilisateur est connecté, vous allez pouvoir appeler la fonction getUserFile(que nous allons voir plus tard) et quand le statut changera vers “notConnected”, vous devrez appeler le code suivant :

var connect = function () {
    if (UrzaGatherer.Skydrive.OnConnecting)
        UrzaGatherer.Skydrive.OnConnecting();

    WL.login({
        scope: ["wl.signin", "wl.skydrive_update"]
    }).then(function (response) {
        getUserFile();
    }, function (responseFailed) {

        if (responseFailed.error == "access_denied") {
        }
        else {
            if (UrzaGatherer.Skydrive.OnFailed)
                UrzaGatherer.Skydrive.OnFailed();
        }
    });
};

j’ai de plus défini certains événements pour permettre à mon interface de présenter des feedbacks visuels à l’utilisateur durant le processus de connexion. De plus, la fonction WL.Login affiche un contrôle qui demandera à l’utilisateur de se connecter sur Live et de donner son autorisation pour accéder à son Skydrive (“wl.signin” et “wl.skydrive_update”).

Si l’utilisateur refuse d’accorder son autorisation, on lèvera un évènement pour indiquer que l’application ne pourra pas fonctionner.

Si il accepte de donner son autorisation, le code suivant va être exécuter :

var getUserFile = function () {
    UrzaGatherer.UserLogged = true;

    // Creating folder
    WL.api({
        path: "me/skydrive",
        method: "POST",
        body: {
            "name": "UrzaGatherer",
            "description": "UrzaGatherer repository folder"
        }
    }).then(
            function (response) {
                Windows.Storage.ApplicationData.current.roamingSettings.values["folderID"] = response.id;
                getFileOnline();
            },
            function (responseFailed) {
                if (responseFailed.error.code != "resource_already_exists") {
                    getFileOffline();
                    if (UrzaGatherer.Skydrive.OnFailed)
                        UrzaGatherer.Skydrive.OnFailed();
                }
                else {
                    getFileOnline();
                }
            }
        );

};

Cette fonction crée un répertoire de travail pour UrzaGatherer sur le Skydrive de l’utilisateur et essaye de récupérer une version de la collection de l’utilisateur. Si une erreur se produit elle bascule sur la version locale :

Pour obtenir la version en ligne, le code suivant est disponible :

var getFileOnline = function () {
    var fileID = Windows.Storage.ApplicationData.current.roamingSettings.values["fileID"];

    if (!fileID) {
        getFileOffline();
        return;
    }

    // Get file info
    WL.api({
        path: fileID,
        method: "GET"
    }).then(
            function (response) {
                var distantVersion = parseInt(response.description ? response.description : "-1");
                var localVersion = currentUserFileVersion();

                // Download file
                if (localVersion < distantVersion) {
                    Windows.Storage.ApplicationData.current.localFolder.
createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { WL.download({ path: fileID, file_output: file }).then( function (response) { getFileOffline(); }, function (responseFailed) { getFileOffline(); } ); }); return; } getFileOffline(); }, function (responseFailed) { getFileOffline(); } ); };

Vous pouvez noter que dès qu’un fichier est chargé, une copie locale est créée.

Vous pouvez également remarquer qu’un numéro de version est maintenu afin de savoir si la version du serveur est plus récente que la version locale (afin de gérer plusieurs périphériques).

En cas d’erreur, le code suivant permettra de récupérer la copie locale (si elle existe) :

var getFileOffline = function () {
    Windows.Storage.ApplicationData.current.localFolder.getFileAsync(filename).then(function (file) {
        // On success
        Windows.Storage.FileIO.readTextAsync(file).then(function (data) {
            if (data)
                UrzaGatherer.UserData = JSON.parse(data);
            else
                UrzaGatherer.UserData = {};

            if (UrzaGatherer.Skydrive.OnConnected && UrzaGatherer.UserLogged)
                UrzaGatherer.Skydrive.OnConnected();

            if (UrzaGatherer.Skydrive.OnDataAvailable)
                UrzaGatherer.Skydrive.OnDataAvailable();
        });
    }, function () {
        // On error
        Windows.Storage.ApplicationData.current.localFolder.
createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { UrzaGatherer.UserData = {}; Windows.Storage.FileIO.writeTextAsync(file, JSON.stringify(UrzaGatherer.UserData)); Windows.Storage.ApplicationData.current.
localSettings.values[
"userFileVersion"] = currentUserFileVersion() + 1; if (UrzaGatherer.Skydrive.OnConnected && UrzaGatherer.UserLogged) UrzaGatherer.Skydrive.OnConnected(); if (UrzaGatherer.Skydrive.OnDataAvailable) UrzaGatherer.Skydrive.OnDataAvailable(); }); }); };

Au pire si le système n’arrive pas à trouver une copie locale, il génèrera une version vide.

Mise à jour du fichier sur Skydrive

Finalement, l’application peut appeler le code suivant pour mettre à jour l’état de la collection sur Skydrive :

var uploadUserFile = function (deferral) {
    Windows.Storage.ApplicationData.current.localFolder.
createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { Windows.Storage.FileIO.writeTextAsync(file, JSON.stringify(UrzaGatherer.UserData)).
then(
function () { var folderID = Windows.Storage.ApplicationData.current.roamingSettings.values["folderID"]; Windows.Storage.ApplicationData.current.localSettings.
values[
"userFileVersion"] = currentUserFileVersion() + 1; WL.upload({ path: folderID, file_name: file.name, file_input: file, overwrite: true }).then( function (response) { Windows.Storage.ApplicationData.current.roamingSettings.values["fileID"] = response.id; updateDistantFileVersion(currentUserFileVersion()); if (deferral) deferral.complete(); }, function (responseFailed) { if (deferral) deferral.complete(); } ); }); }); };

Une fois que le fichier est mis à jour, le numéro de version est changé sur le serveur et en local :

var updateDistantFileVersion = function (version) {
    var fileID = Windows.Storage.ApplicationData.current.roamingSettings.values["fileID"];

    if (!fileID)
        return;

    WL.api({
        path: fileID,
        method: "PUT",
        body: {
            description: version
        }
    }).done();
};

Ainsi avec une API assez réduite (à peine 5 fonctions), vous allez pouvoir vous connecter sur Skydrive et y créer vos fichiers et vos répertoires. Le SDK Live propose même de gérer pour vous le bouton de connexion pour le mettre au look & feel du SDK .

A suivre ?

Vous avez maintenant une application complète et conforme pour Windows 8. Vous allez pouvoir vous en servir de modèle pour développer l’application qui va vous rendre riche Sourire. N’hésitez pas à réutiliser le code ou les ressources que j’ai présenté dans cet article.

De mon côté, j’ai rajouté quelques fonctionnalités comme la carte du jour, des fonds dynamiques ou des animations un peu partout. Je publierai donc mon application UrzaGatherer sur le Windows Store dès le 6 juin pour la Release Preview (après l’avoir porté bien sur) :

 

image_thumb4