Comment “cuisiner” une application Windows 8 avec HTML 5, CSS3 et JavaScript en une semaine–Jour 3 - 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 3


 

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

  • Comments 1

(La version finale est accessible 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)

Nous allons aujourd’hui nous pencher sur l’intégration de notre application au sein de Windows 8 Metro.

Nous avions déjà intégré le mode snapped dans les précédents épisodes et nous allons dans cet article nous attaquer aux sujets suivants:

  • Contrat de recherche
  • Contrat de partage
  • Contrat d’ouverture de fichier
  • Vignette interactive
  • Vignettes secondaires

Ces cinq sujets sont très importants pour la bonne symbiose entre votre application et Windows 8 Metro.

image

 

Vous pouvez de plus retrouver les précédents épisodes juste ici :

Comme toujours le contenu complet de la solution est disponible ici: http://www.catuhe.com/msdn/urza/day3.zip

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

Les contrats? C’est quoi donc prévu pour?

Un contrat constitue la définition d’une interface technique entre une application et Windows 8 Metro. C’est un sujet important puisqu’il permet à votre application d’avoir de nouveaux points d’entrée en plus de sa vignette principale.

Il va vous permettre de vous intégrer au sein de services primordiaux de Windows 8 comme la recherche, le partage de données ou bien encore la sélection de fichiers.

Ce n’est pas un sujet à prendre à la légère lors de votre réflexion car cela peut fortement manquer à vos utilisateurs si vous omettez des contrats que vous auriez pu couvrir.

Nous allons donc couvrir les trois principaux contrats avec UrzaGatherer puisque chacun d’eux à un sens dans notre cadre (quelle chance!!).

Contrat de recherche

Le contrat de recherche va permettre à votre utilisateur de faire une recherche dans votre application au même niveu qu’il ferait une recherche dans ses fichiers ou dans ses applications:

image

Windows 8 se chargeant de remonter les applications les plus souvent “cherchées”, c’est un espace ou toutes les applications proposant de la recherche vont apparaitre.

Pour indiquer que vous supportez la recherche, il suffit de modifier le manifeste de l’application, pour en faire la déclaration comme indiqué ci-dessous :

image

A noter que vous pouvez indiquer une page à charger dans le cadre du lancement de l’application par le biais de la recherche (si votre application n’est pas déjà lancée quand l’utilisateur la sélectionne comme cible de recherche dans la fenêtre de recherche de Windows 8).

Par la suite il faut modifier le fichier default.js pour intégrer justement le fait que l’on peut être lancé par le service de recherche :

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

En cas de lancement par le service de recherche, il faut récupérer et stocker le texte de la recherche :

