Jusqu’à maintenant nous avons utilisé Flask pour générer le contenu de la page à chaque requête (notamment grâce aux templates). Cependant, une fois la page envoyée à l’utilisateur, Flask n’a plus aucun contrôle sur son contenu. Il faut donc utiliser une autre technologie pour rendre la page dynamique, c’est-à-dire pour que son contenue évolue au cours du temps ou en réponse à des actions de l’utilisateur.

Tous les navigateurs modernes courants supportent le langage de programmation Javascript. Ce dernier permet de rendre une page dynamique car il est possible d’exécuter du code au chargement de la page, de manière périodique ou en réponse à des événements. Nous utiliserons aussi la bibliothèque Javascript jQuery qui rend beaucoup plus intuitive et sûre la programmation en Javascript.

Au cours de cette session, nous développerons progressivement une application web permettant d’afficher en temps réel les prochains passages aux arrêts de transports en commun de la TAN grâce à l’API publique fournie par la TAN. Ce sera l’occasion de nous familiariser avec le format d’échange de données JSON. Notre application présentera aussi un thème clair et un thème sombre, avec la possibilité de basculer entre les deux.

  1. Préparatifs
    1. Fichiers CSS / Javascript à inclure pour notre projet
    2. L’API de la TAN
  2. Thème sombre : les bases de Javascript + jQuery
    1. Variables et fonctions en Javascript
    2. Accès au DOM avec jQuery
    3. Les événements
    4. Implémentation du thème sombre
  3. Chargement de la liste des arrêts : récupérer et traiter du JSON
    1. Boucles en Javascript
    2. Les chaînes template
    3. Récupérer et traiter un fichier JSON
    4. Exécution d’une fonction quand le site est complètement chargé
    5. Implémentation du chargement de la liste d’arrêts
  4. Chargement des horaires d’un arrêt : choix de liste et périodicité
    1. Événement changement de valeur d’une liste déroulante
    2. Callback périodique
    3. Paramètres par défaut d’une fonction
    4. Implémentation de l’affichage des horaires
  5. Correction “Horaires TAN”
  6. Surprise : Throne Mash !

Préparatifs

Veuillez d’abord télécharger et importer le projet suivant : archive. L’archive contient une application FLASK qui servira de proxy pour appeler l’API de la TAN. Cette dernière n’autorise en effet pas un navigateur à faire un appel depuis un site autre que celui de la TAN. Pour plus d’information sur cette fonctionnalité appelée Cross-Origin Resource Sharing (CORS), voir la page Wikipedia correspondante.

L’archive contient :

  • app.py : application Flask qui définit trois points d’entrée :
    • / : affichage de la page web (template horaires.html.jinja2)
    • /liste_arrets : proxy vers la liste des arrêts fournie par l’API de la TAN au format JSON
    • /arrets/<code> : récupère les prochains passages à l’arrêt dont le code est passé en URL auprès de l’API de la TAN, modifie le format des données pour le rendre plus facile à utiliser par notre application et renvoie le résultat au format JSON
  • templates/horaires.html.jinja2 : page HTML de notre application. Elle ne contient aucune information provenant de la TAN, tout sera dynamique.
  • static/css/hoaires.css : feuille de style CSS très simple pour améliorer l’affichage de notre application
  • static/js/horaires.js : script Javascript presque vide que nous allons compléter lors de la session

Fichiers CSS / Javascript à inclure pour notre projet

Veuillez observer le contenu du fichier templates/horaires.html.jinja2. Comme nous utilisons Bootstrap, nous devons d’abord inclure le CSS de Bootstrap. Cependant, cette fois nous n’allons pas utiliser le CSS de référence mais un thème Bootstrap, qui est une version modifiée du CSS de référence. Nous utiliserons le thème suivant : https://bootswatch.com/flatly/. Nous faisons cela car une version sombre de ce même thème existe : https://bootswatch.com/darkly/. Pour le moment, nous nous contentons d’inclure le thème clair :

<link id="theme" rel="stylesheet" href="https://bootswatch.com/4/flatly/bootstrap.min.css" crossorigin="anonymous">

Vous remarquerez que nous incluons aussi notre propre CSS dont l’URL est un paramètre du template :

<link rel="stylesheet" href="">

