Comment créer un petit lecteur RSS pour Windows 8 Metro en HTML5 en 30 min (partie 1/2)

Nous allons voir à travers ces 2 tutoriaux comment, en partant de zéro, réaliser un petit lecteur de flux RSS avec HTML5, CSS3 et WinJS, le framework JavaScript de Microsoft pour Windows 8 Metro. Nous essaierons également de suivre au mieux les règles de design Metro en faisant appel à l’outil Expression Blend 5. Si vous vous débrouillez bien, vous devriez pouvoir jouer ces 2 articles en 30 minutes.

Ce 1er article vous permettra de créer la page d’accueil utilisant un contrôle de type liste affichant l’ensemble des derniers articles de blog publiés. Le 2ème s’occupera de créer la vue de détail affichée lorsque vous cliquerez sur l’un des éléments de la liste. Vous trouverez à la fin 2 petites vidéos déroulant les étapes que vous pouvez suivre en lisant cet article ainsi que le code source de solution. Voyez-les comme un complément pratique pour éventuellement éclaircir certaines zones d’ombres.

Prérequis : pour pouvoir suivre ces tutoriaux, vous devez au préalable :

1 – Avoir téléchargé et installé la version Release Preview de Windows 8 sur votre machine : https://preview.windows.com
2 – Avoir téléchargé et installé les outils Visual Studio 2012 RC Express pour Windows 8 : https://msdn.microsoft.com/en-us/windows/apps/br229516

Note : si vous avez un Mac, Windows 8 s’installe très bien via BootCamp ou dans une machine virtuelle gérée par Parallels par exemple

Note 2 : cet article a été mis à jour le 04/06/2012 pour tenir compte des changements présents dans l’interface et le code des versions Windows 8 Release Preview par rapport à la Consumer Preview. D’une manière générale, vous trouverez un document complet sur les fameux “breaking changes” entre CP et RP ici : document de breaking changes. Il devrait vous aider à migrer vos applications. Dans notre cas, c’est uniquement le code de parsing des éléments retournés par la requête XHR qui a été impacté . Nous avons changé les sélecteurs pour passer des requêtes XPath via des selectNodes à des sélecteurs CSS via querySelectorAll.

Au menu de cet 1er article :

- Etape 1 : création du projet vide
- Etape 2 : création du squelette HTML et CSS de notre page principale
- Etape 3 : premier contact avec Blend
- Etape 4 : charger les données via XHR et les associer au contrôle ListView
- Etape 5 : mise en place d’un template et revue du design sous Blend
- Etape 6 : vidéos déroulant toutes les étapes précédentes

Etape 1 : création du projet vide

La première chose à faire est de lancer Visual Studio 2012 et de créer un nouveau projet de type JavaScript Metro en faisant « File -> New Project » :

image

Nommez-le « SimpleChannel9Reader » car nous allons récupérer le flux RSS de la section Coding4Fun de Channel 9 disponible ici : https://channel9.msdn.com/coding4fun/articles/RSS

Etape 2 : création du squelette HTML et CSS de notre page principale

Rendez-vous dans le fichier « default.html » qui représente la page qui sera affichée au démarrage de notre application. A la place du morceau HTML suivant :

 <p>Content goes here</p> 

Insérez celui-là :

 <div id="main">
    <header id="banner">
        <button id="backbutton" class="win-backbutton">
        </button>
        <h1 id="maintitle" class="win-title">Welcome to Channel 9!</h1>
    </header>
    <section id="content">
    </section>
</div>

Nous avons un conteneur global avec l’id « main » contenant 2 sous-conteneurs nommés « banner » et « content ». Le header sera affiché en haut de page et le contenu présent dans la section sera affiché juste en dessous.

Ajoutons un peu de CSS à tout cela en nous rendant dans le fichier « default.css » présent dans le répertoire « css ». Vous verrez qu’il y a déjà un peu de CSS fournit par défaut pour vous indiquer les différentes vues disponibles dans une application Windows 8 gérées via des Media Queries.

Nous nous intéresserons dans ces 2 articles qu’à la vue plein écran donc l’état « fullscreen-landscape ». Insérez dans cette partie le CSS suivant :

 #main {
    width: 100%;
    height: 100%;
}