switch (eventObject.detail.kind) {
    case Windows.ApplicationModel.Activation.ActivationKind.search:
        UrzaGatherer.QueryText = eventObject.detail.queryText;
        break;

Si cette variable est définie lorsque l’on arrive sur home.html, on reroutera immédiatement l’utilisateur vers la page de résultat de la recherche :

if (UrzaGatherer.QueryText) {
    var queryText = UrzaGatherer.QueryText;
    delete UrzaGatherer.QueryText;

    nav.navigate("/pages/search/search.html", { queryText: queryText });
}

De plus il faut également gérer le fait que la recherche peut être lancée alors que notre application est active. Pour ce faire il suffit de réagir à un évènement de WinRT au niveau de default.js:

// Search
var searchPane = Windows.ApplicationModel.Search.SearchPane.getForCurrentView();
searchPane.addEventListener("querysubmitted", function (e) {
    nav.navigate("/pages/search/search.html", { queryText: e.queryText });
}, false);

Comme vous pouvez le constater, si une requête est soumise nous naviguerons vers la page de résultat de la recherche en lui passant le texte de la recherche.

La page de résultat va se contenter de mimiquer la page des extensions mais au lieu d’afficher les cartes d’une extension, elle affichera toutes les cartes dont le texte contiendra le texte de recherche :

image

A noter que j’en profite pour rajouter des filtres afin de proposer une expérience de recherche/filtrage riche dans l’application.

Un autre service que peut proposer l’application dans le cadre du contrat de recherche est la suggestion de réponse. Ainsi au fur et à mesure que l’utilisateur tape ce qu’il veut chercher, l’application sélectionnée peut faire des propositions :

image

Pour faire cela, il suffit encore de s’abonner à un évènement :

// Register to Handle Suggestion Request
searchPane.addEventListener("suggestionsrequested", function (e) {
    var query = e.queryText;
    var maxNumberOfSuggestions = 5;
    var suggestionRequest = e.request;

    for (var i = 0, len = UrzaGatherer.Cards.length; i < len; i++) {
        if (UrzaGatherer.Cards[i].name.substr(0, query.length).toLowerCase() === query) {
            suggestionRequest.searchSuggestionCollection.appendQuerySuggestion(UrzaGatherer.Cards[i].name);
            if (suggestionRequest.searchSuggestionCollection.size === maxNumberOfSuggestions) {
                break;
            }
        }
    }
}, false);

 

Contrat de partage

Le contrat de partage va vous permettre de ne plus avoir à vous préoccuper de coder des services de partage sur les réseaux sociaux. En effet, auparavant si vous souhaitiez pouvoir publier sur Facebook, sur Twitter ou sur tout autre réseau vous deviez intégrer le code dans votre application.

Avec le contrat de partage, vous pouvez dorénavant indiquer que vous êtes un producteur de données de partage ou un service capable de les publier (vers un réseau social ou toute application capable de consommer ces données comme par exemple un courrier) :

image

Ainsi la responsabilité est partagée et cela encourage fortement le travail commun entre application.

Pour UrzaGatherer par exemple, nous allons considérer que chaque fiche représentant une carte va pouvoir exposer l’image de la carte vers le service de partage ou l’utilisateur pourra choisir une application capable de traiter cette information (l’envoyer par mail, la publier sur un réseau social, etc.)

Pour faire cela, il suffit de se déclarer auprès du DataTransferManager :

// Share
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.addEventListener("datarequested", shareFunction);

La fonction en question est la suivante :

var shareFunction = function (e) {
    var request = e.request;
    var deferral = request.getDeferral();

    request.data.properties.title = card.name + UrzaGatherer.Tools.GetString("ShareTitle") 
+ card.expansion.name; request.data.properties.description = UrzaGatherer.Tools.GetString(
"ShareDescription"); UrzaGatherer.Tools.GetCardFile(card).then(function (file) { var streamReference = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(file); request.data.properties.thumbnail = streamReference; request.data.setStorageItems([file]); request.data.setBitmap(streamReference); deferral.complete(); }); };

Elle doit juste remplir la requête avec des informations issues de la carte en indiquant grâce à getDeferral que la méthode est asynchrone (et donc que ce n’est pas parce qu’elle rend la main qu’elle a fini).

 

Contrat d’ouverture de fichier

Le contrat d’ouverture de fichier va permettre à votre application d’être fournisseur de fichiers (comme par exemple une application Skydrive qui fournirait à d’autres applications le contenu de votre Skydrive comme si il était local).

Pour UrzaGatherer, nous allons fournir à toutes les applications voulant requêter une image la possibilité de récuperer une image de notre collection.

Ainsi prenons l’exemple d’un nouveau courrier à écrire :

image

En cliquant en bas à gauche, je peux choisir de rajouter une pièce jointe et l’écran de sélection de fichiers de Windows 8 Metro s’ouvre :

image

Comme UrzaGatherer est un fournisseur de fichier, il apparait dans la liste des sources au même niveau que le contenu de mon disque et en le sélectionnant je me retrouve avec une expérience intégrant UrzaGatherer dans le sélecteur. Lors de cette expérience, il m’est possible de choisir des images qui vont remonter vers l’application appelante :

image

Pour activer ce contrat, comme dans le cas du contrat de recherche nous allons aller modifier le manifeste pour indiquer à Windows 8 Metro que nous sommes capable de supporter le contrat d’ouverture de fichier pour les extensions de type .jpg :

image

Pour mettre ce contrat en place, c’est très simple. Tout d’abord on va gérer le fait que notre application va être appelée par le système en mode “fileOpenPicker” :

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){

 

Et du coup, il faut juste sauvegarder une information qui nous est faite passée afin de communiquer avec Windows 8 pour lui faire passer les fichiers que l’on veut transmettre :

switch (eventObject.detail.kind) {
    case Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker:
        UrzaGatherer.FileOpenPickerUI = eventObject.detail.fileOpenPickerUI;
        break;
    default:

Une fois dans la page des extensions, lorsque l’on va cliquer sur une carte, plutôt que d’ouvrir la fiche de la carte, nous allons remonter un fichier représentant l’image de la carte :

itemInvoked: function (eventObject) {
    if (UrzaGatherer.FileOpenPickerUI) {
        var item = eventObject.detail.itemPromise.then(function (invokedItem) {
            var card = invokedItem.data;

            UrzaGatherer.Tools.GetCardFile(card).then(function (file) {
                UrzaGatherer.FileOpenPickerUI.addFile(card.name, file);
            });
        });
        return;
    }
    nav.navigate("/pages/card/card.html", { cardIndex: eventObject.detail.itemIndex, cards: filtered });
},

La fonction suivante est alors utilisée pour générer le fichier à partir de la carte :

var getCardFile = function (card) {
    // HTTP ?
    if (card.logo.substring(0, 5).toLowerCase() == "http:") {
        var uri = new Windows.Foundation.Uri(card.logo);
        var thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(uri);

        return Windows.Storage.StorageFile.createStreamedFileFromUriAsync(card.name + ".jpg", 
uri, thumbnail); }
// Local ? return Windows.Storage.ApplicationData.current.localFolder.getFileAsync(card.alt); }

Vignette interactive

Windows 8 Metro se base maintenant sur des vignettes pour lancer les applications. Une vignette est une super icone (http://msdn.microsoft.com/en-us/library/windows/apps/hh779724.aspx) qui peut être dynamique et mise à jour par l’application, par une tâche de fond ou par un service de notification :

image

Envoyer une notification de mise à jour à la vignette

Nous allons dans le cadre de UrzaGatherer mettre à jour cette tuile à chaque fois qu’une carte est ouverte avec le code suivant :

var updateTile = function (card) {
    var Notifications = Windows.UI.Notifications;
    var Imaging = Windows.Graphics.Imaging;

    var tileXml = Notifications.TileUpdateManager.getTemplateContent(
Notifications.TileTemplateType.tileWideSmallImageAndText02);
var tileTextAttributes = tileXml.getElementsByTagName("text"); tileTextAttributes[0].appendChild(tileXml.createTextNode("UrzaGatherer")); tileTextAttributes[1].appendChild(tileXml.createTextNode(card.name)); tileTextAttributes[2].appendChild(tileXml.createTextNode(card.expansion.name)); tileTextAttributes[3].appendChild(tileXml.createTextNode(card.expansion.block.name)); var filename = card.alt.replace(".jpg", "_small.png"); rescaleImage(card.logo, 150, 150, filename, true, function (appDatafilename) { // Image var tileImageAttributes = tileXml.getElementsByTagName("image"); tileImageAttributes[0].setAttribute("src", appDatafilename); // Square var squareTileXml = Notifications.TileUpdateManager.getTemplateContent(
Notifications.TileTemplateType.tileSquareImage);
var squareTileImageAttributes = squareTileXml.getElementsByTagName("image"); squareTileImageAttributes[0].setAttribute("src", appDatafilename); var node = tileXml.importNode(squareTileXml.getElementsByTagName("binding").item(0), true); tileXml.getElementsByTagName("visual").item(0).appendChild(node); // Update var tileNotification = new Notifications.TileNotification(tileXml); tileNotification.tag = card.id; var tileUpdater = Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication(); tileUpdater.enableNotificationQueue(true); tileUpdater.update(tileNotification); }); }

Tout d’abord il nous faut choisir la configuration de notre tuile avec la fonction Notifications.TileUpdateManager.getTemplateContent (http://msdn.microsoft.com/en-us/library/windows/apps/hh761491.aspx).

Cette fonction retourne un objet XML qu’il faut renseigner avec des informations que l’on veut mettre en avant sur la vignette.

Générer une image retaillée pour la vignette

Pour la partie image, j’ai du redimensionner mes images pour les adapter à la taille carré des images présentées grâce à la fonction rescaleImage :

var rescaleImage = function (src, destinationWidth, destinationHeight, localfilename, fillAlpha, then) {
    var Imaging = Windows.Graphics.Imaging;
    var image = new Image();

    // lors du chargement
    image.addEventListener('load', function () {
        var canvas = document.createElement('canvas');

        canvas.width = destinationWidth;
        canvas.height = destinationHeight;

        var targetWidth;
        var targetHeight;

        if (this.width > this.height) {
            var ratio = destinationWidth / this.width;
            targetWidth = destinationWidth;
            targetHeight = this.height * ratio;
        }
        else {
            var ratio = destinationHeight / this.height;
            targetWidth = this.width * ratio;
            targetHeight = destinationHeight;
        }

        var context = canvas.getContext('2d');
        if (fillAlpha)
            context.clearRect(0, 0, canvas.width, canvas.height);
        else {
            context.fillStyle = "#fff";
            context.fillRect(0, 0, canvas.width, canvas.height);
        }
        context.drawImage(this, (canvas.width - targetWidth) / 2, (
canvas.height - targetHeight) / 2, targetWidth, targetHeight); Windows.Storage.ApplicationData.current.localFolder.createFileAsync(localfilename, Windows.Storage.CreationCollisionOption.replaceExisting).then(
function (file) { file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) { Imaging.BitmapEncoder.createAsync(Imaging.BitmapEncoder.pngEncoderId, stream).then(
function (encoder) { encoder.setPixelData(Imaging.BitmapPixelFormat.rgba8, Imaging.BitmapAlphaMode.straight, canvas.width, canvas.height, 96, 96, new Uint8Array(context.getImageData(0, 0, canvas.width, canvas.height).data)); encoder.flushAsync().then(function () { stream.flushAsync().then(function () { stream.close(); if (then) then("ms-appdata:///local/" + localfilename.replace("\\", "/")); }); }); }); }); }); }, false); // Chargement image.src = src; }

Le fonctionnement est donc le suivant :

  • On crée une image HTML et on s’abonne à son événement load
  • On lui donne l’url de l’image à retailler
  • Après le chargement de l’image on la dessine dans un canvas mis à la bonne taille (celle du redimensionnement donc)
  • On récupère les pixels du canvas grâce à getImageData
  • On crée un BitmapEncoder pour générer un fichier bitmap
  • En utilisant un typed array (Uint8Array ) on transfère sans conversion les données du canvas vers la fonction setPixelData du BitmapEncoder
  • On envoie le tout vers le fichier et le tour est joué!

Enregistrement de plusieurs mise à jour de la vignette pour les faire cycler

Par la suite une fois que notre image est prête, en utilisant un tileUpdater (via la fonction Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication) nous allons enregistrer la mise à jour de la vignette (en notant que l’on active une file de notifications qui permet d’avoir plusieurs vignettes qui cycleront plutôt qu’une seule).

Et pour éviter de polluer le disque du client, nous allons stocker le chemin des images pour chaque vignette afin de supprimer celles qui ne sont plus utiliser :

// Store and clean
var previousTilesValue = Windows.Storage.ApplicationData.current.localSettings.values["previousTiles"];
var previousTiles = [];

if (previousTilesValue)
    previousTiles = JSON.parse(previousTilesValue);

previousTiles.push(filename);

if (previousTiles.length > 5) {
    var toRemove = previousTiles.shift();

    Windows.Storage.ApplicationData.current.localFolder.getFileAsync(toRemove).then(function (file) {
        file.deleteAsync().done();
    });
}

Windows.Storage.ApplicationData.current.localSettings.values["previousTiles"] = 
JSON.stringify(previousTiles);

 

Vignettes secondaires

Les vignettes secondaires fonctionnent comme les vignettes principales à ceci près qu’elles doivent donner accès à un autre endroit de l’application que la page principale.

Dans le cadre de UrzaGatherer, la page des extensions pourra proposer via son appbar (la barre qui apparait en bas quand on clique avec le bouton droit de la souris ou quand on glisse son doigt du bord bas de l’écran vers le centre) de créer une vignette secondaire pour arriver directement sur l’extension courante :

image

 

J’ai donc commencé par créer l’appbar :

<!--App bar-->
<div data-win-control="WinJS.UI.AppBar" data-win-options="">
    <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'pinButton', 
icon:'pin',section:'global'}"> </
button> </div>

Par la suite en cliquant sur le bouton de l’appbar, je vais lancer le code suivant :

var pinByElementAsync = function (then) {
    var tileID = expansion.id;
    var shortName = expansion.name;
    var displayName = expansion.name;
    var tileOptions = Windows.UI.StartScreen.TileOptions.showNameOnWideLogo;
    var tileActivationArguments = expansion.id;

    UrzaGatherer.Tools.RescaleImage(expansion.logo, 150, 150, 
expansion.logoPath.replace(
".png", "_uri.png"), false, function (uriLogo) { var tile = new Windows.UI.StartScreen.SecondaryTile(tileID, shortName, displayName,
tileActivationArguments, tileOptions,
new Windows.Foundation.Uri(uriLogo)); tile.foregroundText = Windows.UI.StartScreen.ForegroundText.dark; UrzaGatherer.Tools.RescaleImage(expansion.logo, 310, 150,
expansion.logoPath.replace(
".png", "_wide.png"), false, function (wideLogo) { tile.wideLogo = new Windows.Foundation.Uri(wideLogo); var element = document.getElementById("pinButton"); var selectionRect = element.getBoundingClientRect(); tile.requestCreateAsync({ x: selectionRect.left, y: selectionRect.top }).then(then); }); }); };

Vous pourrez noter la réutilisation de mon RescaleImage pour préparer les logos à la bonne taille pour la vignette.

La vignette est produite avec un paramètre personnalisable (tileActivationArguments) qui sera transmis à l’application en cas de lancement par la vignette secondaire. Nous allons donc pouvoir la récupérer au lancement de notre application dans le default.js :

 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) {

switch (eventObject.detail.kind) { case Windows.ApplicationModel.Activation.ActivationKind.search: UrzaGatherer.QueryText = eventObject.detail.queryText; break; case Windows.ApplicationModel.Activation.ActivationKind.fileOpenPicker: UrzaGatherer.FileOpenPickerUI = eventObject.detail.fileOpenPickerUI; break; default: UrzaGatherer.Arguments = eventObject.detail.arguments; break; }

Ainsi dans la page home.html, si UrzaGatherer.Arguments n’est pas null, nous allons pouvoir aller directement à la page des extensions en lui fournissant la bonne extension :

if (UrzaGatherer.Arguments) {
    var expansionID = parseInt(UrzaGatherer.Arguments);
    delete UrzaGatherer.Arguments;

    var expansion;
    
    for (var index = 0; index < UrzaGatherer.Expansions.length; index++) {
        var exp = UrzaGatherer.Expansions.getAt(index);

        if (exp.id == expansionID) {
            expansion = exp;
            break;
        }
    }

    nav.navigate("/pages/expansion/expansion.html", { expansion: expansion });
}

Je peux donc me retrouver sur mon Windows 8 Metro avec une vignette principale pour mon application et plusieurs vignettes secondaires pour aller directement dans des sous-parties de UrzaGatherer :

image

A noter en plus que comme les principales les vignettes secondaires sont bien sur potentiellement dynamiques.

 

A suivre

Lors de notre prochaine étape, nous intègrerons le support de Live SDK et de Skydrive afin de gérer votre collection (à savoir indiquer quelles cartes vous possédez et quelles cartes il vous manque).

Leave a Comment
  • Please add 1 and 3 and type the answer here:
  • Post
  • Merci beaucoup pour ces tutos ... des merveilles

Page 1 of 1 (1 items)