Archives pour la catégorie Windows Metro

Windows 8 – Jouer un son pendant l’affichage d’une notification Toast

Dans le tutorial précédent, on a vu comment envoyer une notification de type Toast à partir de son application Windows 8 Metro UI:

Windows 8 – Afficher des pop-ups de type Toast

Par défaut, les applications Windows jouent un son court quand le toast est affiché. Vous pouvez annuler ce son ou bien en donner un autre, parmi le catalogue de sons fournis par Windows 8.

Ajout de l’élément audio au template Toast

Par défaut, le template XML du Toast que vous utilisez ne contient pas de balise audio. On va d’abord récupérer une référence vers le noeud « toast » puis créer une balise <audio> qui va venir accueillir nos options audio:

var toastNode = toastXml.selectSingleNode("/toast");
var audio = toastXml.createElement("audio");

Après avoir fixé les options, il vous suffira de faire:

toastNode.appendChild(audio);

Annuler la lecture du son

Pour annuler la lecture du son par défaut, il faut définit la valeur de l’attribut « silent » à true pour la balise <audio>:

audio.setAttribute("silent", "true");

Jouer un son différent parmi le catalogue des sons Windows

Pour changer le son par défaut, vous devrez choisir parmi le catalogue des sons Windows 8 Metro et fixer l’attribut « src » du tag audio à l’identifiant du son comme ceci:

audio.setAttribute("src", "ms-winsoundevent:Notification.IM");

Voici la liste complète sur le site de Microsoft:

The toast audio options catalog (Windows Store apps)

Jouez un son qui se répète (appel, alarme, …)

Vous avez dans le catalogue des sons simples (arrivée d’un email / message, etc) ou répétés (appel, alarme). Pour les sons qui se répètent, vous devrez fixer l’attribut « loop » à « true » dans la balise audio:

audio.setAttribute("loop", "true");

Attention, la répétition du son n’est disponible que pour les toast indiqué comme étant de longue durée.

Spécifier un Toast avec affichage long

Par défaut, un Toast est affiché pendant une courte période de temps. Pour indiquer à Windows que le Toast doit être affiché plus longtemps, pour prévenir l’utilisateur d’un appel entrant par exemple, vous devrez affecter la variable « duration » sur le tag <toast>:

var toastNode = toastXml.selectSingleNode("/toast");
toastNode.setAttribute("duration", "long");

 

Windows 8 – Afficher des pop-ups de type Toast

Dans une des précédents tutoriaux Windows 8 Metro en HTML5, on a vu comment afficher une fenêtre de type « alert », grâce à la classe Windows.UI.Popups.MessageDialog. Cette pop-up est modale et parfois, vous avez simplement besoin d’afficher un message en fond, sans troubler la navigation de l’utilisateur.

Pour cela, Windows a implémenté les notifications de type Toast. Celles-ci fonctionne en partie comme la mise à jour des tuiles du menu démarrer:

http://html5-tutorial.fr/windows-metro-clouder-14-modifier-la-tile-du-menu-demarrer-avec-notifications-et-tileupdatemanager/

Modification du manifest de l’application

Pour que votre application puisse afficher des Toast, vous devrez l’indiquer dans le manifest de l’application. Ouvrez donc le fichier package.appxmanifest dans Visual Studio et dans l’onglet « Application UI » vous trouverez l’option « Toast Capable (not set) ». Indiquer « Yes ». Sauvegardez.

Envoi d’une notification toast simple

On commence par ajouter un simple bouton dans notre interface:

<button id="sendNotification">Send Notification</button>

Puis on ajoute un listener en JavaScript dans le fichier JS correspondant:

(function () {
    "use strict";

    WinJS.UI.Pages.define("/pages/home/home.html", {

        onSendNotification : function(){

        },

        ready: function (element, options) {
            var btn = document.getElementById("sendNotification");
            btn.addEventListener("click", this.onSendNotification, false);
        }
    });
})();

Ensuite, cela se passe un peu comme les tuiles du menu démarrer Windows 8 (Live Tile). Vous allez choisir un template prédéfini (un XML) que vous allez remplir avec vos informations (textes, images) et utiliser une méthode de WinJS pour envoyer cette notification.

Exemple simple:

    onSendNotification : function(){
        var template = Windows.UI.Notifications.ToastTemplateType.toastText02;
        var toastXml = Windows.UI.Notifications.ToastNotificationManager.getTemplateContent(template);

        var toastTextElements = toastXml.getElementsByTagName("text");
        toastTextElements[0].appendChild(toastXml.createTextNode("Titre de la notification"));
        toastTextElements[1].appendChild(toastXml.createTextNode("Message de la notification"));

        var toast = new Windows.UI.Notifications.ToastNotification(toastXml);

        var toastNotifier = Windows.UI.Notifications.ToastNotificationManager.createToastNotifier();
        toastNotifier.show(toast);
    },