#banner {
    width: 100%;
    height: 100%;
}

#backbutton {
}

#maintitle {
}

#content {
    width: 100%;
    height: 100%;
}

Indiquant tout simplement que l’on souhaite prendre l’ensemble de la taille disponible pour nos 3 conteneurs principaux.

Exécutez votre application en appuyant sur la touche F5 ou en cliquant sur la touche suivante :

image

Logiquement, vous devriez avoir quelque chose comme :

clip_image005

On voit ici un problème évident de design : le bouton de retour et le texte ne sont pas du tout alignés. Résolvons cela à l’aide de Blend 5.

Etape 3 : premier contact avec Blend

Lancez Blend et allez chercher votre projet SimpleChannel9Reader sur votre système de fichiers pour obtenir cela :

image

L’idée ici est de créer 2 grilles. Une 1ère qui va contenir 1 colonne sur toute la largeur et 2 lignes pour le conteneur global et une 2ème qui va contenir 1 ligne et 2 colonnes pour le bouton back et le titre.

Commençons par sélectionner dans la section « Live DOM » l’élément « main » :

clip_image008

Rendez-vous dans la partie « CSS Properties », sélectionnez le style #main puis dans la partie « Layout » et basculez le display en « -ms-grid » :

clip_image009

Nous allons utiliser ici la spécification CSS Grid Layout actuellement uniquement supportée par IE10 mais arrivant bientôt dans les autres navigateurs. Si vous souhaitez en savoir davantage sur le type de layout supporté en mode Metro, vous pouvez lire cet article : Choosing a CSS3 layout for your app.

Si vous souhaitez simplement en savoir davantage sur CSS3 Grid, n’hésitez pas à lire l’article de Raphael Goetter d’Alsacréations ici : CSS3 Grid Layout.

Maintenant que nous avons basculé le mode d’affichage en grille, il faut définir la structure de la grille. Pour cela, rendez-vous naturellement dans la partie « Grid » et déclarer la grille suivante :

clip_image010

Nous aurons donc 1 seule colonne qui prendra toute la largeur de l’écran disponible (quelle que soit la résolution) et 2 lignes. La 1ère ligne aura une taille en « dur » de 132px de haut et la suivante prendra tout le reste de l’espace disponible. Cela est alors reflété dans le designer de Blend :

clip_image012

Maintenant, nous allons placer l’élément « content » dans la 2ème ligne. Sélectionnez-le dans le « Live DOM », puis après avoir choisi la règle #content et rendez-vous dans sa partie « Grid ». Mettez la propriété –ms-grid-row à 2. Vous pouvez éventuellement positionner l’élément « banner » à la ligne 1 mais sinon cela est fait par défaut si vous ne précisez rien.

Nous allons maintenant découper notre 1ère ligne en 2 colonnes de manières à mettre les choses à leur place. Sélectionnez l’élément « banner » et basculer son display en « -ms-grid » puis définissez 1 ligne en 1fr et 2 colonnes de 120px et 1fr :

clip_image013

Placez l’élément « maintitle » en colonne 2 et centrez le verticalement grâce à la propriété « -ms-grid-row-align » à « center » :

clip_image014

Sélectionnez l’élément « backbutton » et dans sa partie « Layout », mettez 54px de marge en haut et 40px de marge vers la gauche. Si vous ne vous êtes pas planté, vous devriez avoir maintenant cela sur la surface de design :

clip_image016

Sauvegardez l’ensemble via “File -> Save All” puis retournez sous Visual Studio. Rendez-vous dans « default.css » et vous observerez que Blend a généré du CSS « propre » :

 @media screen and (-ms-view-state: fullscreen-landscape)
{

    #main {
        width: 100%;
        height: 100%;
        display: -ms-grid;
        -ms-grid-columns: 1fr;
        -ms-grid-rows: 132px 1fr;
    }

    #banner {
        width: 100%;
        height: 100%;
        display: -ms-grid;
        -ms-grid-columns: 120px 1fr;
        -ms-grid-rows: 1fr;
    }

    #backbutton {
        margin-top: 54px;
        margin-left: 40px;
    }

    #maintitle {
        -ms-grid-column: 2;
        -ms-grid-row-align: center;
    }

    #content {
        width: 100%;
        height: 100%;
        -ms-grid-row: 2;
    }
}

