CentraleSupélecDépartement informatique
Gâteau du Glouton
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
TD 4 — Structuration des données

Dans la séance précédante, vous avez stocké les les valeurs des données affichées par votre application météo dans la variable javascript valeursCourantes locale à la page. Les unités à utiliser pour l'affichage étaient stockées dans la variable unitesChoisies. La page était ensuite modifiée à l'aide d'une fonction afficher() qui utilisait cette variable.

Dans cette séance, nous allons voir comment récupérer ces valeurs depuis un fichier externe, qui, dans cette séance sera local à votre ordinateur.

1 - Structuration de données en XML

Nous allons représenter les données à afficher avec un fichier XML. Pour fixer les choses, nous allons reprendre la température et la vitesse du vent. Pour les besoins de cet exercice, nous allons aussi mettre la pression et la couverture nuageuse.

Voici un schéma XML caractérisant le format de donnée que l'on souhaite utiliser:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:simpleType name="typeCels">
    <xs:restriction base="xs:integer">
      <xs:minInclusive value="-62"/>
      <xs:maxInclusive value="50"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="typeHPa">
    <xs:restriction base="xs:nonNegativeInteger">
      <xs:minInclusive value="900"/>
      <xs:maxInclusive value="1200"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:simpleType name="typeKmh">
    <xs:restriction base="xs:nonNegativeInteger">
      <xs:minInclusive value="0"/>
      <xs:maxInclusive value="222"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="meteo">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="vent" type="typeKmh" minOccurs="1" maxOccurs="1"/> 
        <xs:element name="pression" type="typeHPa" minOccurs="0" maxOccurs="1"/>
        <xs:element name="temp" type="typeCels" minOccurs="1" maxOccurs="1"/>
        <xs:element name="nuage" minOccurs="0" maxOccurs="1">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="description">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:enumeration value="soleil" />
                    <xs:enumeration value="couvert" />
                    <xs:enumeration value="apocalypse" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
            </xs:sequence>
            <xs:attribute name="visibilite" type="dist"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="dist">
    <xs:restriction base="xs:decimal">
      <xs:minInclusive value="0"/>
      <xs:maxInclusive value="10"/>
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

À faire

  • Lisez ce fichier et tachez de comprendre ce qui se passe.
  • Écrivez un fichier XML qui valide ce schéma XSD
  • Si vous pouvez utiliser des outils de validation offline, un outil en ligne est très pratique.
  • Quelles limitations a-t-on posé pour les types ? Combien sont-elles contraignantes ?
    • Sont-elles adaptés pour le désert d'El Azizia ?
    • Le Pole Sud ?
    • La Lune ?
    • Un ouragan ?
  • Certains types (comme "typeKmh") ne sont utilisés qu'une seule fois: integrez-les à la seule balise element ou attribute qui les utilise.
  • Faites une DTD qui correspond au mieux au fichier XSD.
    • Quelles sont les limitations que vous rencontrez ?

2 - Accès à des données XML depuis JavaScript

On peut charger un fichier en JavaScript avec jQuery en utilisant la fonction $.get. Cette fonction est asynchrone : elle rend la main tout de suite et commence à charger le fichier en arrière-plan. Lorsque le fichier est entièrement chargé, elle appelle une fonction de callback qui lui a été transmise en argument. À cette fonction, elle donne en argument le fichier chargé.

Syntaxe générale :

$.get(url, fonction_de_callback, format);

Par exemple :

$.get("monSuperFichier.xml", function(xmlDocument) {
    // Cette fonction est appelée lorsque le document XML est chargé
    // Il reste à faire quelque-chose du document...
    console.log(xmlDocument)
}, "xml");

La nature de l'objet que reçoit la fonction de callback dépend du format de fichier indiqué :

Format indiquéNature de l'objet reçu
"xml"Nœud DOM du document, après analyse du fichier XML
"json"Objet JavaScript résultant de l'analyse du fichier JSON

Attention

La fonction $.get ci-dessus fonctionne très bien via le réseau. En revanche, la politique de sécurité de certains navigateurs interdisent de charger un fichier local (URL commençant par "file:") depuis un autre fichier local.

Sur Firefox vous ne devriez pas avoir de problème mais avec Chrome et Safari le chargement dynamique de fichiers locaux est par défaut interdit

  • Avec Safari: dans le menu de développement, vous pouvez désactiver les "Cross Orgin Restrictions"
  • Avec Chrome: Il faut utiliser le programme avec l'option –allow-file-access-from-files.

À faire

Dans le fichier javascript associé à la page HTML principale de votre application mété, ajouter une fonction qui charge votre fichier XML et mets à jour la variable globale valeursCourantes . Vous pouvez partir des deux fichiers meteo.html et meteo.js ci-dessous. Ce sont essentiellement ceux du TD précédant.

  • Ajoutez une fonction litDonnees() définie en utilisant le template:
function litDonnees(){
 $.get("addresse-du-fichier.xml", function(xmlDocument) {
    // Fait quelque chose avec la variable xmlDocument
    // Rappel: il s'agit d'un objet DOM !
 });
}
  • Faitez en sorte qu'elle mette à jour la variable valeursCourantes, et qu'elle appelle la fonction affichez()
    • N'hésitez pas à utiliser la fonction .find() de la librairie jQuery
  • Testez là :
    • Changez le contenu du fichier XML
    • Lancez la fonction et vérifiez que les choses changent

3 - Accès à des données JSON depuis JavaScript

  • On fournit la donnée JSON suivante, à mettre dans un fichier pref.json et qui contient les préférences qui sont sensées correspondre au fichier config.html, que nous traiterons dass le TD 6. Il s'agit donc des valeurs à placer dans la variable globale unitesChoisies, et de l'état d'affichage des différents champs.
{
 "temp": {
    "unite": 0,
    "state": 1
    },
 "pression": {
    "unite": 0,
    "state": 0
 },
 "nuage": {
    "visib": {
       "unite": 1,
       "state": 1
    },
    "status": 1
 },
 "vent": {
    "unite": 0,
    "state": 1
 }
}

L'objectif est de récupérer ce fichier à l'aide de la fonction .get, de changer les valeurs de la variable globale unitesChoisies et d'afficher ou de cacher ce qu'il faut.

  • Si state (ou status) vaut 0, on veut cacher le champ en question, le montrer sinon.
  • Le champ unite donne la valeur a mettre dans unitesChoisies

À faire

  • Écrivez une fonction litConfig() qui lit le fichier json, récupère les données, mets à jour la variable globale unitesChoisies et affiche/cache les champs ad-hoc.
  • Testez votre fonction dans la console:
    • Modifiez le fichier JSON avec un éditeur de texte
    • Lancez votre fonction
    • Affichez la variable et vérifiez que cela s'est mis à jour

4 - Utilisons les deux fichiers à la fois

L'idée maintenant est d'avoir les deux fichiers en même temps. Problème : on veut s'assurer que la variable unitesChoisies a bien été mise à jour avant d'envisager d'appeler la fonction affichage(). Mettre cote à cote les deux appels à .get ne résoudra pas le problème: chacun des appels est asynchrone. C'est le propre de la programmation événementielle: Il faut donc forcer la séquencialité des choses.

Une manière de faire est de donner une fonction de callback à chacune des fonctions litConfig() et litDonnees(). Par exemple, litDonnees() sera maintenant définie comme suit:

function litDonnees(callback){
 $.get("addresse-du-fichier.xml", function(xmlDocument) {
    // Fait quelque chose avec la variable xmlDocument
    // Rappel: il s'agit d'un objet DOM !

    ....

    // On est à la fin, on a fini, on peut faire autre chose: on
    // appelle la fonction qui dit quoi faire après
    callback()
 });
}

Pour s'assurer que les fichiers sont lus et traités dans le bon sens, il suffit alors de faire:

litConfig(function (){
   litDonnees(function () {
      affichage()
   });
})

Il se passe les choses suivantes:

  1. litConfig commence
  2. .get est appelé: il fait simplement lancer une demande d'appel de fichier
  3. c'est très rapide, il s'arrète tout de suite
  4. la fonction litConfig termine donc
  5. la demande d'appel de fichier finit par aboutir: la fonction de callback est appelée dessus
  6. cette fonction met à jour unitesChoisies et appelle litDonnees
  7. .get est de nouveau appelé: il fait encore simplement lancer une demande d'appel de fichier
  8. c'est toujours très rapide, il s'arrète tout de suite
  9. la fonction litDonnees termine donc
  10. la demande d'appel de fichier finit par aboutir: la fonction de callback est appelée dessus
  11. celle-ci met à jour valeursCourantes et appelle sa fonction de callback
  12. celle-ci appelle affichage

À faire

Testez cela !

Conclusion

On sait maintenant

  • Lire et traiter du XML et du JSON
  • Faire de la programmation évenementielle

La prochaine fois, nous iront chercher avec AJAX les données sur internet... Par contre, les préférences devront attendre le TD 6.

Appendix

Voici un fichier HTML et un fichier JS (en principe) fonctionnel pour ce travailler sur ce TD. Bien sûr, vous pouvez utiliser les votres si vous voulez !