Comme vous pouvez le constater, on prend le template « toastText02 » qui contient 2 textes. On récupère sa définition XML dans « toastXml » puis on récupère les balises « <text> » de cette définition. Ensuite, on met le titre de la notification dans la première et le message dans la deuxième.

On créé ensuite une Notification native de type ToastNotification à partir de notre XML rempli. On récupère une référence vers un ToastNotifier et on lui passe la notification à afficher.

Résultat:

Bien sûr, la notification va s’adapter au style graphique de votre application. Quand vous allez définir un icône pour votre application, il apparaîtra à la place du logo carré barré par défaut. Idem pour la couleur de fond, qui va reprendre celle définie dans le fichier package.appxmanifest.

Lire la suite

Windows 8 – Réaliser une ListView avec pagination intégrée

Vous savez sûrement déjà créer une liste de type Windows 8 Metro UI avec le composant ListView. Sinon, vous pouvez commencer par lire ces articles:

Windows Metro – Clouder! (8) – Chargement des commentaires depuis l’API SoundCloud

Windows 8 – Utiliser un template différent selon l’élément affiché dans une ListView

Cela fonctionne bien si vous avez une dizaine voire une vingtaine d’éléments à afficher. Au delà de cela, il se peut que le premier chargement soit long et que le rendu puisse ralentir l’application. Dans ce genre de situation, la solution est bien sûr de paginer ses résultats, c’est-à-dire de les afficher par tranches de 20 ou 30.

La méthode simple

La méthode la plus simple est d’ajouter un bouton dans l’interface pour charger la prochaine page. A l’appui sur le bouton, on fait un appel XHR pour récupérer les nouveaux résultats. On peut ensuite les ajouter à notre objet de type WinJS.Binding.List et notre grille va se mettre à jour toute seule.

La méthode est simple mais pas très élégante car vous devrez trouver un endroit adapté pour placer votre bouton. Les applications Windows 8 ayant généralement une interface plein écran, vous n’allez pas vraiment pouvoir le caser quelque part.

Une solution plus élégante est de regarder le scroll de la liste et quand il arrive au bout, charger automatiquement une autre page (alias infinite scroll). Perso, je préfère avoir une action utilisateur avec un bouton à presser, pour éviter que le programme fuck up le scroll pendant que je me déplace.

La méthode intégrée

Une méthode plus sympa est de placer un élément à la fin de la liste qui va servir pour la pagination. Cet élément sera complètement intégré au rendu de la liste, vous n’aurez donc pas à vous soucier de son placement.

On va aussi voir comment se servir de cet élément bidon pour afficher que le reste de la liste est en chargement à l’utilisateur avec un composant <progress>.

Pour l’exemple de la liste, veillez vous reporter à l’exemple de cet article que l’on va modifier:

Windows 8 – Utiliser un template différent selon l’élément affiché dans une ListView

On va commencer par créer un template pour les éléments de base et un pour l’élément de pagination:

   <div class="templateBase" data-win-control="WinJS.Binding.Template">
        <div class="item-overlay" style="background-color:blue;width:200px;height:200px;">
            <h2 class="item-title" data-win-bind="textContent: title"></h2>
            <h4 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h4>
        </div>
    </div>

    <!-- PAGINATION TEMPLATE -->
    <div class="paginationTemplate" data-win-control="WinJS.Binding.Template">
        <div style="background-color:#FF6600;width:200px;height:200px;">
           <button  class="paginationIcon" data-win-control="WinJS.UI.AppBarCommand" 
            data-win-options="{icon:'add'}"
               style="margin-left:50px;margin-top:60px;"
            ></button>
            <progress class="win-ring win-large paginationRing" style="color:#FFFFFF;padding-left:65px;padding-top:65px;"></progress>
        </div>
    </div>

Puis on va modifier un peu notre JavaScript pour y intégrer un élément de pagination à la fin de la liste. On va aussi modifier notre fonction itemTemplate pour détecter la propriété « pagination » dans les objets envoyés:

(function () {
    "use strict";

    WinJS.UI.Pages.define("/pages/home/home.html", {

        pageSize: 5,
        currentPage:0,

        sampleItems: [
            {  title: "Item Title: 9", subtitle: "Item Subtitle: 0" },
            { title: "Item Title: 1", subtitle: "Item Subtitle: 1" },
            {  title: "Item Title: 2", subtitle: "Item Subtitle: 2" },
            {  title: "Item Title: 3", subtitle: "Item Subtitle: 3" },
            {  title: "Item Title: 4", subtitle: "Item Subtitle: 4"},
            {  title: "Item Title: 5", subtitle: "Item Subtitle: 5" }],

        models: null,

        paginationItem : {pagination:true},

        itemTemplateFunction : function (itemPromise, recycledElement) {
            return itemPromise.then(function (currentItem) {
                var itemData = currentItem.data;
                var tpl = null;
                if (itemData.pagination) {
                    tpl = document.querySelector(".paginationTemplate");
                } else {
                    tpl = document.querySelector(".templateBase");
                }
                // dump div to contain the result
                var result = document.createElement("div");
                return tpl.winControl.render(itemData, result);
            })
        },

        ready: function (element, options) {
            var listView = document.querySelector(".tracklist").winControl;
            this.models = new WinJS.Binding.List(this.sampleItems);
            this.models.push(this.paginationItem);
            WinJS.UI.setOptions(listView, {
                itemDataSource: this.models.dataSource,
                itemTemplate: this.itemTemplateFunction
            });
        }
    });
})();

Voici le résultat dans l’application:

 

Lire la suite

Windows 8 – Utiliser un template différent selon l’élément affiché dans une ListView

Dans un des articles précédents, on a vu comment alimenter un composant « ListView » Windows 8, depuis un appel XHR:

Windows Metro – Clouder! (8) – Chargement des commentaires depuis l’API SoundCloud

Pour cela, on assigne la propriété « itemDataSource » que l’on va lier à la propriété « dataSource » d’un objet de type WinJS.Binding.List. Un objet de type List est une sorte de tableau qui supporte le Binding sur une liste (liaison dynamique entre la vue et le model). Cette List peut donc contenir différents types d’objets. Même si cela n’est pas conseillé, vous pouvez mélanger des choux et des carottes.

On a ensuite vu que pour effectuer le rendu d’un élément de ListView, on utilisait un Template, sous la forme d’un bout de HTML avec l’option itemTemplate à laquelle on donne un sélecteur DOM:

itemTemplate: select('.commentTemplate')

Seulement, dans ce cas-là vous allez faire le rendu de tous les items avec le même template. Si vous avez des objets utilisateurs et des objets playlist dans votre List, vous voudrez représenter chaque objet différemment.

Simple, si vous réalisez votre application en C#

JavaScript n’est pas la seule possibilité pour réaliser une application Windows 8 Metro. Vous pouvez aussi la réaliser en C# / XAML. Dans ce cas-là, vous avez une propriété « ItemTemplateSelector » sur « GridView » qui prend comme valeur une fonction qui va renvoyer le template à utiliser. Tout est détaillé dans ce très bon tutorial:

Windows 8 – Appliquer un template en fonction des items d’une liste

Pour les flexeurs, cela ressemble beaucoup à une itemRendererFunction :)

Moins simple, si vous réalisez votre application en HTML/JS

Il se peut aussi que comme moi, vous ayez choisi de la faire en HTML. Si vous regardez la documentation de ListView, vous verrez que cette propriété n’est pas implémentée:

WinJS.UI.ListView object

La solution est en fait plus complexe que cela. Pour trouver une solution acceptable, j’ai du retourner WinJS, et notamment le mécanisme de rendu interne de ListView.

Réalisation de la solution en JavaScript

On prépare d’abord notre page HTML pour accueillir une ListView:

<section aria-label="Main content" role="main">
  <div class="tracklist" style="height:100%;" data-win-control="WinJS.UI.ListView"
data-win-options="{ selectionMode: 'none'}"></div>
</section>

Ensuite dans le code JS correspondant à notre page, on ajoute un jeu de données:

(function () {
    "use strict";

    WinJS.UI.Pages.define("/pages/home/home.html", {

        sampleItems : [{  title: "Item Title: 1", subtitle: "Item Subtitle: 1", kind: 0 },
            {  title: "Item Title: 2", subtitle: "Item Subtitle: 2", kind: 1 },
            {  title: "Item Title: 3", subtitle: "Item Subtitle: 3", kind: 2 },
            {  title: "Item Title: 4", subtitle: "Item Subtitle: 4", kind: 1 },
            {  title: "Item Title: 5", subtitle: "Item Subtitle: 5", kind: 2 }],

        models: null,

        ready: function (element, options) {
            var listView = document.querySelector(".tracklist").winControl;
            this.models = new WinJS.Binding.List(this.sampleItems);
            WinJS.UI.setOptions(listView, {
                itemDataSource: this.models.dataSource
            });
        }
    });
})();