Vérifiez que l’application fonctionne bien en faisant F5.

Etape 4 : charger les données via XHR et les associer au contrôle ListView

Bon pour l’instant, vous vous dîtes que c’est bien gentil mais que vous aimeriez rentrer dans le vif du sujet.

La 1ère chose à faire est d’insérer un contrôle qui va être chargé d’afficher de petites vignettes sur la page d’accueil contenant les images des articles de blogs qui vont être remontés. Pour cela, nous allons utiliser WinJS.

La librairie WinJS ou “Microsoft Windows Library for JavaScript SDK” est un framework conçu pour aider les développeurs JavaScript à intégrer plus simplement l’expérience Windows 8 Metro. Elle fournit ainsi un ensemble de contrôles Metro, un moteur de templating, un moteur de binding, une gestion de l’asynchronisme via l’implémentation du Promise, des helpers pour gérer des Namespaces, etc.

Par exemple, si vous souhaitez en savoir davantage sur la partie contrôles, rendez-vous ici : Quickstart: adding WinJS controls and styles

Dans les projets Windows 8 Metro, vous retrouverez cette librairie  dans les références du projet dans le “Solution Explorer” :

image

Vous y retrouverez donc les feuilles de styles par défaut avec les 2 thèmes dark et light fournis ainsi que le code JavaScript. N’hésitez pas à aller y jeter un œil pour voir comment nous avons fabriqué l’ensemble. Cela peut toujours être utile en complément de la documentation MSDN.

Dans notre cas, nous allons utilisé le contrôle ListView qui utilise une structure en grille pour afficher une liste d’éléments.

Pour cela, rendez-vous dans le fichier « default.html » et dans la partie section, tapez ce morceau d’HTML :

 <div id="articlelist" data-win-control="WinJS.UI.ListView"></div> 

Pour l’instant, cela n’est qu’un « bête » div comme un autre. Par contre, il est marqué d’un attribut data-win-control qui indique que l’on souhaite utiliser la librairie WinJS pour transformer ce div en contrôle de type ListView.

La magie ne s’opère uniquement que grâce à une ligne de JavaScript particulière que l’on retrouve dans « default.js » et se trouve être celle-ci :

 WinJS.UI.processAll();

Cette opération, traitée de manière asynchrone, s’occupe de parcourir tout le DOM à la recherche de div marqués des attributs data-win-control pour les transformer en réel contrôle WinJS implémentant l’expérience Metro. Si vous supprimez par mégarde cette ligne, vous rendrez à nouveau « bête » tous vos div.

Ok, maintenant il faut l’alimenter en données cette ListView. Pour cela, dans la fonction associée à l’évènement « onactivated », ajoutez ce code au-dessus de la ligne processAll() :

 articlesList = new WinJS.Binding.List();
var publicMembers = { ItemList: articlesList };
WinJS.Namespace.define("C9Data", publicMembers);

Du coup, déclarez en haut de la fonction la variable « articlesList », juste en dessous de la variable « app » par exemple.

Nous déclarons ici un type Binding.List() à utiliser pour binder des données aux contrôles WinJS. Ce type a le bon goût de disposer de méthodes qui vous permettront d’ajouter des données au fil de l’eau et de laisser la magie du binding s’opérer pour rafraîchir la vue associée.

Par ailleurs, comme nous codons proprement en JavaScript, nous utilisons le « module pattern » avec une fonction JS anonyme auto-exécutée dans « default.js ». Il nous donc un moyen d’exposer des données publiques vers l’extérieur. Pour cela, nous utilisons le concept de Namespace avec un helper issu de WinJS nous permettant de facilement définir ce que nous souhaitons exposer. Dans le cas ci-dessus, nous aurons donc un objet nommé « C9Data » qui sera visible avec une propriété « ItemList » contenant nos futurs éléments à afficher.

Il nous faut maintenant une fonction pour aller chercher les données du flux RSS, les analyser, créer des objets JS à la volée et les pousser dans cette fameuse liste de binding. Voici la fonction que je vous propose pour cela :

 function downloadC9BlogFeed() {
    WinJS.xhr({ url: "https://channel9.msdn.com/coding4fun/articles/RSS" }).then(function (rss) {

    });
}