Le fichier meteo.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Météo</title>
    <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
    <script type="text/javascript" src="meteo.js"></script>
  </head>
  <body>
    <header>
      <h1>La météo de Gif-Sur-Yvette</h1>
    </header>
    <div id="menu">
      <ul>
        <li><a href="config.html">Configuration</a></li>
        <li><a href="contact.html">Contact</a></li>
      </ul>
    </div>
    <div id="main">
      <section>
        <h2>La ville</h2>
        <p>Gif-sur-Yvette (prononcé [ ʒif syʁ ivɛt̪] ) est une commune
          française située à vingt-quatre kilomètres au sud-ouest de
          Paris dans le département de l’Essonne en région
          Île-de-France. Elle est le chef-lieu du canton de
          Gif-sur-Yvette. Du village celte installé sur le plateau du
          Moulon au VIe siècle av. J.-C. à l’abbaye bénédictine fondée
          au XIIe siècle, Gif fut très tôt un site agricole et
          spirituel important du Hurepoix. Implantée dans une vallée
          de Chevreuse verdoyante et reliée dès le milieu du XIXe
          siècle à la capitale par le chemin de fer, la commune devint
          un lieu de villégiature pour les bourgeois et les artistes,
          tels Juliette Adam, Fernand Léger ou le duc de Windsor. La
          seconde moitié du XXe siècle vit l’évolution rapide de la
          commune avec l’implantation d’importants centres de
          recherche et de formation scientifique, au nombre desquels
          le CNRS ou Supélec, et le lotissement « à l’américaine » du
          plateau sud avec la création du nouveau quartier de Chevry,
          multipliant par trois sa population. Celle-ci devrait encore
          s'accroître dans les années à venir avec la réalisation
          d'une opération d'aménagement dans le quartier de Moulon,
          qui prévoit la construction de logements et de grandes
          écoles dans le cadre du projet de pôle technologique
          Paris-Saclay. Ses habitants sont appelés les Giffois. </p>
      </section>  
      <section>
        <h2>Une carte</h2>
        <img width="200px" src="http://www.cartes-de-france.fr/carte/carte_de_france_relief.gif">
      </section>
      <section>
        <h2>Les données actuelles</h2>
        <ul>
          <li class="contenant" id="temp">Température : <span class="contenu">12</span> <span class="unite">C</span></li>
          <li class="contenant" id="nuage">Couverture nuageuse : <span class="contenu">gris foncé</span></li>
          <li class="contenant" id="vent">Vent : <span class="contenu">40</span> <span class="unite">km/h</span></li>
          <li class="contenant" id="visib">Visibilité : <span class="contenu">5</span> <span class="unite">mètres</span></li>
          <li class="contenant" id="pression">Pression : <span class="contenu">4</span> <span class="unite">bars</span></li>
        </ul>
      </section>
    </div>
    <footer>
      <address>Gateau du Glouton Inc. Corp.</address>
    </footer>
  </body>
</html>

Le fichier meteo.js:

var unitesPossibles = {
    temp : ["C", "F", "K"],
    vent : ["km/h", "m/s"],
    visib : ["km", "m"],
    pression : ["hPa", "bar"]
}

var unitesChoisies = {
    temp : 0,
    vent : 0,
    visib : 0,
    pression : 0
}

var valeursCourantes = {
    temp : -42,
    vent : 100,
    visib : 5,
    pression : 1000,
    nuage : "gris"
}

function valeurVent() {
    if (unitesChoisies.vent == 0) {
        return valeursCourantes.vent;
    } else {
        return valeursCourantes.vent / 3.6;
    }
}

function valeurPression() {
    if (unitesChoisies.pression == 0) {
        return valeursCourantes.pression;
    } else {
        return valeursCourantes.pression / 1000;
    }
}

function valeurTemp() {
    if (unitesChoisies.temp == 0) {
        return valeursCourantes.temp;
    } else if (unitesChoisies.temp == 1) {
        return (valeursCourantes.temp * 9 / 5) + 32;
    } else {
        return valeursCourantes.temp + 273.15;
    }
}

function valeurVisib() {
    if (unitesChoisies.visib == 0) {
        return valeursCourantes.visib;
    } else {
        return valeursCourantes.visib / 1000;
    }
}


function afficher() {
    $("#vent").find(".contenu").text(valeurVent());
    $("#vent").find(".unite").text(unitesPossibles.vent[unitesChoisies.vent]);

    $("#pression").find(".contenu").text(valeurPression());
    $("#pression").find(".unite").text(unitesPossibles.pression[unitesChoisies.pression]);

    $("#temp").find(".contenu").text(valeurTemp());
    $("#temp").find(".unite").text(unitesPossibles.temp[unitesChoisies.temp]);    

    $("#visib").find(".contenu").text(valeurVisib());
    $("#visib").find(".unite").text(unitesPossibles.visib[unitesChoisies.visib]);    

    $("#nuage").find(".contenu").text(valeursCourantes.nuage);
}


function cacher() {
    $(".contenant").hide();
}

function montrer(liste) {
    for(var i = 0; i < liste.length; ++i) {
        $("#" + liste[i]).show();
    }
}