Par défaut, Bootstrap nécessite d’inclure trois fichiers Javascript : jQuery slim, Popper et Bootstrap lui-même. Nous remplaçons jQuery slim par la version complète de jQuery car nous en aurons besoin. Cela donne :

<script src="https://code.jquery.com/jquery-3.2.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
        crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
        crossorigin="anonymous"></script>

Nous importons aussi notre fichier Javascript, dont l’URL est un paramètre du template :

<script src=""></script>

L’API de la TAN

Pour notre applications, nous utiliserons l’API temps réel de la TAN : https://data.nantesmetropole.fr/explore/dataset/244400404_api-temps-reel-tan/information/. Vous trouverez en suivant ce lien la description de l’API et une documentation. Nous utiliserons deux points d’entrée proposés :

  • http://open.tan.fr/ewp/arrets.json qui donne la liste des arrêts complète
  • [http://open.tan.fr/ewp/tempsattente.json/](http://open.tan.fr/ewp/tempsattente.json/COMM) qui donne la liste des prochains passage à un arrêt (lien : exemple de l'arrêt Commerce)

Ces deux points d’entrée nous renvoient des informations au format JSON. Ce format permet l’échange de données structurées sous forme de dictionnaires, tableaux, entiers, nombres à virgule, booléens et chaînes de caractères. Voici un extrait de la liste des arrêts qui nous est retournée par le premier point d’entrée :

[
   {  
      "codeLieu":"CNAV",
      "libelle":"Chantiers Navals",
      "distance":null,
      "ligne":[  
         {  
            "numLigne":"1"
         }
      ]
   },
   {  
      "codeLieu":"CTRE",
      "libelle":"Chantrerie",
      "distance":null,
      "ligne":[  
         {  
            "numLigne":"C6"
         },
         {  
            "numLigne":"75"
         },
         {  
            "numLigne":"115"
         }
      ]
   },
   {  
      "codeLieu":"CHZY",
      "libelle":"Chanzy",
      "distance":null,
      "ligne":[  
         {  
            "numLigne":"C1"
         }
      ]
   }
]

On a donc un tableau de dictionnaire, chaque dictionnaire contenant les informations d’un arrêt (son code de lieu, son nom réel, la distance qui le sépare des coordonnées GPS passées en argument (null ici car nous n’en avons pas fourni) et une liste dictionnaires contenant chacun un numéro de ligne passant à cet arrêt). Cette syntaxe est identique à celle utilisée en Python et en Javascript (sauf que null devient None en Python).

Thème sombre : les bases de Javascript + jQuery

Nous allons d’abord permettre à l’utilisateur de changer le thème de notre application en cliquant sur le bouton à droite de la barre de navigation (ou en bas en affichage mobile). Pour cela, nous changerons la feuille de style CSS Bootstrap utilisée à chaque fois qu’on clique sur le bouton.

Variables et fonctions en Javascript

Il y a deux façons de déclarer une variable en Javascript : avec le mot clé var et avec le mot clé let. La différence est la portée : une variable déclarée avec var sera accessible dans tout le script une fois déclarée, alors qu’une variable déclarée avec let ne sera accessible que dans la portée de la déclaration (comme en Java). let a été introduit plus récemment et est désormais considéré comme une meilleure pratique pour éviter des bugs.

let entier = 5;
let nvirgule = 2.5;
let texte = "Je suis un texte";
let tableau = [4, 2, 42];
let tableau2 = [1, "2", 3.4, tableau]; // Possible, mais à éviter
let objet = {champ1: "valeur", "champ2": 2}; // Nom des champs avec ou sans guillemets

let l = tableau.length; // 3
let pe = tableau[0]; // 4
let c1 = objet["champ1"]; // "valeur"
let c2 = objet.champ2;    // 2        (les deux sont interchangeables)

let test1 = 5=="5"; // Vrai en Javascript (conversion implicite)
let test2 = 5==="5"; // Faux
let test3 = 5===5; // Vrai

let b = true;
if (b) {
    let v_let = 5;
    var v_var = "Salut";
    console.log(v_let, v_var); // Équivalent de print, prend autant d'argument que désiré
}
console.log(v_var); // Marche seulement si b vaut true
console.log(v_let); // Erreur (c'est une bonne chose)

Comme vous pouvez le remarquer, Javascript permet de faire des choses étranges, il faut donc être un programmeur rigoureux pour éviter les bugs. Pour les curieux, et après le cours, cette vidéo est intéressante (le Javascript est à partir de 1:20) : https://www.destroyallsoftware.com/talks/wat

Il y a plusieurs façons de déclarer une fonction en Javascript, les deux que nous utiliserons sont :

function maFonction(param1, param2) {
    // Contenu
    return 4; // return optionnel
}

let monAutreFonction = function(param1, param2) {
    // Contenu
    return 2; // return optionnel
}

On notera qu’on peut passer des fonctions en paramètre :

function appliquer(f, a, b) {
    return f(a,b);
}

function sum(n1,n2) {
    return n1+n2;
}

console.log(appliquer(sum, 3, 2)); // Affiche 5
console.log(appliquer(
    function (n,m) {return n-m;},
    3,
    2
)); // Affiche 1

Accès au DOM avec jQuery

jQuery nous permet de créer une référence vers les éléments de notre page HTML avec les mêmes sélecteurs que CSS. Ainsi :

$("p"); // Référence vers tous les paragraphes
$(".maclasse"); // Référence vers tous les éléments de classe "maclasse"
$("#monid"); // Référence vers l'élément d'ID "monid"

Une fois que l’on a une référence vers un ou plusieurs éléments du DOM, on peut leur appliquer plusieurs opérations. Certaines opérations peuvent s’appliquer sur plusieurs éléments, quand d’autres ne s’appliquent que sur la première référence (on cherchera alors à ne cibler qu’un seul élément avec notre sélecteur).

// Plusieurs éléments
$("p").hide(); // Cache tous les paragraphes
$("p").show(); // Les réaffiche
$("p").html("Bonjour <span class='nom'>Jonathan</span> !"); // Replace leur contenu
$("p").attr("lang", "fr"); // leur ajoute l'attribut lang="fr"

// Un seul élément
console.log($("p").html()); // Affiche le contenu HTML du premier paragraphe
console.log($("p").attr("class")); // Affiche ses classes

Les événements

Javascript est un langage basé sur les événements. Une fois l’intialisation effectuée, la plupart du temps il ne se passe rien sur notre page. Pour réagir à un événement, on demande au navigateur d’appeler une fonction (qu’on appelle alors “callback”) lorsqu’un événement se produit.

Nous allons voir l’exemple de l’événement “cliquer sur un élément HTML”. En jQuery, on déclare un callback pour cet événement ainsi :

function maFonction() {
    console.log("J'ai cliqué !");
}
$("sélecteur").click(maFonction);

Implémentation du thème sombre

Vous avez maintenant toutes les cartes en main pour permettre à l’utilisateur de passer en thème sombre (et idéalement de repasser en thème clair) ! Pour vous aider, voici une suggestion sur l’organisation du code :

  • D’abord déclarer un tableau contenant la liste des fichiers CSS possibles. Le premier sera celui déjà déclaré dans le code HTML (à récupérer en Javascript), le deuxième sera https://bootswatch.com/4/darkly/bootstrap.min.css.
  • Déclarer une variable indiquant l’indice du tableau qui correspond actuellement au thème actif (au début 0).
  • Définir une fonction permettant de changer le thème (changer l’indice du thème actif puis changer l’attribut href de la balise HTML <link> incluant le CSS).
  • Déclarer que cette fonction est un callback de l’événement “cliquer sur le bouton de changement de thème”.

Chargement de la liste des arrêts : récupérer et traiter du JSON

Nous allons maintenant récupérer la liste des arrêts de l’API pour permettre à l’utilisateur de choisir un arrêt grâce à la liste déroulante dans la barre de menu.

Boucles en Javascript

La syntaxe du while en Javascript est similaire à celle de Java, en revanche le for est légèrement différent.

while (condition) {
    // Instructions à répéter
}

for (let i=0; i<5; ++i) {
    console.log(i);
}

tableau = [1, 2, 3];

for (let e of tableau) {
    console.log(e);
}

Les chaînes template

Nous allons prochainement construire un code HTML en Javascript. Pour ceci, nous utiliserons un type de chaînes de caractères ajouté il y a quelques années dans Javascript : les chaînes template. Elles ont un comportement similaire à ce qu’on peut faire en Jinja 2.

let a = 20;
let s = "Adrien";

let ts = `
Salut ${s} !
Il fait ${a} degrés aujourd'hui !
"Ça fait du bien !"
`;

On remarquera qu’il est possible d’insérer une expression Javascript entre ${ et } ainsi que des retours à la ligne et les caractères ', " à l’intérieur de la chaîne.

Récupérer et traiter un fichier JSON

Nous allons appeler l’API de la TAN (enfin, le proxy fourni par notre application Flask) pour récupérer la liste des arrêts de bus/tram. Cette technique de chargement dynamique de contenu s’appelle AJAX.

JQuery fournit plusieurs fonctions pour faire de l’AJAX. L’une d’elle permet de demander le chargement d’un fichier JSON et d’appeler un callback pour le traiter une fois qu’il est reçu.

$.getJSON("URL", traiterJSON);
function traiterJSON(objet) {
    // On utilise "objet" comme un objet Javascript
}

Exécution d’une fonction quand le site est complètement chargé

En jQuery, il est possible de définir un callback pour l’événement “site complètement chargé” avec les syntaxes (très minimalistes) :

$(callback);

// ou

$(function() {
   // Contenu
})

Implémentation du chargement de la liste d’arrêts

Vous pouvez maintenant compléter la partie 2 du fichier Javascript. Pour rappel, nous souhaitons charger la liste des arrêts de la TAN et permettre à l’utilisateur d’en choisir un dans la liste déroulante dans la barre de menu (l’identifiant de la liste est ligne-select). Voici une suggestion :

  • Commencer par écrire une fonction qui fera l’appel AJAX. L’URL à appeler ici est /liste_arrets.
  • Écrire la fonction callback qui traitera la liste des arrêts. Cette fonction générera une nouvelle liste d’options pour la liste déroulante, puis insérera ce code HTML à l’intérieur de l’élément HTML d’identifiant ligne-select. Pour rappel, voici à quoi ressemble la liste d’arrêts reçue (que vous pouvez traiter comme un objet Javascript dans la fonction de callback) :
[
   ...
   {  
      "codeLieu":"CNAV",
      "libelle":"Chantiers Navals",
      "distance":null,
      "ligne":[  
         {  
            "numLigne":"1"
         }
      ]
   },
   {  
      "codeLieu":"CTRE",
      "libelle":"Chantrerie",
      "distance":null,
      "ligne":[  
         {  
            "numLigne":"C6"
         },
         {  
            "numLigne":"75"
         },
         {  
            "numLigne":"115"
         }
      ]
   },
   ...
]

Et voici à quoi le HTML généré devrait ressembler (la liste des lignes entre parenthèses est optionnelle) :

<option value="">Sélectionner un arrêt</option>
...
<option value="CNAV">Chantiers Navals (1)</option>
<option value="CTRE">Chantrerie (C6, 75, 115)</option>
...

La propriété value de la balise <option> nous permettra par la suite de connaître le code de l’arrêt choisi par l’utilisateur, que nous devrons fournir pour récupérer les horaires à un arrêt.

  • Ajouter un callback pour l’événement de fin de chargement déclenchant le chargement de la liste des arrêts.

Chargement des horaires d’un arrêt : choix de liste et périodicité

Nous allons maintenant afficher la liste des prochains passages à l’arrêt sélectionné par l’utilisateur, puis mettre cette liste à jour périodiquement.

Événement changement de valeur d’une liste déroulante

Quand l’utilisateur change la valeur d’une liste déroulante, un événement Javascript est émis. Il est possible d’ajouter un callback à cet événement grâce au code jQuery suivant :

$('sélecteur').change(callback);

Notez qu’il est aussi possible de définir directement la fonction au lieu de passer une fonction définie précédemment. Il est aussi possible d’accéder à l’élément choisi (et notamment à son attribut value qui nous intéresse) en utilisant le mot clé this. Par exemple :

$('sélecteur').change(function() {
    console.log(this.value);
});

Callback périodique

Il est possible en Javascript de définir un callback qui sera appelé de manière périodique grâce à la fonction suivante :

setInterval(callback, 8000); // Toutes les 8 secondes (temps donné en ms)

Paramètres par défaut d’une fonction

On peut définir en Javascript des valeurs par défaut pour les paramètre des fonctions. Par exemple :

function afficheNombre(n=0) {
    console.log(n);
}
afficheNombre();  // 0
afficheNombre(5); // 5

Implémentation de l’affichage des horaires

Vous pouvez maintenant compléter la partie 3 du code. Vous remarquerez qu’une fonction directionHtml vous est déjà fournie : elle sert à mettre en forme les données de passages.

De plus, nous n’utiliserons pas directement le format renvoyé par l’API de la TAN qui n’est pas pratique. Vous n’avez cependant rien à faire, la conversion de format est déjà codée dans l’application Flask.

Vous devrez faire en sorte que quand l’utilisateur choisisse un arrêt, les horaires qui correspondent à cet arrêt soient chargés et affichés. De plus, les horaires devront se mettre à jour toutes les 30 secondes. Voici une suggestion d’organisation du code :

  • Commencer par initialiser une variable “arrêt courant” qui nous permettra de savoir quels horaires charger quand nous feront la mise à jour périodique. Par défaut, cette variable devrait valoir "".
  • Bonus : sauvegarder le contenu de l’élément HTML d’identifiant lignes (typiquement un message de bienvenue) pour pouvoir le rafficher ultérieurement.
  • Écrire une fonction qui va faire l’appel AJAX pour charger les prochains passages à l’arrêt courant (URL /arret/<code>). Bonus : vous pouvez utiliser un paramètre booléen pour qu’on puisse afficher un message de chargement si l’utilisateur vient de changer de ligne, et ne pas en afficher si on fait juste une mise à jour des horaires. De plus, on peut rafficher le message de bienvenue précédemment sauvegardé si l’utilisateur choisit “Sélectionner un arrêt”.
  • Écrire une fonction callback qui traitera la réponse de l’API et générera un code HTML puis l’insérera dans l’élément HTML d’identifiant lignes. Les données que vous receverez seront sous cette forme (exemple pour Chantrerie) :
[
   {  
      "nom":"75",
      "type":3,
      "directions":[  
         {  
            "passages":[  
               {  
                  "no_terminus":1,
                  "temps":"3 mn"
               },
               {  
                  "no_terminus":1,
                  "temps":"32 mn"
               }
            ],
            "terminus":{  
               "Charbonneau":1
            }
         },
         {  
            "passages":[  
               {  
                  "no_terminus":1,
                  "temps":"17 mn"
               },
               {  
                  "no_terminus":1,
                  "temps":"45 mn"
               }
            ],
            "terminus":{  
               "Facultés":1
            }
         }
      ]
   },
   {  
      "nom":"C6",
      "type":3,
      "directions":[  
         {  
            "passages":[  
               {  
                  "no_terminus":1,
                  "temps":"11 mn"
               },
               {  
                  "no_terminus":1,
                  "temps":"23 mn"
               },
               {  
                  "no_terminus":1,
                  "temps":"35 mn"
               },
               {  
                  "no_terminus":1,
                  "temps":"47 mn"
               }
            ],
            "terminus":{  
               "Hermeland":1
            }
         }
      ]
   }
]

Vous remarquerez que les tableau directions contiennent une liste d’objets indiquant les prochains horaires et terminus possibles pour une direction d’une ligne. Ces objets seront directement traités par la fonction fournie directionHtml. Le format HTML attendu est le suivant :

<div class="ligne col-xl-3 col-sm-6 col-12">
    <div class="card">
        <h2 class="card-header">75</h2>
        <div class="card-body">
            <!-- Direction 1 (fourni par la fonction directionHtml) -->
            <!-- Direction 2 (fourni par la fonction directionHtml) -->
        </div>
    </div>
</div>
<div class="ligne col-xl-3 col-sm-6 col-12">
    <div class="card">
        <h2 class="card-header">C6</h2>
        <div class="card-body">
            <!-- Direction 1 (fourni par la fonction directionHtml) -->
        </div>
    </div>
</div>
  • Définir un callback qui changera la valeur de la variable d’arrêt courant et appellera la fonction de rechargement (bonus : avec écran de chargement) quand l’utilisateur changera d’arrêt dans la liste déroulante.
  • Définir un callback pour la fin de chargement de la page qui demandera l’exécution périodique de la fonction de rechargement de l’arrêt courant.

Correction “Horaires TAN”

Correction : https://github.com/chardetm/horairesTAN

Surprise : Throne Mash !

Voici une application utilisant tout ce qu’on a vu, avec en plus la création d’une API en Flask, ce qui pourrait vous être utile si vous voulez créer des pages dynamiques dans votre projet.

Code : https://github.com/chardetm/ThroneMash Démo : http://thronemash.jonathanpastor.fr