Ce code effectue une requête XmlHttpRequest vers l’url spécifiée de manière asynchrone. Le code indiqué dans la Promise (via le .then()) sera donc exécuté uniquement qu’une fois que la requête aura reçu les données. C’est à ce moment-là que l’on peut les filtrer via ce code à insérer dans la fonction anonyme :

 var items = rss.responseXML.querySelectorAll("item");

for (var n = 0; n < items.length; n++) {
    var article = {};
    article.title = items[n].querySelector("title").textContent;
    var thumbs = items[n].querySelectorAll("thumbnail");
    if (thumbs.length > 1) {
        article.thumbnail = thumbs[1].attributes.getNamedItem("url").textContent;
        article.content = items[n].textContent;
        articlesList.push(article);
    }
}

Ce code est je pense auto-explicite. Il sélectionne les nœuds « item » et récupère les propriétés qui l’intéressent pour les mapper sur un objet « article » instancié à la volée sur ses propriétés « title », « thumbs » & « content ». Notez bien le nom de ces propriétés, on les retrouvera plus tard. Le code finit par ajouter ce nouvel objet à la collection de binding.

Il faut maintenant indiquer que l’on souhaite exécuter le code au démarrage de l’application. Nous allons le faire une fois que l’analyse du DOM aura été faite pour fabriquer les contrôles WinJS. Du coup, utilisez cette ligne de code :

 WinJS.UI.processAll().then(downloadC9BlogFeed);

Il ne reste plus qu’une chose à faire : c’est d’indiquer au contrôle quelle est sa source de données. Pour cela, rendez-vous dans le HTML et modifiez le div associé à la ListView pour changer ses options. Vous obtiendrez alors cela :

 <div id="articlelist" data-win-control="WinJS.UI.ListView" 
     data-win-options="{ itemDataSource: C9Data.ItemList.dataSource }">
</div> 

Pour finir, il faut indiquer un minimum la manière dont on souhaite afficher notre contrôle. Pour cela, rendez-vous bien sûr dans « default.css » et ajoutez ces 2 règles :

 #articlelist {
    width: 100%;
    height: 100%;
}

#articlelist .win-item {
    width: 150px;
    height: 150px;
}

On indique que notre contrôle ListView devra prendre toute la place disponible et que chacun de ses éléments (via la classe .win-item) devront faire 150 par 150 pixels.

Exécutez l’ensemble en faisant F5. Vous devriez avoir quelque chose d’aussi moche que cela :

clip_image018

C’est normal, ne vous inquiétez pas ! Sourire

Bon, il nous reste encore un peu de boulot. Mais on peut déjà voir que le binding fonctionne et que le contrôle fonctionne bien à la souris et au touch. Par ailleurs, le contrôle s’adapte déjà à la résolution actuelle de votre écran. Vous n’aurez donc pas forcément la même disposition que la copie d’écran précédente.

Etape 5 : mise en place d’un template et revue du design sous Blend

Il faut maintenant préciser au contrôle la manière de dessiner chacun de ses éléments. Cela se fait via un mécanisme dit de templating. Un template n’est à nouveau qu’un morceau de HTML décoré par les attributs WinJS.

Rendez-vous dans « default.html » et ajoutez ce morceau d’HTML tout en haut juste au-dessus du « main » :

 <div id="C9ItemTemplate" data-win-control="WinJS.Binding.Template" style="display: none;">
    <div class="listItemTemplate">
        <div class="listItemImage">
            <img data-win-bind="src: thumbnail" />
        </div>
        <div class="listItemTitle" data-win-bind="innerText: title">
        </div>
    </div>
</div>

On voit bien que c’est un template car il est décoré de la valeur « WinJS.Binding.Template » ce qui permettra à WinJS de savoir quoi en faire suite à l’exécution du processAll() . Ensuite, le reste n’est que du HTML standard avec malgré tout une autre particularité : les expressions de binding. Pour savoir comment mapper les propriétés des objets JS actuellement en mémoire aux contrôles HTML, on passe par l’attribut « data-win-bind » puis on précise une expression de binding de la manière suivante : « nom_de_l’attribut_html : nom_de_la_propriété_objet_JS ».