Puis on va définir notre template HTML:

...
</head>
<body>
    <div class="templateKindZero" data-win-control="WinJS.Binding.Template">
        <div class="item-overlay">
            <h4 class="item-title" data-win-bind="textContent: title"></h4>
            <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
        </div>
    </div>

On pourrait assigner simplement notre template à la propriété « itemTemplate » de notre ListView:

            WinJS.UI.setOptions(listView, {
                itemDataSource: this.models.dataSource,
                itemTemplate: select('.templateKindZero')
            });

On obtient:

Maintenant, on va créer un autre template pour représenter chaque autre type d’élément. Bien sûr, vous pourrez être plus imaginatif que moi :).

    <div class="templateKindZero" data-win-control="WinJS.Binding.Template">
        <div class="item-overlay">
            <h4 class="item-title" data-win-bind="textContent: title"></h4>
            <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
        </div>
    </div>
    <div class="templateKindOne" data-win-control="WinJS.Binding.Template">
        <div class="item-overlay" style="background-color:red;">
            <h3 class="item-title" data-win-bind="textContent: title"></h3>
            <h5 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h5>
        </div>
    </div>
   <div class="templateKindTwo" data-win-control="WinJS.Binding.Template">
        <div class="item-overlay" style="background-color:blue;">
            <h2 class="item-title" data-win-bind="textContent: title"></h2>
            <h4 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h4>
        </div>
    </div>

On a simplement des couleurs de fond différentes et des tailles de font différentes.

En fait, au lieu d’assigner un sélecteur DOM à la propriété « itemTemplate », on va assigner une fonction. Cette fonction a la signature suivante:

object renderItem(itemPromise, recycledElement)

Et doit renvoyer soit

  • un élément DOM
  • un objet contenant une propriété « element » de type élément DOM et un propriété « renderComplete » de type Promise.

Commençons par assigner notre fonction:

        itemTemplateFunction : function (itemPromise){

        },

        ready: function (element, options) {
            var listView = document.querySelector(".tracklist").winControl;
            this.models = new WinJS.Binding.List(this.sampleItems);
            WinJS.UI.setOptions(listView, {
                itemDataSource: this.models.dataSource,
                itemTemplate: this.itemTemplateFunction
            });
        }

On pourrait construire directement les éléments DOM qui vont bien, par exemple:

function itemTemplateFunction(itemPromise) {

       return itemPromise.then(function (item) {
           var div = document.createElement("div");

           var img = document.createElement("img");
           img.src = item.data.picture;
           img.alt = item.data.title;
           div.appendChild(img);

           var childDiv = document.createElement("div");

           var title = document.createElement("h4");
           title.innerText = item.data.title;
           childDiv.appendChild(title);

           var desc = document.createElement("h6");
           desc.innerText = item.data.text;
           childDiv.appendChild(desc);

           div.appendChild(childDiv);
           
           return div;
       });
    };

C’est l’exemple qui est donné dans l’exemple de la documentation ListView. Simple si vous avez un DOM qui tient en quelques lignes. Moins si vous avez un DOM complexe. La notation HTML est tout de même plus lisible et plus cohérente pour votre programme.

La solution est en fait assez simple, quand on a compris comment se faisait le rendu des templates en interne dans WinJS. Un composant de type WinJS.Binding.Template contient en fait une méthode « render » qui va prendre en premier argument, un objet de données (l’objet à binder au template) et en second argument, l’élément DOM qui va venir contenir le template. Cet élément DOM peut être un simple DIV, cela n’a pas d’importance.

Voici donc la fonction à adopter. Bien sûr, celle-ci devra être adaptée selon votre propre logique métier.

itemTemplateFunction : function (itemPromise, recycledElement) {
    return itemPromise.then(function (currentItem) {
        var itemData = currentItem.data;
        var tpl = null;
        switch (itemData.kind) {
            case 0:
                tpl = document.querySelector(".templateKindZero");
                break;
            case 1:
                tpl = document.querySelector(".templateKindOne");
                break;
            case 2:
                tpl = document.querySelector(".templateKindTwo");
                break;
            default:
                // do something, really
                break;
        }
        // dumb div to contain the result
        var result = document.createElement("div");
        return tpl.winControl.render(itemData, result);
    })
},

Et voici le résultat:

Cela se chevauche car je n’ai pas précisé la talle des cases de mon tableau (qui du coup sont variables). Mais ce sera pour un autre tutorial :). J’utilise par exemple cette technique dans Clouder! pour Windows 8 pour le système de pagination:

Le dernier item est différent et sert à charger une nouvelle page. On verra comment faire dans un prochain tutorial de html5-tutorial.fr !