Il nous faut maintenant dire au contrôle WinJS de ne plus de dessiner avec son template par défaut mais avec celui que nous venons juste de définir en changeant simplement ses options :

 <div id="articlelist" data-win-control="WinJS.UI.ListView" 
     data-win-options="{ itemDataSource: C9Data.ItemList.dataSource, itemTemplate: C9ItemTemplate }"> 
</div> 

Si vous exécutez pour voir le résultat à ce moment, vous devriez avoir cela :

image

C’est mieux mais ce n’est pas encore cela. Il va falloir revoir le design sous Blend.

Bah allez, c’est parti. Retournez sous Blend qui va vous demander de recharger les fichiers suite aux modifications que vous avez effectuées sous Visual Studio pour obtenir cela :

image

Rien ne vous choque ? On voit ici le même résultat que si vous faîtes F5 depuis Visual Studio. Cela signifie que Blend 5 exécute en « live » votre application au sein du designer ! En voilà une bonne nouvelle.

Du coup, on va pouvoir travailler sur le vrai jeu de données finales sans être obligé de faire ce que l’on appelle communément du « mocking ». C’est un des avantages de JavaScript et de son côté interprété et dynamique. Blend a exécuté le code qui fait la requête XHR et construit les objets WinJS.

Sous « default.css », on va commencer par ajouter 2 règles CSS. Pour cela, cliquer sur le bouton + sur la media query principale :

clip_image023

Et ajoutez les sélecteurs suivants :

.listItemTemplate et .listItemTemplate img

Sélectionnez la règle #articlelist .win-item qui sélectionne chacun des éléments de la ListView disposant de l’ID « articlelist ».

Changez la taille de ces éléments pour les faire passer de 150px par 150px à 250px par 250px en vous rendant dans la partie « Sizing » sur le panneau de droite.

Il faut ensuite forcer un rafraichissement de la zone de design en cliquant sur le bouton adéquat :

clip_image024

Et voici le résultat :

image

On va redimensionner les images du template. Pour cela, sélectionnez le pointeur « Direction Selection » et cliquez sur une des images :

image

Vous pouvez alors voir la liste des règles actuellement appliquées dans la section « Applied Rules ». Cliquez sur « .listItemTemplate img » puis redimensionnez à la souris l’image que vous aviez à l’origine sélectionnée. Vous voyez alors que toutes les autres images répondant au même sélecteur sont naturellement impactées en temps réel.

Finalement, rendez-vous dans la section « Sizing » et fixez la taille suivante : 234px de large par 165px de haut.

Pour améliorer un peu plus le design, il nous reste à aérer un peu l’espace entre les éléments et aligner correctement notre ListView avec le titre.

Cliquez sur le sélecteur « .listItemTemplate » puis dans la section « Layout », commencez par cliquez sur l’icône « Lock » tout à droite de la zone « Margin ». Prenez ensuite n’importe quelle marge et entrez 8px.

Pour finir, pour décaler la grille de la ListView et l’aligner avec le titre, il faut la bouger de 120px – 8px de marge des éléments que l’on vient de mettre en place.

Ajoutez donc un nouveau sélecteur en cliquant sur + et nommez le « .win-surface » puis mettez une marge à gauche de 112px.

Revenez sous Visual Studio, acceptez les changements puis faîtes F5. Voici ce que vous devriez désormais obtenir :

image

Etape 6 : vidéos déroulant toutes les étapes précédentes

Voici 2 petites vidéos vous montrant comment suivre toutes les étapes décrites précédemment.

Note : les vidéos sont toujours celles de la Consumer Preview. Je vais les refaire aussi vite que possible en Release Preview.

La 1ère correspond aux étapes 1, 2 et 3 :

Poster Image

Download Video: MP4, WebM, HTML5 Video Player by VideoJS

La 2ème correspond aux étapes 4 et 5 :

Poster Image

Download Video: MP4, WebM, HTML5 Video Player by VideoJS

Vous pouvez télécharger le code source de la solution associée à ce 1er article ici : Simple Channel9 Reader Article1

Voilà, on a déjà bien avancé. Il nous reste maintenant à afficher le détail de l’article, à continuer de découvrir la puissance de l’outil Blend ainsi que de nouvelles choses apportées par CSS3.

Rendez-vous dans le second article pour cela !

David