JavaScript, jQuery, AJAX et JSON :

Le JavaScript est un langage vaguement inspiré de la famille des langages C (comme Java, C++, Objective-C, ...).

Introduction à JavaScript :

JavaScript est un langage de scripts essentiellement utilisé pour enrichir les pages HTML de code exécutable par le navigateur. Attention ! Il n' a guère qu'une partie de son nom en commun avec le langage Java ! L'utilisation de JavaScript s'étend de plus en olus vers de nouvelles cibles comme la programmation sur le serveur. Nous ne prendrons en compte ici que son utilisation pour les navigateurs.

Ce langage permet d'apporter du dynamisme au niveau des pagesWeb. Nous percevons immédiatement une contradiction entre le fait qu'un navigateur doive essentiemment se contenter de réaliser l'affichage de pages et celui de lui faire exécuter du code. On veillera donc à limiter au maximum l'utilisation de code JavaScript pour :

  • éviter d'alourdir les documents HTML
  • faciliter leur maintenance
  • ne pas surcharger de traitements les postes de travail

Veillons aussi à produire des pages qui pourront être exploitées sur un maximum de types de navigateurs en n'oubliant pas que cerains d'entre eux n'intègrent pas JavaScript (exemple : certains browsers de smartphones) et que certains utilisateurs inhibent JavaScript.

JavaScript est le premier outil nous permettant de faire évoluer un navigateur de la notion de "client léger" à celle de "client riche".

De manière générale, JavaScript est utilise pour :

  • Permettre de réaliser des validations de données saisies dans des formulaires.
  • Pour gérer les cookies.
  • Pour éviter d'augmenter inutilement le nombre de requêtes soumises au serveur Web.
  • Il est peut aussi permettre d'enrichir la présentation de la page (menus dynamiques par exemple).

Il peut aussi être utilisé pour faire clignoter une guirlande, faire traverser l'écran par un canard ou demander à un poisson de suivre votre souris (comme quoi il faut savoir ne pas abuser des bonnes choses, ...).

Intégration au HTML :

Un script Javascript sera fourni dans la balise script avec l'attribut type de valeur text/javascript. Il existe également une version plus complexe tenant compte des vieux navigateurs qui ne gèrent pas le JavaScript. En HTML5, on déconseille de spécifier explicitement le type s'il s'agit de text/javascript : https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script.

Soit direment dans le code HTML :

<script type="text/javascript">
    // code du script...
</script>

Soit par référence à un fichier externe :

<script type="text/javascript" src="scriptname.js"></script>

On préfèrera cette deuxième option par souci de lisibilité du code.

Déclenchement du code :

Une balise <script type="text/javascript"> peut apparaîtra soit dans le head de la page, soit dans le body. Le chargement du fichier de script JavaScript interrompt celui du fichier HTML. Tous les scripts invoqués dans le fichier JavaScript sont immédiatement exécutés lors du chargement dudit fichier.

Quelques éléments de syntaxe de JavaScript

  • Une instruction JS ("statement") se compose de :

    • Expressions
    • Valeurs
    • Opérateurs (+, -, ...)
    • Mots clés (for, break, ...)
    • Commentaires
  • Les instructions sont délimitées par le point-virgule mais celui-ci n'est obligatoire que si l'on désire reprendre plusieurs instructions sur la même ligne.
  • Les commentaires sont repris, sur une ligne derrière les caractères //.
  • Les noms de variables sont sensibles à la casse. Il est possible également d'utiliser directement une variable sans utiliser let (portée associée à un bloc), var (portée globale) ou const (portée associée à un bloc et immuable (la valeur ne peut pas être changée) !
  • Les instructions de contrôle de séquence (if, while, ...) sont sensiblement les mêmes qu'en Java.

Dans le cadre de ce cours, nous nous obligerons à respecter la convention suivante :

  • Toutes les variables seront déclarées !
  • Les variables seront déclarées avec let ou const !
  • Aucune variable sans déclaration !
  • Aucune variable déclarée avec var !

Types :

var i = 1; // Number
var f = 1.0; // Number
var s = "chaîne; // string
var s2 = 'chaîne'; // string
var s3 = new String('chaîne'); // string
var a = [1, 2.0, "3"]; // Array
var b = true; // Boolean
var u; // undefined
var n = null; // null

Notez la différence entre undefined (la variable n'a pas été affectée, son type est inconnu) et null (cas particulier des objets).

Opérateur instanceof :

instanceof Number String Array Object Booelan Function
1
new Number(1) X X
"a"
new String("a") X X
[1] // Array X X
new Array() X X
{} // Object X
new Object() X
function() X X
true
new Boolean(true) X X
null
undefined
var color1 = new String("green");
color1 instanceof String;
// returns true
var color2 = "coral";
color2 instanceof String;
// returns false (color2 is not a String object)

1 et new Number(1) sont fonctionnellement identiques en JavaScript. Cependant, en internce, 1 correspond à un int Java, tanis que New Number(1) correspond à un Integer. Ceci explique le comportement bizarre d'instanceof.

Opérateur typeof :

L'opérateur typeof v : renvoie une chaîne de caractères décrivant le type de v.

typeof
1 "number"
new Number(1) "object"
"a" "string"
new String("a") "object"
[1] // Array "object"
new Array() "object"
{} // Object "object"
new Object() "object"
function() "function"
true "boolean"
new Boolean(true) "object"
null "object"
undefined "undefined"

Pour être vraiment certain d'un type (par exemple, String), il faudra donc utiliser typeof et instanceof :

typeof s == 'string' || s instanceof String;

À propos du typage :

  • Dynamiquement typé == le type de la variable est connu à l'exécution uniquement. Contraire : statiquement typé (ex. : Java).
  • Faiblement typé == la variable contient une certaine valeur qui peut être interprétée suivant plusieurs types. 10 == la chaîne 10 == l'entier 10 == le flottant 10.0. Contraire : fotement typé (ex. : Java).
  • Dynamiquement typé != faiblement typé

JavaScript n'est pas faiblement typé mais certains opérateurs se comportent comme si c'était le cas !

Un transtypage est alors automatiquement appliqué.

L'opérateur == effectue un tel transtypage. Ceci donne des résultats parfois surprenants. En général, c'est une mauvaise idée de l'utiliser.

=== est l'opérateur d'égalité qui n'effectue pas de transtypage. En général, c'est celui-ci qui doit vraiment être utilisé, pas ==.

Idem pour !== et !=.

Un exemple simple :

Plaçons ceci dans le body d'un de nos précédents documents HTML :

<script type="text/javascript">
    document.write("Added by JavaScript code : ");
    document.write("He2b-Esi <3");
</script>

Nous pouvons aussi sauvegarder ce script à part dans un fichier Myscript.js, dont la référence relative au document HTML ou sa référence absolue sera fournie dans l'attribut source de l'élément script. Si le fichier se trouve dans le même répertoire que le document HTML, nous pourrons le référencer comme suit :

<script type="text/javascript" src="MyScript.js"></script>

Quelques utilisations classiques :

Validation de formulaire :

Un cas classique est celui de la validation d'un champ de saisie devant être numérique. Nous allons réaliser la validation lors de la perte du focus par le contrôle (voir http://www.w3schools.com/tags/ref_eventattributes.asp pour une liste des événements gérés).

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Validation de formulaire</title>
        <script type="text/javascript">
            function validateNumeric(champ) {
                if (isFinite(champ.value)) {
                    return true;
                } else {
                    alert("Vous ne pouvez entrer que des nombres !");
                    return false;
                }
            }
        </script>
    </head>
    
    <body>
            <h1>Validation externe</h1>
            <form name="InfoPersonnelle">
                Âge : <input type="text" name="saisie" onblur="validateNumeric">
            </form>
    </body>
</html>
Remarques :
  • D'autres événements peuvent vous intéresser onclick, onkeypress, onload, ... En voici la liste complète : http://www.w3schools.com/jsref/dom_obj_event.asp.
  • Par convention, si la méthode associée à l'événement renvoie false (comme dans l'exemple ci-dessusà, cela signifie que l'événement n'a pas eu lieu : la page ne sera pas soumise, le caractère pas rentré, ...
  • Si nous désirons réakuser une validation lors de l'envoi du formulaire nous serons obliger d'écrire un script spécifique au formulaire.
  • Nous verrons par la suite que nous veillerons à éviter l'utilisation des fonctions alert(), confirm(), prompt(), ... qui ont tendance à alourdir l'interface.

Gestion des cookies :

Un Cookie est un petit fichier texte, chiffré ou non, que des sites visités peuvent stocker sur votre machine pour être récupérés d'une visite à l'autre (pour vous faciliter la navigation, pour vous tracer, ...). Seul le site ayant stocké le cookie y a accès.

Un cookie est une paire nom=valeur associé à un serveur et stockée sur le poste client. Lorsque le navigateur envoie une requête à un serveur, il lui envoie également tous les cookies associés.

Un cookie possède aussi une date d'expiration. JavaScript permet, via la propriété cookie du document, de lire et de créer des cookies. La version simplifiée qu'on présente ici peut être prise en défaut dans certains cas particuliers.

function setCookie(nom, valeur) {
    document.cookie = nom + "=" + encodeURI(valeur);
}

function getCookie(nom) {
    // Tous les cookies sont reçus dans une seule chaîne,
    // séparés par des point-virgules, qu'il faut découper.
    start = document.cookie.indexOf(nom + "=");
    if (start != -1) {
        start = start + nom.length + 1;
        end = document.cookie.indexOf(";", start);
        if (end == -1) {
            end = document.cookie.length;
        }
        return unescape(document.cookie.substring(start, end));
    } else {
        return "";
    }
}

DOM (Document Object Model) :

Le Document Object Model va nous permettre de manipuler (consulter/modifier) notre page web via le code JavaScript. On va pouvoir, par exemple, ajouter un message d'erreur à côté d'un champ erroné. C'est également un élément essentiel d'Ajax que nous verrons ultérieurement.

Le DOM (Document Object Model), ou modèle objet de document, est une API pour les documents HTML et XML. Le DOM fournitune représentation structurelle du document, permettant de modifier son contenu et sa présentation visuelle. Fondamentalement, il relie les pages Web aux scripts et langages de programmation.

Concrètement, cela signifie qu'au niveau de JavaScript on dispose d'une représentation du document HTML sous forme d'un "arbre", chaque "noeud" correspond à une balise du document. Cet arbre peut être parcouru mais surtout modifié. Les possibilités sont énormes mais nous l'utiliserons essentiellement :

  • Pour faire apparaître des messages d'information ou d'erreurs aux endroits appropriés de la page.
  • Avec la technologie AJAX dont il est une des composantes essentielles.

Quelques éléments de syntaxe

Nous ne présentant que la toute petite partie qui sert à notre propos. Nous en dirons un peu plus lorsque nous introduirons les technologies XSL et, comme d'habitude, vous trouverez plus d'informations sur le site de la W3 Schools (http://www.w3schools.com/jsref/dom_obj_document.asp).

  • Il existe un certain nombre d'objets prédéfinis. Vous apprendrez à l'usage les méthodes et attributs offerts : document (la racine de l'arbre), window (la fenêtre), ...
  • On peut accéder à un ou des noeuds de l'arbre :

    • getElementById("id") ; le noeud correspondant à cet identifiant unique.
    • getElementsByTagName("tag") : un tableau de noeuds porteurs de ce tag.
    • querySelector(selecteur CSS) : le premier élément dans la descendance de l'élément appelant la méthode querySelector compatible avec le sélecteur CSS en argument.
  • On peut interroger un noeud via des attributs :

    • L'attribut innerHTML correspond au code HTML inclus dans un noeud.
    • L'attribut style donne le style associé à un noeud.
    • L'attribut attributes donne les attributs associés à un noeud.
    • Il y a aussi : nodeType, nodeName, nodeValue, ...
  • On peut, à partir d'un noeud, se promener dans l'arbre : parentNode, childNodes, firstChild, lastChild, nextSibling, previousSibling.
  • On peut aussi modifier l'arbre : createElement, createTextNode, appendChild, insertBefore, replaceChild, cloneNode, removeChild.

Exemple récapitulatif :

Si le document contient <span id="titre"></span>, alors le bout de code suivant permet d'insérer un titre :

document.getElementById("titre").innerHTML = "Compléments Applications";

Le code suivant affiche un message avec le nombre de paragraphes (à lancer via l'événement onload de la balise <body> par exemple).

function countParagraphs {
    var myParagraphs = document.getElementsByTagName("p");
    alert(myParagraphs.length);
}

Découplage HTML / JavaScript :

Tout comme il est de bonne pratique de découpler le contenu HTML de la mise en forme CSS, en liant le(s) fichier(s) CSS à l'aide de la balise <link>, il est conseillé de séparer HTML et JavaScript à l'aide de l'attribut src de la balise <script>.

Par ailleurs, il est également conseillé de découpler fortement les éléments HTML du JavaScript en associant les gestionnaires d'événements dynamiques en JavaScript plutôt que statiquement dans le document HTML.

Dans la suite, voyons en détails comment associer une fonction de validation à un champ d'un formulaire, comme cela a été déjà rapidemment montré ci-dessus.

JavaScript interne :

Reprenons le document HTML avec un script interne :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Validation de formulaire</title>
        <script type="text/javascript">
            function validateNumeric(champ) {
                if (isFinite(champ.value)) {
                    return true;
                } else {
                    alert("Vous ne pouvez entrer que des nombres !");
                    return false;
                }
            }
        </script>
    </head>
    
    <body>
            <h1>Validation externe</h1>
            <form name="InfoPersonnelle">
                Âge : <input type="text" name="saisie" onblur="validateNumeric">
            </form>
    </body>
</html>

JavaScript externe

Association statique :

Le script est déplacé dans un fichier formvalidate.js :

function validateNumeric(champ) {
    if (isFinite(champ.value)) {
        return true;
    } else {
        alert("Vous ne pouvez entrer que des nombres !");
        return false;
    }
}

Et le fichier HTML est inchangé à l'exception du lien vers ce script :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Validation de formulaire</title>
        <script type="text/javascript" src="formvalidate.js"></script>
    </head>
    
    <body>
            <h1>Validation externe</h1>
            <form name="InfoPersonnelle">
                Âge : <input type="text" name="saisie" onblur="validateNumeric">
            </form>
    </body>
</html>
Associaion dynamique :

On cherche à associer dynamiquement un gestionnaire d'événement à un élément HTML. Pour ce faire, on utilise le DOM via JavaScript dans un script association_event.js :

document.getElementById("age").addEventListener("blur", function(event) {
    validateNumeric(event.target.value);
}

Et on ajoute un lien vers ce script dans le code HTML :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Validation de formulaire</title>
        <script type="text/javascript" src="formvalidate.js"></script>
    </head>
    
    <body>
        <h1>Validation externe</h1>
        <form name="InfoPersonnelle">
            Âge : <input type="text" name="saisie">
        </form>
        
        <script type="text/javascript" src="association_event.js"></script>
    </body>
</html>

Notez qu'on préfère charger les scripts d'association en fin de la balise <body>, puisque le code JavaScript est exécuté dès qu'il est chargé; les éléments HTML doivent donc être présents dans le DOM au moment de l'exécution du code.

Association dynamique avec defer :

Si toutefois on veut charger le script à un autre endroit du code HTML, l'attribut defer permet de reporter le chargement du script après la fin du chargement de la page HTML dans son entièreté. Cet attribut n'est disponible que lorsqu'un script est chargé via l'attribut src.

Par exemple, on pourrait avoir un fichier de script unique formvalidate_association_events.js :

function validateNumeric (champ) {
    if (isFinite(champ.value)) {
        return true;
    } else {
        alert("Vous ne pouvez entrer que des nombres !");
        return false;
    }
}

const ageElement = document.getElementById("age");

ageElement.addEventListener("blur", function(event) {
    validateNumeric(event.target.value);
});

Qui est chargé dans le head comme suit :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Validation de formulaire</title>
        <script defer type="text/javascript" src="formvalidate_association_events.js"></script>
    </head>
    <body>
            <h1>Validation externe</h1>
            <form name="InfoPersonnelle">
                Âge : <input type="text" name="saisie">
            </form>
    </body>
</html>
Association dynamique avec l'événement DOMContentLoaded :

L'événment DOMContentLoaded est émis lorsque le document HTML est chargé et traité dans son entièreté. On peut donc aussi s'en servir afin d'exécuter du code JavaScript une fois que le document complet est à notre disposition.

Notre script devient alors :

function validateNumeric (champ) {
    if (isFinite(champ.value)) {
        return true;
    } else {
        alert("Vous ne pouvez entrer que des nombres !");
        return false;
    }
}

document.addEventListener("DOMContentLoaded", function() {
    const ageElement = document.getElementById("age");

    ageElement.addEventListener("blur", function(event) {
        validateNumeric(event.target.value);
    });
});

Qui est chargé dans le head comme suit :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Validation de formulaire</title>
        <script type="text/javascript" src="formvalidate_association_DOMContentLoaded.js"></script>
    </head>
    <body>
            <h1>Validation externe</h1>
            <form name="InfoPersonnelle">
                Âge : <input type="text" name="saisie">
            </form>
    </body>
</html>
Conclusion :

La méthode recommandée est celle qui utilise l'attribut defer de la balise <script>. Elle est en effet :

  • Propre : le HTL et le JavaScript sont séparés car defer oblige à utiliser l'attribut src.
  • Lisible: les balises <script> sont rassemblées dans le <head> du HTML.
  • Efficace : la récupération du JavaScript se fait de manière synchrone avec l'analayse du HTML.
  • Sûre : l'exécution des scripts JavaScript a lieu une fois le document HTML complètement construit.

jQuery :

jQuery est une librairie JavaScript qui facilite grandement l'écriture de scripts.

Introduction au jQuery :

JavaScript est parfois lourd à utiliser et impose de reprendre régulièrement le même code, de truffer le code de tests pour s'adapter au navigateur, ... Beaucoup de librairies de scripts permettent d'alléger ou éliminer ces défauts : jQuery, Mootools, Dojo, ...

jQuery est certainement la plus répondue et un grand nombre de firmes majeures du domaine de l'IT l'utilise). Ses fonctionnalités incluent :

  1. Manipulation du DOM.
  2. Manipulation du CSS.
  3. Gestion des événements.
  4. Animations.
  5. Ajax (que nous verrons un peu plus tard).

Une référence de jQuery se trouve à l'adresse https://jquery.com/.

Utilisation :

Inclusion de la librairie :

La librairie elle-même doit référencée dans votre page HTML, soit elle doit être fournie par votre site soit être référencée sur un site tiers.

Souvent, on utilise un CDN (ou Content Delivery Network) : une infrastructure réseau permettant de fournir vite et sans interruption de service des ressources stables dans le temos, par exemple celui de :

  1. jQuery
  2. Google
  3. Microsoft

D'autres CDN peuvent se trouver à l'adresse https://jquery.com/download/#other-cdns. Ces CDN ont l'avantage d'être utilisés par tellement de sites que les clients de nos pages risquent d'avoir déjà la librairie en mémoire cache (et ne doivent donc pas le re-télécharger). En outre, même si la librairie n'est pas en cache, son téléchargement par l'utilisateur n'utilisera de toute façon pas la bande passante de notre site.

On inclut cette librairie comme tout autre script JavaScript, donc :

<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HdCJo=" crossorigin="anonymous"></script>

Ou plus simplement :

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

Ces librairies sont condensées (minified en anglais, représenté dans le nom du fichier par .min pour minimiser le trafic réseau, donc quasiment illisibles (on parle de version de "production"). Nous pouvons accéder à la librairie "lisible à l'adresse https://code.jquery.com/jquery-3.4.1.js. Cette version est généralement utilisée pour le développement, elle nous permettra de vérifier qu'il s'agit bien d'un "gros" fichier de fonctions JavaScript.

Notons bien sûr que, puisque les scripts JavaScript sont chargés dans leur ordre d'apparition dans le DOM, qu'ilfaudra s'assurer que jQuery est chargé avant nos scripts qui l'utilisent.

Syntaxe de jQuery :

Pour analyser quelques exemples d'utilisations de jQuery, nous utiliserons le fichier suivant :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Exemples d'utilisation de jQuery</title>
        <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HdCJo=" crossorigin="anonymous"></script>
        <script src="script.js"></script>
    </head>
    <body>
        <div id="a">TODO write content</div>
        <p>Coucou</p>
        <p>Bonjour</p>
        <p>Hello</p>
    <;/body>
</html>

Si le script script.js est vide, l'affichage de la page ressemble à ceci :

TODO write content

Coucou

Bonjour

Hello

La fonction jQuery :

La librairie jQuery propose une seule fonction, jQuery(), qui peut aussi être appelée par son alias, $(). Cette fonction permet de rapidement sélectionner un élément, puis d'appeler d'autres fonctions sur cet élément, sous la forme suivante :

$(sélecteur).action(...);

Cette sélection se fait via des sélecteurs, qui utilisent la même syntaxe que les sélecteurs CSS.

Par exemple, le script suivant demande que l'élément dont l'id est "a" soit remplacé par l'argument de la méthode html(), qui est une des méthodes définies dans jQuery.

$(function() {
    $("#a").html("Je modifie ceci avec jQuery.");
}
Les événements en jQuery :

La plupart des événements du DOM disposent de leur équivalent en jQuery, généralement avec le même nom. Par exemple, la méthode click() associe à l'élément HTML sélectionné un événement qui se produit lorsque l'utilisateur clique sur cet élément.

Ajoutons un événement de ce type à notre script :

$(function() {
    $("#a").html('Je modifie ceci avec jQuery.');
    $("#a").click(function() {
        $(this).hide();
    });
});

Au moment où l'élément d'identifiant "a" reçoit un clic de souris, il va maintenant disparaître via la méthode hide(), qui permet de "cacher" l'élément. Le mot-clé this désigne l'élément courant.

On notera que la méthode on(event, handler) permet d'ajouter le gestionnaire d'un événement via son nom dans le DOM.

Enfin, notons que la méthode jquery() (et son alias $()), si elle reçoit un gestionnaire d'événement en paramètre au lieu d'un sélecteur CSS, sert de raccourci à l'expression :

$(document).ready(handler);

C'est-à-dire qu'il s'agit de l'événement qui se produit lorsque le DOM a terminé de se charger, qui comme nous l'avons vu permet de s'assurer que nos scripts JavaScript s'exécutent sur la page dans son ensemble.

Les méthodes de jQuery :

Nous avons déjà vu quelques méthodes qui peuvent être appelées sur un élément une fois qu'il a été sélectionné via jQuery. Il ne sera pas possible d'en voir une liste exhaustive dans le cadre de cours : comme toujours, nous ferons référence à la documentation sur le sujet.

Notons toutefois quelques méthodes fréquentes :

  1. html(text) permetde modifier le contenu d'un élément HTML (similaire à innerHTML du DOM).
  2. css() permet d'accéder du style CSSde l'élément. Elle admet plusieurs syntaxes :

    1. css("background") retourne la valeur de la propriété CSS background.
    2. css("color", "red") modifie la valeur de la propriété CSS color en red.
    3. css({"color": "red", "font-stle": "italic"}) modifie plusieurs propriétés d'un coup.

    Par sourci de séparation des rôles dans nos codes, il est généralement préférable de définir une classe CSS possédant ces propriétés, puis de donner cette classe aux éléments correspondants. De cette façon, toutes les informations destyle restent reléguées au CSS.

  3. attr() permet d'accéder à un attribut d'un élément HTML. Par exemple :

    1. attr("class") retourne la valeur de l'attribut class de l'élément.
    2. attr("class", "myClass") modifie la valeur de l'attribut class de l'élément en myClass.
  4. find() cherche et retourne les éléments descendants correspondants à certains critères, comme un sélecteur ou l'appel à la fonction jQuery(). Dans ce deuxième cas, les éléments retournés par cet appel à jQuery() sont filtrés pour me conserver que ceux qui sont descendants de l'élément sur lequel find() est appelé.

Notons que les méthodes qui modifient un élément HTML (y compris par un ajout de gestionnaire d'événement) renvoient l'élément lui-même en résultat. Cela nous permet d'effectuer du chaînage de méthodes, où l'on appelle des méthodes successivement sans devoir rappeler la fonction jQuery(). Cela nous permet d gagner en efficacité, et d'avoir une écriture plus compacte.

Par exemple, nous pouvons modifier notre script précédent :

$(function() {
    $("#a").html('Je modifie ceci avec jQuery.').click(function() {
        $(this).hide();
    });
});
Event Description
onclick The event occurs when the user clicks on an element.
oncontextmenu The event occurs when the user right-clicks on an element to open a context menu.
ondblclick The event occurs when the user double-clicks on an element.
omousedown The event occurs when the user presses a mouse button over an element.
onmouseenter The event occurs when the pointer is moved onto an element.
onmouseleave The event occurs when the pointer is moved out of an element.
onmousemove The event occurs when the pointer is moving while it is over an element.
onmouseover The event occurs when the pointer is moved onto an elemnt or onto one of its children.
onmouseout The event occurs when a user moves the mouse pointer out of an element, or out of one of its children.
onmouseup The event occurs when a user releases a mouse button over an element.

Validation de formulaires :

Rien n'est prévu à la base de jQuery mais le plugin validate est accessible pour subvenir à nos besoins. Nous devons bien sûr le télécharger :

<script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.19.1/dist/jquery.validate.min.js"></script>

La validation se fait via la création d'un objet JavaScript dans lequel nous définirons diverses règles que la validation prendra en compte. Nous reviendrons sur la syntaxe des objets JavaScript lorsque l'on va parler de JSON ci-dessous, mais prenons un exemple :

$("#formId").validate({
    rules: { // gestion des rèfles de validation
        nom: "required", // l'élement de nom "nom" est obligatoire et ne reprend pas d'autre règle
        mail: {
            email: true // l'élement de nom "mail" est un email mais pas obligatoire
        },
        numero: { // numéro obligatoire et compris entre 30000 et 99999
            required: true,
            range: [30000, 99999]
        },
        messages: { // redéfinition desmessages
            nom: "Le nom est obligatoire.",
            mail: {
                email: "Entrez un adresse mail valide."
            },
            numero: {
                required: "Le numéro est obligatoire.",
                range: "Le numéro doit être compris entre {0} et {1}."
            },
            // ...
        }
    }
});

Animations :

jQuery permet de réaliser aisément un grand nombre d'animations... dont il ne faut bien sûr pas abuser.

Prenez le temps de parcourir les quelques exemples présentés par W3Schools à partir de http://www.w3schools.com/jquery/jquery_hide_show.asp.

Un élément à bien comprendre est la notion de callback function.

Ajax :

Introduction à Ajax :

AJAX, nom donné à la mise en oeuvre d'un ensemble de techniques, permet de dynamiser les pages HTML.

Avec une page permettant de sélectionner un employé comme ci-dessous :

Sélectionnez un employé

Dans un site classique, à chaque fois que l'utilisateur choisit un autre département, une requête doit être faite au serveur afin de peupler la liste des employés avec ceux qui correspondent au département demandé. C'est peu efficace, surtout si l'utilisateur revient en arrière et doit dès lors demander une nouvelle liste.

Avec AJAX, nous pourrons lancer une requête unique au serveur demandant l'ensemble des employés du département sélectionné et les placer dans la liste déroulante des employés. Généralement, la liste des employés nous sera retournée sous la forme d'un document XML ou JSON.

Définition de AJAX :

AJAX signifie Asynchronous JavaScript And Xml. Il ne s'agit pas d'un langage ou d'une librairie, mais d'une démarche permettant de diminuer certaines limitattions imposées aux applications Web par HTTP et HTML.

La lacune essentielle des applications Web était leur manque d'intéractivité : toute modification de la page affichée par le navigateur imposait un réaffichage complet.

Nous avons déjà aperçu que JavaScript permet d'assouplir ce fait, mais les actions apparaissant dans le navigateur sont réalisées sans aucun contact avec le serveur. Si, par exemple, nous voulons afficher des informations à la demande de l'utilisateur, soit l'ensemble des informations doit être chargé initialement (transfert prohibitif comme l'exemple ci-dessus où le client devrait recevoir tous les départements et tous les employés d!s le premier chargement de la page !), soit la page complète doit être rechargée à chaque action !

AJAX va nous permettre de nous libérer de ces contraintes. Attention, à nouveau, comme pour JavaScript, il s'agira de ne pas abuser de cette démarche pour ne pas rendre nos pages trop complexes à concevoir et surtout à maintenir. En partier, la logique métier doit rester sur le serveur !

En reprenant l'acronyme complet, les caractéristiques d'AJAX sont :

  1. Il est Asynchrone : Le client va pouvoir parler au serveur en mode asynchrone. Il demande des informations au serveur et continue son travail. Lorsque la réponse du serveur est prête, un code précédemment spécifié est appelé pour la traiter.
  2. Il utilise JavaScript : Du côté client, c'est généralment ce langage qui est utilisé. JavaScript a longtemps été décrié car peu compatible d'un navigateur à l'autre, créant des problèmes de sécurité et finalement essentiellement utilisé pour de futiles effets graphiques. À tel point que les nvaigateurs permettent d'empêcher l'utilisation de JavaScript. Depuis la compatibilité s'est accrue et, surtout, on s'est rendu compte, qu'au-delà des gadgets, il pouvait apporter un réel confort d'utilisation.
  3. Il utilise aussi XML, en tant que format utilisé par le serveur pour transmettre des informations au client. C'est parfois également fait avec HTML (rarement, car limité) ou JSON (que nous verrons un plus tard).

Mise en oeuvre :

Nous ne réaliserons réellement ceci que plus tard quand nous programmerons une application web, car les exemples ci-dessus sont prévus pour le protocole http(s) et pas pour le protocole file.

En JavaScript :

L'objet JavaScript XMLHttpRequest permet d'exécuter des requêtes HTTP utilisées par Ajax :

http = new XMLHttpRequest();

Il faut noter qu'anciennement, chaque navigateur utilisait une syntaxe différente, et il fallait procéder via un try catch de JavaScript pour essayer les différentes syntaxes existantes. C'est toutefois du passé.

Une fois l'objet créé, il peut être utilisé pour envoyer des requêtes à un serveur, à l'aide de plusieurs méthodes et propriétés :

  1. http.open("method", "url") prépare l'envoi d'une requête au serveur reseigné par l'URL mentionnée, avec la méthode renseignée (par exemple "GET" ou "POST").
  2. http.responseType précise le format dans lequel le serveur va répondre; l'objet se prépare ainsi à recevoir et formater cette réponse.
  3. http.onreadystatechangeest une propriété dans laquelle on peut stocker une fonction, qui sera appelée lorsque la requête a été envoyée et une réponse a été reçue. Les valeurs que l'état peut prendre sont renseignées ici.
  4. http.send() envoie la requête au serveur.

Envoyer une requête asynchrone en JavaScript nécessite quatrelignes, c'est quand même dommage d'utiliser une bibliothèque juste pour cela !

function ajax(url, callbackfunction, method = 'GET') {
    let req = new XMLHttpRequest();
    req.open(method, url, true);
    req.onload = callbackfunction;
    req.send();
}

ajax('/monAPI/public/api/v1/info', () => {
    if (this.status === 200) {
        ...
    } else {
        ...
    }
});

Cette petite fonction prend en paramètre l'URL de la requête HTTP à envoyern la fonction callback qui sera appeléelorsque la réponse sera parvenue et éventuellement le verbe HTTP à utiliser.

Remarque : il est égalemet possible d'utiliser l'API fetch qui est plus moderne que XHR. Néanmoins, Safarine l'implémente que depuis sa version 10.1 datant de mars 2017.

Pour plus d'informaions, un tutoriel est disponible sur ce site.

Un exemple de code complet d'envoi d'une requête serait par exemple :

http = new XMLHttpRequest();
// Fonction appelée quand la réponse est prête.
http.onreadychange = stateChanged;
// Prépare une requête GET asynchrone pour l'URL indiquée.
http.open("GET", "url");
http.send(); // L'envoi au serveur

function stateChanged() {
    if (http.readyState == XMLHttpRequest.DONE) {
        // La réponse est revenue du serveur.
        if (http.status == 200) { // Tout s'est bien passé.
            http.responseText // Contient la réponse.
            http.responseXML // Pour récupérer du XML.
        } else {
            // Gestion de l'erreur.
        }
    }
}

Cet envoi de requête est bien asynchrone :

  1. On spécifie les traitements à réaliser lorsque la réponse sera prête, en associant une fonction à la survenue de l'événement onreadystatechange. La classe XMLHttpRequest fournit également d'autres gestionnaires d'événements.
  2. On prépare la requête avec la méthode open(), puis on l'envoie au serveur avec send().
  3. Quand la réponse est prête, cela déclenche l'exécution de la méthode spécifiée.

Si vous souhaitez tester ces méthodes sur des fichiers locaux, vous pouvez utiliser le protocole file; toutefois, notez que le code de réponse est alors 0 et non 200 comme pour une requête HTTP qui s'est déroulée avec succès.

Avec jQuery :

jQuery introduit une fonction $.ajax() qui permet de définir un appel AJAX avec les paramètres suivants, passés via un objet JavaScript :

  1. url : le document ciblé.
  2. type : les méthodes d'appel.
  3. data : des données qui accompagnent la requête (par exemple via un formulaire).
  4. success : la fonction JavaScript à exécuter en cas de réussite.
  5. error : la fonction JavaScript à exécuter en cas d'échec.
  6. complete : la fonction JavaScript à exécuter quand tout est terminé.

Par exemple (pris du tutoriel de W3Schools) :

$("button").click(function() {
    $.ajex({
        url: "demo_test.txt",
        success: function(result) {
            $("#div1").html(result);
        }
    });
});

Pour des requêtes plus standard, des méthodes prédéfinies existent également :

  1. $(selector).load(url) charge le contenu de la ressource à l'URL donnée dans l'élément sélectionné.
  2. $.get(url [, data, function, dataType]) envoie une requête avec la méthode GET à l'URL donnée.
  3. $.getJSON(url [, data, function]) envoie une requête avec la méthode GET et attend des données spécifiquement au format JSON.
  4. $.post(url [, data, function, dataType]) envoie une requête avec la méthode POST à l'URL donnée.

Avantages et inconvénients :

AJAX permet de fluidifier les échanges navigateur-serveur et de rendre l'interface utilisateur moins rigide (client "riche"). Il faut toutefois rester attentif à certains points :

  1. Les robots ne référenceront pas les données obtenues au travers d'AJAX.
  2. Attention à préserver la fonctionnalité du bouton "Back" du navigateur (des solutions toutes faites existent).
  3. Il faut assurer la sécurité des requêtes via AJAX aussi sérieusement que les requêtes "classiques".
  4. La notion de lien perd de son sens : fournir un lien à quelqu'un donne accès à la page mais pas nécessairement à l'info que l'on désire faire visualiser.

Remarque : Pour des raisons de sécurité, la plupart des navigateurs empêchent d'accéder, par le biais d'AJAX, à une ressource d'un autre serveur que celui qui a fourni la page courante. Ceci peut parfois être modifié, à vos rsiques et périls, dans les paramètres du navigateur.

Orienté Objet en JavaScript :

JavaScript supporte aussi la programmation orientée objet (ex. : new Object(), new Array(), ...).

La manière dont cela fonctionne est très différente d'un Java ou autres langages de programmation orientée objet.

Idépendamment de la programmation orientée objet, les objets JavaScript agissent aussi comme des Maps :

  • Association d'une clé (une String) à une valeur (de n'importe quel type)
  • Ceci s'appelle des objets associatifs.
var o = {"nom": "Atreides",
    prenom: "Paul", // les " de la clé sont facultatifs
    "age": 30,
    "truc": function(p1,p2) {...}
}

Gestion des attributs :

Valeur d'un attribut :

  • .nom // renvoie "Atreides"
  • ["nom"] // renvoie "Atreides"

Changer un attribut :

  • .nom = "Muad'dib"
  • ["nom"] = "Kwisatz Haderach"

Supprimer un attribut :

  • delete o.nom;
  • delete o["nom"];

Parcours sur les clés :

Object.keys(o) renvoie le tableau des clés ["nom", "prenom", ...].

for (var k in o) {
    // k vaudra successivement les clés de o
}

Objets et héritage :

Nous n'avons pas parlé de l'héritage, juste un petit écart :

  • Les attributs peuvent provenir d'un parent de l'objet.
  • Object.key ne renvoie pas les attributs parents.
  • for (... in ...) par contre itère bien sur attributs parents.
  • La méthode hasOwnProperty() retourne un booléen indiquant si l'objet possède l'attribut spécifié.

    o.hasOwnProperty("attribut");

Pseudo objet en JavaScript fonctionnel :

var o (fucntion() { // création d'une portée lexicale
    var a, b, c; // des variables restant privées
    function truc (p1, p2) { // aussi privé
        ...
    }
    function machin() { ... }
    return {
        getA : function() { return a; },
        "truc": truc ** machin rendue publique
    }
}) (); // exécution immédiate de la fonction (self-invoking)

Utilisation :

o.getA(); // renvoie la valeur de a

o.truc(1, 2); // appel de la fonction

Variation : Pseudo classe :

var createPerson = function (name, surname) {
    var age, address;
    function setAddress (a) { address = a:}
    function setAge(a) { age = a;}
    var self = {
        getName : function() { return name;},
        getSurname : function() { return surname;},
        getAge : function() { return age;},
        getAddress : function() { return address;},
        setAge : setAge,
        setAddress : setAddress
    }
    return self;
};

Utilisation de la pseudo classe :

var toto = createPerson("Toto", "Blague");
toto.setAddress("rue de la blague, 10");
toto.setAge(7);
var jean = createPerson("Jean", "Valjean");
jean.setAge(42);

Nous utilisons une variable pour retenir la pseudo-instance retournée. Nous l'appelons ici self, this étant un mot-clef réservé.

Variation : Pseudo héritage :

var createEtudiant = function (nom, prenom) {
    var self = createPerson(nom, prenom);
    var noma;
    self.setNoma = function(n) { noma = n;}
    self.getNoma = function() { return noma;}
    return self;
}

Format de données JSON :

JSON est un format de données dans les objectifs sont similaires à ceux de XML. Il se distingue par son format très léger.

Objets JavaScript :

Les objets JavaScript sont une structure de données permettant de stocker plusieurs informations dans une seule variable. Ces informations sont de deux types :

  • Des propriétés, qui contiennent des valeurs.
  • Des méthodes, qui contiennent des fonctions.

Contrairement à un langage typé tel que Java, il est possible de créer un objet sans définir une classe qui régit la structure de cet objet. Des classes peuvent être définies en JavaScript, mais leur rôle sert plutôt de générer des objets qui possèdent tous la même structure, puisque JavaScript n'est pas un langage typé.

Un objet est défini via des accolades {}, contenant des déclarations sous la forme champ: valeur, séparées par des virgules. Par exemple :

var person = {
    firstName: "John",
    lastName: "Doe",
    age: 50,
    eyeColor: "blue",
    fullName: () => {
        return this.firstName + " " + this.lastName;
    }
};

Cet objet possède quatre propriétés firstName, lastName, age et eyeColor, et une méthode fullName. Notez que les propriétés et méthodes se déclarent exactement de la même façon : c'est la valeur assignée au champ qui détermine s'il s'agit d'une propriété ou méthode.

L'accès aux propriétés peut se faire de deux façons nomDeVariable.propriété ou nomDeVariable["propriété"]. Ces notions sont équivalents.

person.firstName; // "John"
person["firstName"]; // "John"
person.age; // 50

Quant aux méthodes, il est possible d'appeler la méthode de façon similaire à un appel de fonction classique nomDeVariable.méthode(). Il est toutefois également possible de passer la déclaration de la fonction dans une variable en omettant les parenthèses nomDeVariable.méthode.

person.fullName; // Déclaration de la méthode
person.fullName(); // Appel de la méthode : renvoie "John Doe"

On peut donc voir un objet JavaScript comme un dictionnaire mettant en relation des chaînes de caractères à des valeurs ou des fonctions.

Intoduction à JSON :

JSON (JavaScript Object Notation) est un format texte qui peut être facilement interprété comme un objet JavaScript, ou un tableau d'objets JavaScript, du moment que ces objets ne possèdent pas de méthodes. Étant un format très léger, il a tedance à supplanter XML dans les services Web et l'utilisation d'AJAX.

Les éléments constitutifs sont :

  • Des objets, représentés comme dans du code JavaScript, entre accolades contenant des propriétés :

    • Un nom de propriété sous la forme d'une chaîne de caractères. Contrairement à du code JavaScript, le nom de chaque propriété doit être noté entre guillemets.
    • Une valeur, soit :

      • Une chaîne de caractères entre guillemets, avec certains caractères d'échappements possibles.
      • Un nombre.
      • Un booléen (true ou false).
      • La valeur vide null.
      • Un objet.
      • Un tableau.

      Les propriétés sont séparées par des virgules.

  • Des tableaux, représentés par des crochets, contenant des objets séparés par des virgules.

Les espaces blancs (retours à la ligne, tabulations, etc) ne sont généralement pas pris en compte.

Par exemple, les données représentées dans le fichier XML suivant :

<?XML version="1.0" encoding="UTF-8".>
<classe>
    <cours>Développement Internet</cours>
    <etudiants>
        <etudiant>
            <no>23456</no>
            <nom>Dupont</nom>
            <prenom>Marc</prenom>
        </etudiant>
        <etudiant>
            <no>24235</no>
            <nom>Durand</nom>
            <prenom>Hélène</prenom>
        </etudiant>
    </etudiants>
</classe>

Pourraient être représentées via le fichier JSON :

{
    "classe": {
        "cours": "Développement Internet",
        "etudiants": [
            {"no": 23456, "nom": "Dupont", "prenom": "Marc"},
            {"no": 24235, "nom": "Durant", "prenom": "Hélène"}
        ]
    }
}

Utilisation de JSON en JavaScript :

La plupart des langages actuels offrent un support à JSON : nous allons aborder l'utilisation de JSON avec JQuer, comme il pourrait être utilisé avec AJAX.

Nous allons ici construire une page HTML dotée de trois boutons comme ci-dessous !

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Exemple JSON<title>
        <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
        <script type="text/javascript" src="buttons.js"></scripts>
    </head>
    <body>
        <div id="personne">
            <input type="button" id="b1" value="getPersonne">
        </div>
        <div id="personnes">
            <input type="button" id="b2" value="getPersonnes">
        </div>
        <div id="personnesAjax">
            <input type="button" id="b3" value="getPersonnesAjax">
        </div>
    </body>
</html>

Avecle fichier personnes.json :

[
    {"nom": "TonNom", "prenom": "TonPrenom"},
    {"nom": "SonNom", "prenom": "SonPrenom"}
]

Et le script buttons.js :

varjson1 = '{"nom": "MonNom", "prenom": "MonPrenom"}';
var json2 = '[{"nom": "MonNom", "prenom": "MonPrenom"}, \
            {"nom": "SonNom", "prenom": "SonPrenom"}]';
$(document).ready(() => {
    $("#b1").click(() => {
        var une Personne = $.parseJSON(json1);
        $("#personne").html("<p class='personne'>" + unePersonne.nom + ", " + unePersonne.prenom + "</p>");
    });
    $("#b2").click(() => {
        var desPersonnes = $.parseJSON(json2);
        var liste = "<ul>";
        $.each(desPersonnes, (idx, personne) =>) {
            liste += "<li classe='personne'>" + personne.nom + ", " + personne.prenom + "</li>";
        });
        liste += "</ul>";
        $("#personnes").html(liste);
    });
    $("#b3").click(() => {
        $.ajaxSetup({mimeType: "text/plain"});
        $.getJSON("personnes.json", (desPersonnes) =>) {
            var liste = "<ul>";
            $.each(desPersonnes, (key, personne) =>) {
                liste += "<li classe='personne'>" + personne.nom + ", " + personne.prenom + "</li>";
            });
            liste += "</ul>";
            $("#personnesAajx").html(liste);
        });
    });
});

Pour les deux premiers boutons, un gestionnaire d'événement onclick est assigné comme nous l'avons déjà fait. La fonction $.parseJSON() de jQuery permet de transformer une chaîne de caractères déjà stockée dans une variable, et de transformer celle-ci en un objet JavaScript, si le format de la chaîne est un format JSON valide.

Pour le troisième bouton, la fonction de jQuery $.getJSON() est appelée à la place. Cette fonction, déjà vue précédemment, envoie unerequête GET à l'adresse renseignée, reçoit une réponse qui est supposée au format JSON, et convertit celle-ci automatiquement en un objet JavaScript (en appelant implicitement la méthode parseJSON()).

HTTPServlet :

Le front-end émet des requêtes (page web, fichiers JavaScript, CSS, ...) en format HTTP 1.1.

Anatomie d'une URL :

anatomie d'une URL

Il nous faut un serveur pour écouter les requêtes Web et y répondre.

Le composant de Java dédié au traitement d'une requête, HTTPServlet, répond à une requête sur un chemin précis et s'utilise au sein d'un serveur Web.

Un serveur web traite en général beaucoup de requêtes différentes : de multiples HTTPServlet sur un seul serveur.

Configuration :

Il faut configurer le serveur web pour expliquer à Java son comportement :

  • Quel port écouter
  • Quel(s) chemin(s) dans l'URL correspond(ent) à quel(s) servlet(s)
  • À quel endroit trouver les fichiers à servir.
  • S'il y a un fichier index.html à utiliser en cas d'absence de précision (www.monsite.com servira www.monsite.com/index.html)
  • Et plein d'autres...

Jetty :

En Java, sur un unique serveur Web, on peut exécuter plusieurs applications distinctes. On appelle cela un "Application Server". Chaque application est distinguée par son chemin d'URL (www.monsite.com/app1/...). On "déploie" son application server.

Nous utiliserons Jetty,la solution open source 100% Java, qui implémente les dernières technologies.

Sur un Application Server, chaque application poss!de son propre web.xml (description XML de la configuration de l'application) la concernant.

À la place d'avoir un Application Server sur leqel on déploie les applications, on a un processus Java qui embarque (embedded) son propre Application Server pour y déployer son unique application.

Moins souple qu'un Application Server, mais beaucoup plus pratique lors du développement. Parce qu'il n'y a pas de phase de déploiement, c'est une pure application Java qui s'exécute. Les outils de debugging habituels restent 100% fonctionnels.

Nomenclature :
  • Server → ce qui écoute un port TCP.
  • WebAppContext → ce qui configure le serveur en tant qu'Application Server.
  • ServletHolder → retient le nom et la configuration d'une instance de Servlet.
  • HTTPServlet → classe à étendre pour répondre aux requêtes HTTP.
Server server = new Server(8080);	// serveur sur port 8080
WebAppContext context = new WebAppContext();	// objet de configuration
HTTPServlet ms = new MaServlet();	// MaServlet réponds à une requête et il faut définir cette classe !
context.addServlet(new ServletHolder(ms), "/masv/*");	// ms traîtera les requêtes sur le chemin /masv/
context.setResourceBase("www");	// répertoire des fichiers html etc. !!! chemin relatif → à la racine du projet !!!
HTTPServlet ds = new DefaultServlet();	// ce servlet est fourni par Jetty, il répond aux requêtes pour obtenir les fichiers html etc.
context.addServlet(new ServletHolder(ds), "/");	// ds servira les fichiers pour tous les chemins. Comme il est ajouté après ms, sur le chemin /masv/ : ms est prioritaire.
server.setHandler(context);	// donne la configuration au serveur
server.start();	// démarrage

WebAppContext : quelques méthodes :

  • context.setContentPath("/") : Chemin de l'URL pour lequel ce contexte s'applique.
  • context.setWelcomeFiles(new String[] { "index.html" }); : Quel est le fichierà servir si l'utilisateur va à l'URL racine sans plus de précision. www.monsite.com affichera www.monsite.com/index.html.
  • context.setInitParameter("cacheControl", "no-store,no-cache,must-revalidate"); : Dans le protocole HTTP, le serveur dicte le comportement du cache du navigateur. Ici, on dit que par défaut, il faut pas stocker ni retenir en cache les réponses aux requêtes.
  • context.setInitParameter("redirectWelcome", "true"); : Quand c'est "true", la page de bienvenue est affichée par redirection plutôt que par suivi. En d'autres termes, l'URL change à la page de bienvenue. Dans tous les cas, le contenu de cette page sera affichée.
  • context.setMaxFormContentSize(50000000); : Spécifie la taille limite des données qu'un front-end peut soumettre à ce back-end.
  • context.addServlet(new ServletHolder(new MaServlet()), "/url1"); : Enregistre une servlet pour répondre à l'url /url1 exactement.
  • context.addServlet§new ServletHolder(new MaServlet()), "/url2/*); : Enregistre une servlet pour répondre à toute url commençant par /url2. Si plusieurs servlets sont ajoutées, chaque requête est traitée dans l'ordre de cet ajout, jusqu'à trouver une servlet qui accepte de la traiter.
  • context.addServlet(new ServletHolder(new DefaultServlet()), "/"); : La DefaultServlet sert des focjoers. Ici, toutes les URLs seront traitées comme des fichiers à trouver puisque cela commence à / directement. C'est pour cela qu'on ajoute la DefaultServlet en dernier en général.
  • context.setResourceBase("c:\web"); : spécifie dans quel répertoire se trouvent les fichiers.

La classe HTTPServlet a pour but d'être étendue. Chaque requête HTTP appelera la méthode suivante : service (HttpServletRequest i, HttpServletResponse o).

  • HttpServletRequest : contient tout ce qui concerne la requête (son chemin, l'adresse de l'émetteur, les cookies, les données transmises, etc...).
  • HttpServletResponse : possède les méthodes pour préparer la réponse à renvoyer au front-end.

En général, on ne redéfinit pas directement service :

  • doGet (HttpServletRequest i, HttpServletResponse o)
  • doPost (HttpServletRequest i, HttpServletResponse o)
  • doPut (HttpServletRequest i, HttpServletResponse o)
  • doDelete (HttpServletRequest i, HttpServletResponse o)
  • ...

Front-End → Back-End :

La question est composée de :

Back-End → Front-End :

La réponse est composée de :

  • Un code d'état : 200 = ok, 400 = manquant etc
  • Des données
  • Un mimetype pour ces données
  • Des modifications de cookies (ajout/suppression/modification)
  • Des en-têtes, par exemple des informations sur la manière de cacher les entrées.
  • ...

Les méthodes des requêtes :

GET : obtenir une ressource.

  • L'URL entrée dans le navigateur effectue un GET sur cette adresse. Les paramètres sont encodés dans cet URL : ?param1=valeur1&param2=valeur2.
  • Bookmarkable.
  • En général, peut être mis en cache.

POST : soumettre une ressource.

  • Soumission d'un formulaire : les données sont envoyées en POST. Les paramètres sont encodés dans le "payload" qui est distinct de l'URL.
  • Non bookmarkable.
  • En général, ne peut pas être mis en cache : la réponse à la soumission d'un formulaire de ce formulaire et change donc à chaque fois.

DELETE, PUT :

  • utilisés dans les API Webs, dans le standard REST
  • peu voire pas du tout utilisés à partir d'un navigateur
public class MaServlet extends HttpServlet {
    
    @Override
    protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ...
    }
    
    @Override
    protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ...
    }
}

HttpServletRequest :

Tout ce qui concerne la requête. Quelques méthodes :

  • getPathInfo() : renvoie la partie d'URL supplémentaire à ce qui est configuré par la servlet.
  • getParameter(String) : obtient un des paramètres de la requête, c'est-à-dire des données transmises.
  • getCookie() : retourne les cookies.

HttpServletResponse :

Tout ce qui concerne la réponse. Quelques méthodes :

  • setStatus(int) : définit le statut de la réponse
  • setContentLength(int) : définit la longueur en bytes de la réponse
  • setCharacterEncoding(String) : définit l'encodage de la réponse ("utf-8')
  • setContentType(String) : définit le mimetype de la réponse
  • getOutputStream() : retourne l'OutputStream permettant d'écrire la réponse

Exceptions dans doGet ou doPost :

Un fichier non trouvé = erreur 404.

resp.setStatus(404);
resp.setContentType("text/html");
String msg = "<html><body>Fichier non trouvé</body></html>";
byte[] msgBytes = msg.getBytes("UTF-8");
resp.setContentLength(msgBytes.length);
resp.setCharacterEncoding("utf-8");
resp.getOutputStream().write(msgBytes);

Ceci est un traitement normal du côté Java, pas une exception jetée !

Si une exception s'échappe, c'est Jetty lui-même qui la gérera (redirection vers une page d'erreur ou envoi d'un message standard).

En général, on évitera de laisser s'échapper les exceptions.

@Override
protected void doPost (HttpServletRequest req, HttpServletReponse resp) throws ServletException, IOException {
    try {
        ...
    } catch (Exception e) {
        e.printStackTrace(); // pour logger l'erreur
        resp.setStatus(500); // erreur au niveau du serveur
        resp.setContentType("text/html");
        byte[] msgBytes = e.getMessage().getBytes("UTF-8");
        resp.setContentLength(msgBytes.length);
        resp.setCharacterEncoding("utf-8");
        resp.getOutputStream().write(msgBytes);
    }
}

Plusieurs librairies Java permettent de transformer facilement du JSON en Java comme Genson.

Genson peut faire la désérialisation (transformation de JSON vers du Java) et la sérialisation (transformation de Java vers du JSON).

On peut instancier Genson directement :

Genson genson = new Genson();

ou via un Builder pour plus de configuration :

Genson genson = new GensonBuilder().useDateFormat(new SimpleDateFormat("yyyy-MM-dd")).useIdentation(true).useConstructorWithArguments(true).create();

Deux modes de fonctionnement :

  • Mode par défaut (marche dans les deux sens) :

    • JSON object ↔ Java Map
    • JSON array ↔ Java aray
    • JSON number ↔ Java double/int
    • JSON string ↔ Java String
  • Mode POJO/Beans :

    public class Person {
        
        private String name;
        private int age;
        private Address address;
        
        public Person() {}
        
        public Person (String name, int age, Address address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
        // getters & setters
    }
        
    public class Address {
    
        public int building;
        public String city;
        
        public Address() {}
        
        public Address (int building, String city) {
            this.building = building;
            this.city = city;
        }
    
    }
    • Marche aussi dans les deux sens :

      Person someone = new Person("Eugen", 28, new Address(157, "Paris"));
      ↔
      {"address":{"building":157,"city":"Paris"},"age":28,"name":"Eugen"}
    • Mais, dans le sens JSON → Java, il faut préciser la classe Java cible !

      int[] arrayOfInts = new int[] {1, 2, 3};
      // json = [1, 2, 3]
      String jsp, = genson.serialize(arrayOfInts);
      genson.deserialize(json, int[].class);

    On note que la transformation de Java → JSON est totalement automatique. Dans l'autre sens, il faut préciser vers quelle classe désérialiser. En effet, l'information de classe est nécessaire en Java mais n'existe pas en JSON.

    Map<String, Object> person = new HashMap<String, Object>()
    {{
        put("name", "Foo");
        put("age", 28);
    }};
    // {"age":28,"name":"Foo"}
    String json = genson.serialize(person);

    La sérialisation d'une classe anonyme avec un bloc d'initilaisation est automatique.

Utilisation avancée : Filtrer/renommer les propriétés :

new GensonBuilder().exclude("password").exclude(Class.class).include("name", FromSome.class).rename("type", "_type").create();

L'authentification :

L'authentificaion consiste à identifier un utilisateur.

Les requêtes du front-end dépendent de l'utilisateur authentifié. Les requêtes sont facilement manipulables.

Le serveur connaît cet utilisateur au moment de son authentification. Le serveur doit donc trouver un moyen de retenir l'utilisateur une fois qu'il est authentifié pour répondre aux requêtes ultérieures correctement.

Session :

L'application Server (Jetty) va retenir une conversation (ou session, qui est plusieurs requêtes successives du même front-end) entre un front-end et un back-end.

Attention : cependant, une conversation n'est pas une connexion TCP : l'Application Server fait de son mieux pour la tenir aussi exacte que possible, mais peut se tromper. Exemple typique : lors du redémarrage de l'Application Server, toutes les sessions disparaissent.

La méthode getSession() de l'HttpServletRequest permet d'obtenir la HttpSession.

Il faut appeler getSession() pour qu'une session soit créée. Une requête qui est traitée par le serveur sans que ce dernier n'appelle getSession() ne posèdera donc pas de session. Une fois la session créée, getSession() retourne la même session jusqu'à son expiration.

Lors d'une authentification réussie, la session est remplie par l'id de l'utilisateur : req.getSession().setAttribute("id", idUser);.

Lors des requêtes ultérieures, le serveur récupère l'id à partir de la session : Object idUser = req.getSession().getAttribute("id); if (idUser != null) {...}.

Gestion de l'authentification : JSON Web Token (JWT) :

C'est une chaîne de caractères qui retient les informations d'authentification de l'utilisateur et qui est signée cryptographiquement par le serveur.

Seul le serveur peut générer et valider un JWT valide.

Création de la chaîne JWT lors d'une authentification réussie :

Map<String, Object> claims = new HashMap<String, Object>();
claims.put("username", email);
claims.put("id", id);
claims.put("ip", req.getRemoteAddr());
String ltoken = new JWTSigner(JWTSECRET).sign(claims);

JWTSECRET est une constante chaîne de caractères connue uniquement par le serveur. C'est cette chaîne qui permet de signer/valider une signature.

Le navigateur devra donc founir la chaîne JWT à chacune de ses requêtes. Si on retient le JWT dans un cookie : cela sera automatique :

Cookie cookie = new Cookie("user", ltoken);
cookie.setPath("/");
cookie.setMaxAge(60 * 60 * 24 * 365);
resp.addCookie(cookie);

Lors des requêtes ultérieures, on pourra chercher la chaîne JWT dans les cookies :

String token = null;
Cookie[] cookies = req.getCookies();
if (cookies != null) {
    for (Cookie c : cookies) {
        if ("user".equals(c.getName()) && c.getSecure()) {
            token = c.getValue();
        } else if ("user".equals(c.getName()) && token == null) {
            token = c.getValue();
        }
    }
}

Et finalement on pourra valider la chaîne JWT avant de récupérer l'utilisateur :

Object userID = null;
try {
    Map<String, Object> decodedPayload = new JWTVerifier(JWTSECRET).verify(ltoken);
    userID = decodedPayload.get("id");
    if (!remoteHost.equals(decodedPayload.get("ip"))) userID = null;
} catch (Exception exception) {
    // ignore
}
if (userID != null) { // ici on a pu récupérer un utilisateur valide
    ...
} else {
    // ici pas : envoi d'une erreur ou redirection vers page d'authentification ou autre...
    ...
}

On peut mettre un id, un nom, une date de préremption, l'IP du front-end, etc... On ne met surtout pas le mot de passe de l'utilisateur du JWT !

Une chaîne JWT ne peut être créée que par le serveur (signée sur base de son secret caché). Sa signature ne peut être validée que par le serveur (toujours su base de son secret caché). Mais elle est facilement décrytable, le front-end peut en lire le contenu facilement sans même connaître le secret.

Informations supplémentaires :

  • http://127.0.0.1/Calc?expr=2+2

    Ce que l'utilisateur met comme expression arithmétique finit par se trouver dans l'URL. Il pourrait donc en profiter pour abuser le comportement de cette URL en y mettant des caractères non prévus, par exemple &. Tout paramètre doit donc être encodé avec, par exemple, encodeURIComponent(str). La fonction encodeURIComponent(str) permet d'encoder un composant d'un Uniform Resource Identifier (URI) en remplaçant chaque exemplaire de certains caractères par une, deux, trois ou quatre séquences d'échappement UTF-8 correspondantes (quatre séquences seront utilisées uniquement lorsque les caractères à encoder sont composés de deux caractères "surrogate").

    console.log(encodeURIComponent('?x=test'));
    // expected output : "%3Fx%3Dtest"
  • En Java 8, c'est la classe Files qui permet de lire ou écrire un fichier avec un minimum d'effort, ou de lister le contenu d'un répertoire.

    Par exemple, pour lire le contenu du fichier "cal.json" :

    String contenu = new String(Files.readAllBytes(Paths.get("calc.json")), StandardCharsets.UTF_8);

    Pour avoir la liste des fichiers du répertoire "machines" :

    String[] fichiers = new File("machines").list();

    Pour écrire sur un fichier spécifique :

    Files.write(new File("machines", nomFichier).toPath(), nouveauContenu.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
  • Pour déconnecter l'utilisateur :

    if (req.getSession() != null) {
        req.getSession().invalidate();
    }
  • Evoyer une erreur dans la réponse :

    resp.sendError(HttpServletReponse.SC_BAD_REQUEST, "Action invalide");

Exercices :

JavaScript est un langage aux nombreux interpréteurs. Bien sûr chaque navigateur possède le sien mais on le retrouve aussi du côté serveur avec Node.js par exemple. Pour le moment, nous utiliserons le moteur JavaScript fournit par Java 8, qui s'appelle Nasshorn.

Prenez le fichier Nasshom.zip se trouvant sur l'ecampus, décompressez-le etimporter le projet Eclipse : File/Import/General/Existing Project Into Workspace/Browse/ pointez vers le répertoire que vous avez dézippé, puis Next etc. jusqu'à terminer l'import.

Exécutez la classe Nasshom.

Dans la console, entrez 1+ 1, faites Enter et hop, vous avez le résultat.

Le moteur JavaScript possède ce qu'on appelle un store : un espace de stockage auquel on ajoute de l'information.

Entrez : function hello(){return "hello";}

La fonction est ajoutée au store. Entrez hello() et elle est appelée.

Ce serait plus pratique d'enregistrer tout cela dans un fichier. réez un fichier hello.js à la racine (le répertoire src n'est pas la racine de votre projet mais son répertoire parent l'est par contre !) de votre projet Eclipse et copiez-y la définition de la fonction hello ci-dessus. Pour charger le fichier, il faut faire : > file.loadScript("hello.js").

Le script est alors chargé et exécuté immédiatement.

À noter : chaque chargement ajoute au store déjà existant. Si vous désirez nettoyer complètement le store, il faut faire : > file.reload().

Utiisation avancée :

À chaque sauvegarde du fichier, il faut donc faire un location.reload suivi d'un file.loadScript pour obtenir le résultat de l'exécution de la nouvelle version. Ceci devenant vite lassant, on peut faire : > file.reloadScript("hello.js").

Une fois cette commande exécuté, le système surveille le fichier hello.js. À chaque fois qu'il est modifié (à chaque fois que vous sauvez une nouvelle version dans Eclipse), le location.reload et le file.loadScript sont automatiquement refait pour vous.

Exercice n° 1 :

  1. Écrivez une boucle qui va de 1 à 100. Imprimez chaque numéro à la console. C'est console.log(variable) qui permet d'afficher variable à la console.

  2. Écrivez une boucle qui va de 1 à 100. Imprimez chaque numéro à la console, mais les chiffres multiples de 3 sont remplacés par Fizz, ceux multiples e 5 par Buzz et ceux multiples d'à la fois de 3 et 5 sont remplacés par FizzBuzz.

    Nota : x est multiple de y ↔ le restant de la division entière de x par y zéro. En JavaScript, c'est l'opérateur % (appelé modulo) qui calcule le restant d'une division entière.

  3. Transformons la solution de l'exercice précédenten une fonction appelée fizzBuzz et prenant le nombre d'itérations en paramètre. Appeler fizzBuzz(100) va itérer sur les 100 premiers nombres, tandis que fizzBuzz(200) sur les 200, etc. Sauvez cette fonction dans un fichier ex1c.js à la racine de votre projet. Pour la charger : > file.loadScript("ex1c.js").

    Notez le résultat : <function> qui indique que la dernière chose évaluée lors de l'exécution de ce script est une fonction. Appelez-la : > fizzBuzz(200).

function fizzBuzz(n) {
    for (var i = 1; i <= n; i++) ç
        if (i % 3 === 0 && i % 5 === 0) {
            console.log("FizzBuzz");
        } else if (i % 3 === 0) {
            console.log("Fizz");
        } else if (i % 5 === 0) {
            console.log("Buzz");
        } else {
            console.log(i);
        }
    }
}

Exercice n° 2 :

Nous allons demander à l'ordinateur de jouer à Ding Ding Bottle. Les règles sont les suivantes :

  • Les multiples de 5 sont remplacés par Ding Ding.

  • Les multiples de 7 et les chiffres se terminant par 7 sont remplacés par Bottle. Donc 7, 14, 21, ... mais aussi 17, 27, 37, ...

  • Les multiples de 5 et 7 sont remplacés par Ding Ding Bottle.

  • Le jeu se joue autour d'une table; chaque fois qu'un joueur a dit son chiffre ou son emplacement, c'est au joueur qui le suit dans le sens actuel de jeu qui doit s'occuper du chiffre suivant. Cependant,lorsqu'un Bottle est dit, le sens s'inverse ! C'est donc alors au joueur précédent dans l'ancien sens de rotation qui s'occupe du chiffre suivant.

  1. Nous allons d'abord implémenter un joueur :

    • Il possède un nom. Cette valeur est déterminée une fois pour toute.

    • Il possède un joueur à sa droite et un joueur à gauche. Ces valeurs peuvent être changées n'importe quand.

    • Il possède une fonction pour s'occuper d'un chiffre. Cette fonction reçoit le chiffre et la référence de qui l'appelle : ce sera soit son joueur de droite, soit son joueur de gauche. Cette seconde information permet de déterminer le sens du jeu. N'oubliez pas d'inverser ce sens en cas de Bottle !

    • Cette fonction affiche le nom du joueur suivi de ce qu'il doit dire, puis elle passe la main au joueur suivant. Quand le chiffre à afficher est plus grand que 100, cette fonction ne fait plus rien.

    Implémentez ceci en utilisant la technique de pseudo-classe vue dans les diapos.

  2. Créons une partie en adaptant le code suivant en fonction de votre propre code :

    var a = joueur('a');
    var b = joueur('b');
    var c = joueur('c');
    a.setDroite(b);
    b.setDroite(c);
    c.setDroite(a),
    a.setGauche(c);
    c.setGauche(b);
    b.setGauche(a);
    a.traitement(1, c);

    Notez la dernière ligne : elle demander au joueur a de traiter la valeur 1, et en déclarant c comme étant son prédécesseur afin d'imposer le sens initial a → b → c → a → ...

    Le résultat devvait être :

  3. Améliorons le programme : si b et à droite de a, alors a est à gauche de b. Automatisez cela chaque fois qu'on appelle setDroite ou setGauche, l'élément dans l'autre sens est aussi automatiquement placé. Comment faire pour éviter d'avoir une boucle infinie : a.setDroite(b) → b.setGauche(a) → a.setDroite(b) → ...

    C'est possible de trouver une solution sans introduire de getter sur les droites et gauches, et sans introduire de fonctions supplémentaires.

  4. Même s'il ne faut plus qu'utiliser setDroite ou setGauche, configurer une table de jeu reste pénible. Créons la fonction dingdingbottle qui prend un nombre arbitraire de paramètres. Chaque paramètre est un nom de joueur. La fonction configure une table avec ces joueurs dans l'ordre dans lesquels ils sont fournis, le dernier étant relié au premier. Ensuite, elle lance la partie. Ainsi : > dingdingbottle("a", "b", "c") donne de nouveau le résultat de la photo précédente.

    Pour réaliser cette fonction, nous aurons besoin de la variable appelée arguments : c'est une variable de type tableau et automatiquement définie dans les fonctions. Elle contient l'ensemble des arguments passés en paramètre à la fonction arguments[0] sera le premier paramètre, arguments[1] le second, arguments.length donnera le nombre d'arguments réellement passés, etc...

var joueur = function(nom) {

	var droite, gauche;
	
	function setGauche(joueurGauche) {
		if (gauche === undefined) {
			gauche = joueurGauche;
			gauche.setDroite(self);
		}
	}
	
	function setDroite(joueurDroite) {
		if (droite === undefined) {
			droite = joueurDroite;
			droite.setGauche(self);
		}
	}
	
	function traitement(chiffre, joueur) {
		if (chiffre <= 100) {
			var changerSens = afficher(chiffre);
			var suiv = joueur;
			if (!changerSens) {
				suiv = (joueur === gauche) ? droite : gauche;
			}
			suiv.traitement(chiffre + 1, self);
		}
	}
	
	function afficher(chiffre) {
		var sysout = chiffre;
		if (chiffre % 5 === 0 && chiffre % 7 === 0) {
			sysout = "Ding Ding Bottle";
			console.log(nom + " : " + sysout);
			return true;
		}
		if (chiffre % 7 === 0 || chiffre % 10 === 7) {
			sysout = "Bottle";
			console.log(nom + " : " + sysout);
			return true;
		}
		if (chiffre % 5 === 0) {
			sysout = "Ding Ding";
		}
		console.log(nom + " : " + sysout);
		return false;
	}
	
	var self = {
		setDroite : setDroite,
		setGauche : setGauche,
		traitement : traitement
	};
	
	return self;
};

function dingdingbottle() {
	var joueurs = [];
	for (var i = 0; i < arguments.length; i++) {
		joueurs.push(joueur(arguments[i]));
	}
	for (var i = 0; i < arguments.length; i++) {
		joueurs[i].setDroite(joueurs[i + 1]);
	}
	joueurs[arguments.length - 1].setDroite(joueurs[0]);
	joueurs[0].traitement(1, joueurs[arguments.length - 1]);
};

dingdingbottle("a", "b", "c");

Exercice n° 3 :

Jouons à Puissance 4.

  1. Créez un pseudio-objet grille.

    1. Cette grille contient 7 colonnes de 6 cases. Chaque case contient soit : rien, un pion jaune ou un pion rouge.

    2. Une fonction imprime la grille. Utilisez console.log. Comme cette fonction ne permet pas d'afficher de la couleur, utilisez X pour les pions jaunes et 0 pour les pions rouges.

    3. Une fonction permet de jouer en désignant une colonne et une couleur. Elle renvoie vrai si le coup est valide (il reste de la place dans la colonne), faux sinon. La case la plus basse de cetet colonne prend la couleur désignée.

    4. Une fonction interne (privée) vérifie s'il y a une suite de 4 pions de la même couleur dans chacune des colonnes. Elle retourne une valeur représentant rien, rouge ou jaune (par exemple un entier, ou un string ou ...).

    5. Une fonction interne (privée) vérifie s'il y a une suite de 4 poins de la même couleur dans chacune des lignes. Elle retourne une valeur représentant rien, rouge ou jaune.

    6. Une fonction interne (privée) vérifie s'il y a une suite de 4 pions de la même couleur en diagonale, dans les deux directions possibles. Elle retourne une valeur représentant rien, rouge ou jaune.

    7. Une fonction qui vérifie s'il y a un gagnant. Elle retourne une valeur représentant rien, rouge ou jaune.

    8. Une fonction qui vérifie s'il reste de la place dans la grille. Elle renvoie vrai quand la grille est remplie, faux sinon.

  2. Testez votre objet grille

    1. À l'aide de lignes de code, ajoutez des points à la grille et validez que son état est correct via sa fonction d'affichage.

    2. À l'aide de lignes de code, ajoutez des points à la grille pour créer une combinaison gagnante horizontalement. Validez que la fonction de vérification de gagnant détecte bien cette victoire.

    3. Idem pour une combinaison verticale.

    4. Idem pour une combinaison diagonale. En particulier, vérifier que cela fonctionne pour des diagonales pour lesquels un des points est un des quatre coins de la grille.

  3. Il est temps de jouer. Construisez une fonction de jeu.

    1. Cette fonction demande alternativement à deux joueurs d'insérer un pion de leur couleur. La fonction console.readln() retourne la chaîne de caractères rentrée par l'utilisateur. La fonction parseInt(s) retourne le Number équivalent à la chaîne de caractères passée en paramètre, ou null si la conversion n'a pas pi se faire (chaîne invalide).

    2. Après chaquepion posé, la fonction regarde s'il y a une victoire et l'affiche le cas échéant : le jeu est terminé. S'il n'y a pas pas de victoire, la fonction regarde si la grille est pleine. Dans ce cas, c'est une égalité et le jeu est aussi terminé. S'il n'y a ni ctoire ni égalité, le jeu boucle en passant au joueur suivant.

var nbrLignes = 6;
var nbrColonnes = 7;
var caseVide = ' ';
var jetonRouge = 'O';
var jetonJaune = 'X';
var suite = 4;
var jetonCourant = jetonRouge;
var joueurCourant = "red";

var jeu = function () {
    
    var grille = initialiserGrille();
    
    function initialiserGrille() {
        var tab = [];
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            tab.push([]);
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                tab[ligne].push(caseVide);
            }
        }
        return tab;
    }
    
    function afficher() {
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            var ligneAAfficher = ' | ';
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                ligneAAfficher += grille[ligne][colonne] + ' | ');
            }
            console.log(ligneAAfficher);
        }
    }
    
    function jouer(colonne, couleur) {
        colonne--;
        var ligne;
        for (ligne = 0; ligne < nbrLignes; ligne++) {
            if (grille[ligne][colonne] !== caseVide) {
                break;
            }
        }
        if (ligne === 0) {
            return false;
        }
        grille[ligne - 1][colonne] = couleur;
        changerJoueur();
        return true;
    }
    
    function checkedColonne() {
        for (var colonne = ; colonne < nbrColonnes; colonne++) {
            for (var ligne = ; ligne <= nbrLignes - suite; ligne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne + 1][colonne] && grille[ligne][colonne] === grille[ligne + 2][colonne] && grille[ligne][colonne] === grille[ligne + 3][colonne]) {
                    return grille[ligne][colonne];
                }
            }
        }
        return false;
    }
    
    function checkedLigne() {
        for (var colonne = ; colonne <= nbrColonnes - suite; colonne++) {
            for (var ligne = ; ligne < nbrLignes; ligne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne][colonne + 1] && grille[ligne][colonne] === grille[ligne][colonne + 2] && grille[ligne][colonne] === grille[ligne][colonne + 3]) {
                    return grille[ligne][colonne];
                }
            }
        }
        return false;
    }
    
    function checkedD1() {
        for (var ligne = 0; ligne <= nbrLignes - suite; ligne++) {
            for (var colonne = 0; colonne <= nbrColonnes - suite; colonne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne - 1][colonne -1] && grille[ligne][colonne] === grille[ligne - 2][colonne - 2] && grille[ligne][colonne] === grille[ligne - 3][colonne -3]) {
                    return grille[ligne][colonne];
                }
        }
        return false;
    }
    
    function checkedD2() {
        for (var ligne = 0; ligne <= nbrLignes - suite; ligne++) {
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne + 1][colonne -1] && grille[ligne][colonne] === grille[ligne + 2][colonne - 2] && grille[ligne][colonne] === grille[ligne + 3][colonne -3]) {
                    return grille[ligne][colonne];
                }
        }
        return false;
    }
    
    function gagnant() {
        var gagnant;
        if ((gagnant = checkedLigne()) !== false) {
            return gagnant;
        }
        if ((gagnant = checkedColonne()) !== false) {
            return gagnant;
        }
        if ((gagnant = checkedD1()) !== false) {
            return gagnant;
        }
        if ((gagnant = checkedD2()) !== false) {
            return gagnant;
        }
        return false;
    }
    
    function grilleRemplie() {
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                if (grille[ligne][colonne] === caseVide) {
                    return false;
                }
            }
        }
        return true;
    }
    
    function changerJoueur() {
        if (joueurCourant === 'red') {
            jetonCourant = jetonJaune;
            joueurCourant = 'yellow';
        } else {
            jetonCourant = jetonRouge;
            joueurCourant = 'red';
        }
    }
    
    var self = {
        afficher : afficher,
        jouer : jouer,
        gagnant : gagnant,
        grilleRemplie : grilleRemplie
    };
    
    return self;
}

var game = jeu();
var numCol;
var gagnant;
while ((gagnant = game.gagnant()) === false && !game.grilleRemplie()) {
    console.log('C\'est aux ' + joueurCourant + ' de jouer');
    if (!isNan(numCol = parseInt(console.readln()))) {
        game.jouer(numCol, jetonCourant);
        game.afficher();
    }
}
if (game.grilleRemplie()) {
    console.log('C\'est égalité');
} else {
    console.log('gagant = ' + gagnant);
}

Exercice n° 4 :

Jouons à la bataille navale.

Cette fois l'énoncé sera moins, directif, à vous de voir ce qui doit être fait pour que cela fonctionne !

  1. Créez une pseudo classe grille composée de 10 lignes et 10 colonnes.

    1. Ajoutez les fonctions nécessaires à y placer les bateaux (4 bateaux de 2 cases, 3 de 3 cases, 2 de 4 cases et 1 de 5 cases).

    2. Ajoutez les fonctions permettant de tirer sur les bateaux.

    3. Ajoutez les fonctions permettant d'afficher l'état d'une grille. Une fonction affiche la grille du joueur qui voit ses propres bateaux, tandis que l'autre fonction n'affiche que les coups qu'il a joués et s'ils ont touchés ou pas.

    4. Ajoutez une fonction permettant de placer les bateaux aléatoirement. Pour information, Math.random() retourne un nombre compris entre 0 et 1 exclus.

    5. Créez une fonction déterminant s'il reste des bateaux vivants sur la grille.

  2. Testez votre grille.

  3. Construisez une fonction de jeu.

    1. Cette fonction instancie une grille pour l'ordinateur et une grille pour le joueur. Elle tire au hasard des positions pour les bateaux des deux grilles.

    2. À chaque tour, les deux grilles sont affichées; celles de l'ordinateur n'affiche que les coups joués par le joueur, tandis que l'autre affiche aussi ses propres bateaux.

    3. Quand c'est son tour, le joueur rentre la ligne puis ensuite il rentre la colonne du coup qu'il joue. L'ordinateur tire au hasard.

    4. À chaque tour, il faut vérifier si un des joueurs a gagné et ne continuer que tant que ce n'est pas le cas.

var nbrLignes = 10;
var nbrColonnes = 10;
var caseVide = ' ';
var joueurCourant = 'ordi';
var numCol, numLigne, etatGrilleJoueur, etatGrilleOrdi;

var jeu = function () {
    
    var bateaux = [5, 4, 4, 3, 3, 3, 2, 2, 2, 2];
    var grille = initialiserGrille();
    var etatGrille = initialiserGrille();
    placerBateaux();
    
    function initialiserGrille() {
        var tab = [];
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            tab.push([]);
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                tab[ligne].push(caseVide);
            }
        }
        return tab;
    }
    
    function afficherEtat() {
        var ligneAAfficher = '     0   1   3   4   5   6   7   8   9 \n';
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            ligneAAfficher += ligne + ' ';
            for (var colonne = 0; colonne < nbrColonnes; colonne) {
                ligneAAfficher += ' | ' + etatGrille[ligne][colonne];
            }
            ligneAAfficher += ' |\n';
        }
        console.log(ligneAAfficher);
        return etatGrille;
    }

    function afficherEtat() {
        var ligneAAfficher = '     0   1   3   4   5   6   7   8   9 \n';
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            ligneAAfficher += ligne + ' ';
            for (var colonne = 0; colonne < nbrColonnes; colonne) {
                ligneAAfficher += ' | ' + etatGrille[ligne][colonne];
            }
            ligneAAfficher += ' |\n';
        }
        console.log(ligneAAfficher);
    }
    
    function placerBateaux() {
        for (var i = 0; i < bateaux.length; i++) {
            placerBateau(bateaux[i]);
        }
    }
    
    function placerBateau(nbCases) {
        var ok = false;
        while (!ok) {
            var placesLibres = true;
            var inclinaison = Math.random();
            var ligne = Math.ceil(Math.random() * nbrColonnes) - 1;
            var colonne = Math.ceil(Math.random() * nbrLignes) - 1;
            if (inclinaison < 0.5) {
                for (var i = 0; i < nbCases; i++à {
                    if (ligne + nbCases >= nbrLignes || grille[ligne + i][colonne] !== caseVide) {
                        placesLibres = false;
                    }
                }
                if (placesLibres) {
                    for (var i = 0; i < nbCases; i++) {
                        grille[ligne + i][colonne] = 'X';
                    }
                    ok = true;
                }
            } else {
                for (var i = 0; i < nbCases; i++) {
                    if (colonne + nbCases >= nbrColonnes || grille[ligne][colonne + i] !== caseVide) {
                        placesLibres = false;
                    }
                }
                if (placesLibres) {
                    for (var i = 0; i < nbCases; i++) {
                        grille[ligne][colonne + i] = 'X';
                    }
                    ok = true;
                }
            }
        }
    }
    
    function tirer(ligne, colonne, grilleEnnemie) {
        var touvher = grille[ligne][colonne] === 'X';
        if (toucher) {
            grilleEnnemie.placerBonCoup(ligne, colonne);
        } else {
            grilleEnnemie.placerMauvaisCoup(ligne, colonne);
        }
        return toucher;
    }
    
    function placerBonCoup(ligne, colonne) {
        etatGrille[ligne][colonne] = 'X';
    }
    
    function placerMauvaisCoup(ligne, colonne) {
        etatGrille[ligne][colonne] = 'O';
    }
    
    function existeBateau() {
        for (var i = 0; i < nbrLignes; i++) {
            for (var j = 0; j < nbrColonnes; j++) {
                if (grille[i][j] === 'X' && etatGrille[i][j] !== grille[i][j]) {
                    return true;
                }
            }
        }
        return false;
    }
    
    var self = {
        afficherEtat : afficherEtat,
        afficherGrille : afficherGrille,
        existeBateau : existeBateau,
        tirer : tirer,
        placerBonCoup : placerBonCoup,
        placerMauvaisCoup : placerMauvaisCoups
    };
    
    return self;
}

var gameOrdi = jeu();
var gameJoueur = jeu();
console.log('Grille de l\'ordi :');
gameOrdi.afficherGrille();
console.log('Grille du joueur :');
gameJoueur.afficherGrille();
while (gameOrdi.existeBateau() && gameJoueur.existeBateau()) {
    console.log('Grille état de l\'ordi' :);
    etatGrilleOrdi = gameOrdi.afficherEtat();
    console.log('Grille état du joueur :');
    etatGrilleJoueur = gameJoueur.afficherEtat();
    while ('ordi' === joueurCourant) {
        numLigne = Math.ceil(nbrLignes * Math.random()) - 1;
        numCol = Math.ceil(nbrColonnes * Math.random()) -1;
        if (etatGrilleJoueur[numLigne][numCol] === caseVide) {
            gameOrdi.tirer(numLigne, numCol, gameJoueur);
            joueurCourant = 'joueur';
        }
    }
    while ('joueur' === joueurCourant) {
        numLigne = parseInt(console.readln());
        numCol = parseInt(console.readln());
        if (!isNaN(numLigne) && !isNaN(numCol) && etatGrilleOrdi[numLigne][numCol] === caseVide) {
            gameJoueur.tirer(numLigne, numCol, gameOrdi);
            joueurCourant = 'ordi';
        }
    }
}
if (!gameOrdi.existeBateau()) {
    console.log('Le joueur a gagné !');
} else {
    console.log('L\'ordi a gagné !');
}

Exercice n° 4 :

Nous allons porter le Puissance 4 en HTML :

  • Créez une page qui contient une table de 7x7 cellules.

  • Sur les six premières rangées, chaque cellule contient un <div> dont le width et height est de 100%.

  • Sur la dernière rangée, chaque cellule contient un <button> dont le width et height est de 100%. De plus, le onclick de chaque bouton appelle la fonction joue avec en paramètre le numéro de colonne. Donc, onclick="joue(1)" pour le premier bouton, onclick="joue(2)" pour le second bouton etc.

  • À la fin de la page, le fichier puissance4.js est chargé. Vous pouvez récupérer votre code code de puissance4 en console comme base de travail.

  • L'affichage de l'état du jeu passe maintenantpar le DOM, écrivez une fonction pour le réaliser :

    • Il faut trouver la table, par exemple en utilisant getElementsByTagName.

    • Il faut parcourir cette table pour trouver les cellules. Utilisez l'inspecteur du browser pour bien visualiser la structure de l'arbre. Utilisez par exemple prop element childnodes.

    • Les <div> dans chacune des cellules doivent avoir leurs backgroundColor misent à blanc, rouge ou jaune en fonction de l'état du jeu.

  • Les tours de jeu ne peuvent plus être gérés par une boucle utilisant un console.readln() : ccette fonction n'existe pas dans un navigateur. Par contre, nous avons placé des boutons qui appelle la fonction joue et donnant en paramètre le numéro de colonne qui doit être joué. Implémentez cette fonction.

  • Bonus 1 : améliorer le programme en soignant la présentation avec le CSS, ajoutez un texte qui indique qui doit jouer, gérer de la fin du jeu avec la possibilité de relancer une partie, etc...

  • Bonus 2 : animons la descente d'un pion dans la grille. La manière d'effectuer cela en JavaScript est contre-intuitive : on ne peut pas faire une boucle qui passe le pion dans chacune des cases intermédiaires. Le résultat sera que le navigateur reste "gelé" durant toute la période de l'animation et seule la dernière étape est affichée. La raison est qu'un navigateur agit d'une manière mono-threadée : quand la boucle JavaScript s'exécute, rien d'autre ne se passe : pas d'intéraction avec l'utilisateur, pas de rafraïchissement de l'interface. Il faut donc "rendre" la main au navigateur, en terminant le traitement en cours (en laissant la fonction s'achever). Mais l'animation demande de reprendre la main après un certain temps pour afficher l'étape suivante. Nous utiliserons la fonction setTimeout(f, t) pour effectuer cela : f est une fonction qui sera appelée après t millisecondes. Ainsi, si nous avons besoin d'une boucle, nous pouvons écrire :

    function boucle (etape, max) {
        if (etape >= max) {
            return;
        }
        // gérer le traitement de l'étape n° etape
        setTimeout(function() {
            boucle(etape + 1, max);
        }, 100);
    }
    boucle(1, 10);

    Ainsi la fonction boucle sera appelée jusqu'à ce que l'étape soit supérieur ou égal au max. Chaque traitement s'effectue à la place du commentaire, c'est similaire au corps d'une boucle for. Pour que l'itération suivante se fasse, la fonction setTimeout est appelée. Le premier paramètre est une fonction qui appelle la fonction boucle en incrémentant le numéro d'étape. Le scond paramètre dit de le faire dans 100 ms.

Dans le fichier "puissance4.css" :

#grille td {
	width: 2cm;
	height: 2cm;
	border: 2px solid black;
	border-radius: 50%;
}

#grille div, button {
	width: 100%;
	height: 100%;
	border-radius: 50%;
}

button #reload {
	border-radius: unset;
	padding: 10px;
	width: initial;
	height: initial;
}

Dans le fichier "puissance4.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Puissance 4>
        <link rel="stylesheet" type="text/css" href="puissance4.css">
    </head>
    <body>
        <h1>C'est aux <span id="information">red</span> de jouer !!!</h1>
        <table id="grille">
            <tbody>
                <!-- 1 -->
                <tr>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                </tr>
                <!-- 2 -->
                <tr>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                </tr>
                <!-- 3 -->
                <tr>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                </tr>
                <!-- 4 -->
                <tr>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                </tr>
                <!-- 5 -->
                <tr>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                </tr>
                <!-- 6 -->
                <tr>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                    <td><div></div></td>
                </tr>
                <!-- 7 -->
                <tr>
                    <td><button onclick="joue(1)">1</button></td>
                    <td><button onclick="joue(2)">2</button></td>
                    <td><button onclick="joue(3)">3</button></td>
                    <td><button onclick="joue(4)">4</button></td>
                    <td><button onclick="joue(5)">5</button></td>
                    <td><button onclick="joue(6)">6</button></td>
                    <td><button onclick="joue(7)">7</button></td>
                </tr>
            </tbody>
        </table>
        <script type="application/javascript" src="puissance4.js"></script>
    </body>
</html>

Dans le fichier "puissance4.js" :

var nbrLignes = 6;
var nbrColonnes = 7;
var caseVide = ' ';
var jetonRouge = 'O';
var jetonCourant = jetonRouge;
var joueurCourant = 'red';
var suite = 4;
var info = document.getElementById('information');
var td = document.body.getElementsByTagName('td');

var jeu = function () {
    
    var grille = initialiserGrille();
    
    function initialiserGrille() {
        var tab = [];
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            tab.push([]);
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                tab[ligne].push(caseVide);
            }
        }
        return tab;
    }
    
    function afficher() {
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            var ligneAAfficher = ' | ';
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                ligneAAfficher += grille[ligne][colonne] + ' | ';
            }
            console.log(ligneAAfficher);
        }
    }
    
    function jouer(colonne) {
        colonne--;
        var ligne;
        for (ligne = 0; ligne < nbrLignes; ligne++) {
            if (grille[ligne][colonne] !== caseVide) {
                break;
            }
        }
        if (ligne === 0) {
            return false;
        }
        ligne--;
        grille[ligne][colonne] = jetonCourant;
        /* var nb = (ligne * nbrColonnes) + colonne;
        td[nb].style.backgroundColor = joueurCourant; */
        animer(ligne, 0, colonne);
        return true;
    }
    
    function animer(max, ligne, colonne) {
        if (ligne > max) {
            changerJoueur();
            return;
        }
        if (ligne - 1 >= 0) {
            var nbO = ((ligne - 1) * nbrColonnes) + colonne;
            td[nbO].style.backgroundColor = 'white';
        }
        var nbC= (ligne * nbrColonnes + colonne;
        td[nbC].style.backgroundColor = joueurCourant;
        setTimeOut(function() {
            animer(max; ligne + 1, colonne);
        }, 10);
    }
    
    function checkedColonne() {
        for (var colonne = ; colonne < nbrColonnes; colonne++) {
            for (var ligne = ; ligne <= nbrLignes - suite; ligne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne + 1][colonne] && grille[ligne][colonne] === grille[ligne + 2][colonne] && grille[ligne][colonne] === grille[ligne + 3][colonne]) {
                    return grille[ligne][colonne];
                }
            }
        }
        return false;
    }
    
    function checkedLigne() {
        for (var colonne = ; colonne <= nbrColonnes - suite; colonne++) {
            for (var ligne = ; ligne < nbrLignes; ligne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne][colonne + 1] && grille[ligne][colonne] === grille[ligne][colonne + 2] && grille[ligne][colonne] === grille[ligne][colonne + 3]) {
                    return grille[ligne][colonne];
                }
            }
        }
        return false;
    }
    
    function checkedD1() {
        for (var ligne = 0; ligne <= nbrLignes - suite; ligne++) {
            for (var colonne = 0; colonne <= nbrColonnes - suite; colonne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne - 1][colonne -1] && grille[ligne][colonne] === grille[ligne - 2][colonne - 2] && grille[ligne][colonne] === grille[ligne - 3][colonne -3]) {
                    return grille[ligne][colonne];
                }
        }
        return false;
    }
    
    function checkedD2() {
        for (var ligne = 0; ligne <= nbrLignes - suite; ligne++) {
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                if (grille[ligne][colonne] !== caseVide && grille[ligne][colonne] === grille[ligne + 1][colonne -1] && grille[ligne][colonne] === grille[ligne + 2][colonne - 2] && grille[ligne][colonne] === grille[ligne + 3][colonne -3]) {
                    return grille[ligne][colonne];
                }
        }
        return false;
    }
    
    function gagnant() {
        var gagnant;
        if ((gagnant = checkedLigne()) !== false) {
            return joueur(gagnant);
        }
        if ((gagnant = checkedColonne()) !== false) {
            return joueur(gagnant);
        }
        if ((gagnant = checkedD1()) !== false) {
            return joueur(gagnant);
        }
        if ((gagnant = checkedD2()) !== false) {
            return joueur(gagnant);
        }
        return false;
    }
    
    function grilleRemplie() {
        for (var ligne = 0; ligne < nbrLignes; ligne++) {
            for (var colonne = 0; colonne < nbrColonnes; colonne++) {
                if (grille[ligne][colonne] === caseVide) {
                    return false;
                }
            }
        }
        return true;
    }
    
    function joueur(jeton) {
        return (jeton === jetonRouge) ? 'red' : 'yellow';
    }
    
    function changerJoueur() {
        if (joueurCourant === 'red') {
            jetonCourant = jetonJaune;
            joueurCourant = 'yellow';
        } else {
            jetonCourant = jetonRouge;
            joueurCourant = 'red';
        }
    }
    
    var self = {
        afficher : afficher,
        jouer : jouer,
        gagnant : gagnant,
        grilleRemplie : grilleRemplie
    };
    
    return self;
}

var game = jeu();
var gagnant;
var button = '<br><button onclick="location.reload();" id="reload">Recharger la grille</button>
function joue(i) {
    game.jouer(i);
    info.innerHTML = joueurCourant;
    game.afficher();
    if (game.grilleRemplie) {
        info.innerHTML = 'C\'est égalité !' + button;
    }
    if ((gagnant = game.gagnant()) !== false) {
        info.innerHTML = 'Le gagnant est les "' + gagnant + '"' + button;
    }
}

Exercices 5 sur jQuery :

Préambule - Environnement de développement jQuery :

Consultez à l'aide de Notepad++ le code source de deux emples dans les répertoires suivants d'"Exercices 4" :

  • Base pour le développement

  • Hello World !

Faites-les fonctionner dans le navigateur Web de votre choix.

  1. À partir du code présenté dans le dossier "Exercice 4.1", écrivez un script jQuery pour :

    1. colorier le er élément de la liste en rouge

    2. colorier les éléments de la liste une ligne sur deux en vert et l'autre ligne sur deux en rouge.

    Dans le fichier "index.html" :

    <!DOCTYPE HTML>
    <html lang="fr">
        <head>
            <meta charset="utf-8">
            <title>Exercice 4.1</title>
        </head>
        <body>
            <div>
                <ul id="list">
                    <li>Élément 1 de liste</li>
                    <li>Élément 2 de liste</li>
                    <li>Élément 3 de liste</li>
                    <li>Élément 4 de liste</li>
                </ul>
            </div>
            <script src="jquery.js"></script>
            <script src="myscript.js"></script>
        </body>
    </html>

    Dans le fichier "myscript.js" :

    $(function() {
    	$('#list > li:first').css('color', 'red');
    	$('#list > li:even').css('color', 'green');
    	$('#list > li:odd').css('color', 'red');
    });
  2. À partir du code présenté dans le dossier "Exercice 4.2", écrivez un script jQuery pour :

    1. mettre le focus sur la 1ère zone de texte du formulaire

    2. colorier le background de tous les éléments de type input, textarea, select et button en #FAFAD2,

    3. sélectionner d'emblée le radio button Homme,

    4. dimensionner l'image à 300 pixels de largeur,

    5. qu'au clic dans la zone textarea, le contenu de celle-ci soit effacé,

    6. colorier le background de l'élément ayantle focus en #F0E68C,

    7. qu'au clic sur l'image, une boîte de dialoue s'ouvre et affiche "Sexe : H" ou "Sexe : F" selon le radio button sélectionné.

    Dans le fichier "index.html" :

    <!DOCTYPE HTML>
    <html lang="fr">
        <head>
            <meta charset="utf-8">
            <title>Formulaire</title>
        </head>
        <body>
            <form method="POST">
                Nom d'utilisateur
                <input type="text" name="nom"/><br>
                Mot de passe
                <input type="password" name="pass"/><br>
                Sexe<br>
                Hommme <input type=radio" name="sexe" value="H"/><br>
                Femme <input type=radio" name="sexe" value="F"/><br>
                Commentaires
                <textarea rows="3" name="commentaire">Entrez votre commentaire ici</textarea><br>
                <img id="image" src="chat.jpg"/><br>
                <input type="submit" value="Envoyer"/>
            </form>
            <script src="jquery.js"></script>
            <script src="myscript.js"></script>
        </body>
    </html>

    Dans le fichier "myscript.js" :

    $(function() {
    	$('input[type="text"]:first').focus();
    	$('input, textarea, select, button').css('backgroundColor', '#FAFAD2');
    	$('input[type="radio"][value="H"]').prop('checked', true);
    	$('#image').css('width', '300px');
    	$('textarea').on('click', function(event) {
    		$(event.currentTarget).html('');
    		$(this).html('');
    	});
    	$(':input').on('focus', function() {
    		$(this).css('backgroundColor', '#F0E68C');
    	});
    	$(':input').on('blur', function() {
    		$(this).css('backgroundColor', '#FAFAD2');
    	});
    	$('#image').click(function() {
    		alert('Sexe : ' + $(':radio:checked').val());
    	});
    });

Exercice n° 6 en jQuery :

Objectifs :

  • Être capable de gérer des événements en jQuery,

  • Être capable d'écrire des fonctions,

  • Être capable d'insérer des éléments dans le DOM.

Préambule - Base de développement de l'exercice 5 :

Consultez à l'aide de Notepad++ le code source présenté dans le répertoire "Base pour le développement de l'exercice 5".

Faites-le fonctionner dans le navigateur Web de votre choix.

Il s'agit d'une page Web avec un champ de saisie de type texte (textfield), deux boutons et une table à une seule case.

C'est une base de travail pour la créaton d'un mini-tableur.

Étape 1 - Insertion d'une colonne :

La première étape est d'insérer une colonne. Lorsque l'utilisateur appuie sur le bouton "Ajouter une colonne" le script lit la valeur contenue dans le textfield et ajoute une colonne à la table avec pour contenu la valeur du textfield.

Remarque : avant d'écrire votre script, vous pouvez exécuter des sélections jQuery dans la console JavaScript de Chrome.

Étape 2 - Insertion d'une ligne :

Similaire à l'étape 1, ajoutez une ligne plutôt qu'une colonne.

Remarque : une ligne peut évidemment contenir une ou plusieurs colonnes.

Étape 3 - Ajout d'une colonne avec plusieurs lignes :

Vérifiez que lorsque vous ajoutez une colonne, celle-ci contient autant de lignes que le tableau. Si ce n'est pas le cas, adaptez votre code.

Étape 4 - Sélection d'une case :

Nous voulons maintenant que lorsque l'utilisateur clique sur une case, il puisse en modifier le contenu. Quand le contenu est modifié, le textfield "Valeur cellule :" est aussi modifé.

Piste de solution : Il faudra en fait ajouter un listener par case.

Illustration : modification de la case du tableau située en 2ème colonne, 2ème ligne.

Illustration : modification de la case du tableau située en 1ère colonne, 1ère ligne.

Étape 5 - Coloriage d'une case :

Gérez le fait qu'en cliquant avec le bouton de droite de la souris sur une case du tableau, celle-ci est coloriée en jaune.

Remarque : pour réaliser cette fonctionnalité, il faudra empêcher le menu contenu contextuel par défaut d'apparaître. Cherchez une solution sur Internet pour ce faire.

Et, si vous appuyez sur ALT + clic droit de la souris, la case est décolorisée.

Remarque : lorsque vous ajoutez une nouvelle colonne ou ligne, les cellules ne doivent pas être colories en jaune.

Étape 6 - Déplacement dans le tableau :

Enfin, dernière étape pour cet exercice, lors de la modification d'une case, il faut pouvoir se déplacer sur une case adjacente en appuyant sur ALT et l'une des 4 flèches du clavier.

Remarque : vérifiez que la touche Enter fonctionne lors de la modification d'une case.

Dans le fichier "index.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Un Mini Tableur</title>
        <style type="text/css">
            td {
                border: 1px solid #000;
                width: 50px;
                height: 30px;
                padding: 0px;
                font-size: 14px;
                font-family: Arial;
            }
            
            table {
                margin-top: 10px;
                border-collapse: collapse;
            }
            
            input {
                font-size: 14px;
                font-family: Arial;
                width: 44px;
            }
            
            table input {
                width: 44px;
                height: 24px;
                border-color: transparent;
            }
            
            .coloriee {
                background-color: yellow;
            }
            
            .selected {
                border: 2px solid blue;
            }
        </style>
    </head>
    <body>
        Valeur cellule :
        <input type="text" id="valeurCellule" name="valeur"/>
        <button id="btnNewCol">Ajouter une colonne</button>
        <button id="btnNewRow">Ajouter une ligne</button>
        <table id="myTable">
            <tbody>
                <tr>
                    <td></td>
                </tr>
            </tbody>
        </table>
        <script type="application/javascript" src="jquery.js"></script>
        <script type="application/javascript" src="myscript.js"></script>
    </body>
</html>

Dans le fichier "myscript.js" :

$(function() {
	$('#btnNewCol').click(function() {
		var v = $('#valeurCellule).val();
		addCol(v);
	});
	
	$('#btnNewRow').click(function() {
		var v = $('#valeurCellule).val();
		addRow(v);
	});
	
	$('tbody').on('click', 'td', function() {
		selected($(this));
	});
	
	var selectionne = null;
	$('#valeurCellule').on('change', function() {
		if (selectionne !== null) {
			selectionne.text($('#valeurCellule').val));
		}
	});
	
	$(document).on('keyup', function(event) {
		if (!event.altKey || selectionne === null) {
			return;
		}
		switch(event.key) {
			case 'ArrowLeft' :
				selectionne.prev().click();
				break;
			case 'ArrowUp' :
				selectionne.parent().prev().children().eq(selectionne.index()).click();
				break;
			case 'ArrowRight' :
				selectionne.next().click();
				break;
			case 'ArrowDown' :
				selectionne.parent().next().children().eq(selectionne.index()).click();
				break;
		}
	});
	
	function selected(td) {
		if (selectionne !== null) {
			$(selectionne).removeClass('selected');
		}
		$('#valeurCellule').val(td.text());
		selectionne = td;
		$(selectionne).addClass('selected');
	}
	
	function addCol(val) {
		$('myTable tr').each(function(i, el) {
			var td = $('<td>').text(val);
			$(el).append(td);
			changeColor(td);
		});
	}
	
	function addRow(val) {
		$('#myTable').append($('<tr>'));
		$('#myTable tr:first td').each(function(i, el) {
			var td = $('<td>').text(val);
			$('tr:last').append(td);
			changeColor(td);
		});
	}
	
	function changeColor(td) {
		td.on('contextmenu', function(event) {
			if (event.altKey) {
				$(this).removeClass('coloriee');
			} else {
				$(this).addClass('coloriee');
			}
			event.preventDefault();
		});
	}
	
	function modifieCase(td) {
		var oldValue = $(td).text();
		td.on('click', function(event) {
			$('#valeurCellule').val(oldValue).on('keyup', function(event) {
				if (event.key === 'Enter') {
					$(td).text($(this).val());
				}
			});
			$(this).blur(function() {
				$(td).html(oldValue);
			});
		});
	}
});

Exercice n° 7 :

Un formulaire HTML peut être composé de différents composants graphiques.

Le but de l'exercice est de transformer l'état d'un formlaire en un JSON équivalent, et inversément. Le JSON aura pour clefs les noms des différents éléments du formulaire (attribut name), et pour valeurs :

  • Input de type texte, password ↔ chaîne de caractères

  • Input de type number ↔ number

  • Input de type radiobutton ↔ valeur du bouton sélectionné ou null si aucun ne l'est

  • Input de type checkbutton ↔ true/false

  • Textarea ↔ chaîne de caractères

  • Select unique ↔ valeur de l'élément sélectionné ou null si aucun ne l'est

  • Select multiple ↔ tableau des valeurs des éléments sélectionnés

  1. Créez un formulaire HTML. Il doit contenir au minimum un élément de chacun des types ci-dessus.

  2. Ajoutez un bouton "JSON". Quand l'utilisateur clique sur ce bouton, la fonction formToJSON est appelée en donnant l'élément racine du formulaire en paramètre. Cette méthode renvoie le JSON représentant l'état du formulairen qui est alors affiché (par exemple dans un textarea en base de la page). Vous devez écrire tout le code gérant ceci.

  3. Ajoutez un textarea en dessous du formulaire (ou réutilisez celui du point précédent), et un bouton "Formulaire". Quand l'utilisateur clique sur ce bouton, la fonction JSONToForm est appelée en donnant l'élément racine du formulaire en paramètre ainsi que le contenu du textarea. Cette méthode transforme le texte en objet JSON et change le formulaire pour prendre l'état d'écrit par ce dernier. Vous devez écrire tout le code gérant ceci.

  4. Ajoutez un bouton "Nettoyer" : ce dernier parcourt le formulaire et vide tous les champs. De plus, les checkbox sont mises à faux, le premier radio de chaque groupe est sélectionné, la première option de chaque select unique est sélectionnée, tout est déselectionné pour les selects multiples.

  5. Paufinez votre solution :

    1. Les éléments désactivés sont ignorés par fromToJSON, ils ne doivent pas apparaître dans le JSON.

    2. Rendez JSONToForm solide : les éléments désactivés ne doivent pas être modifiés. SI la valeur fournie par JSON n'est pas valide, alors le champ doit rester inchangé. Si plusieurs champs portent le même nom, une exception doit être lancée; attention cependant qu'il est normal que plusieurs radio portent le même nom. Etc...

Dans le fichier "exo6.html" :

<DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Exos séance 6</title>
    </head>
    <body>
        <form action="#" method="POST" id="f">
            <p>Nom : <input type="text" name="nom" placeholder="entrez votre nom"></p>
            <p>Prénom : <input type="text" name="prenom" placeholder="entrez votre prénom"></p>
            <p>Sexe : M <input type="radio" name="sexe" value="M"> F <input type="radio" name="sexe" value="F"></p>
            <p>Email : <input type="mail" name="email" placeholder="entrez votre email"></p>
            <p>Numéro de téléphone : <input type="number" name="phone_number" placeholder="entrez votre num de tel"></p>
            <p>Numéro de téléphone : <input type="password" name="password" placeholder="entrez votre password"></p>
            <p>Remarques : <textarea cols="15" rows="5"name="remarques"></textararea></p>
            <p>Choix d'options :</p>
            <select name="option_selected">
                <option name="SAP">SAP</option>
                <option name="IA">IA</option>
                <option name="Unity">Unity</option>
            </select>
            <p>Choix de plusieurs options :</p>
            <select multiple name="options_selected">
                <option name="SAP">SAP</option>
                <option name="IA">IA</option>
                <option name="Unity">Unity</option>
            </select>
            <p>Cochez pour accepter les termes d'utilisation : <input type="checkbox" name="accept_term" value="accepted"></p>
        </form>
        <p><input type="submit" value="FormToJSON" onclick="formToJSON($('#f'));"></p>
        <p><input type="submit" value="JSONToForm" onclick="JSONToForm($('#f'), $('#json').val());"></p>
        <p><input type="submit" value="Nettoyer" onclick="clearForm($('#f'));"></p>
        <p><textarea cols="25" rows="15" id="json"></textarea></p>
        <script src="jquery.js"></script>
        <script src="myscript.js"></script>
    </body>
</html>

Dans le fichier "myscript.js" :

function formToJSON(f) {
	var o = {};
	var inputs = f.find('input:not(:radio):not(:checkbox), textarea').each(function(i, el) {
		var nameEl = $(el).attr('name');
		var valEl = $(el).val();
		if ($(el).attr('type') === 'number') {
			valEl = parseFloat(valEl);
		}
		o[nameEl] = valEl;
	});
	inputs = f.find('select').each(function(i, el) {
		var nameEl = $(el).attr('name');
		var valEl = $(el).val();
		if ($(el).attr('multiple') === undefined) {
			o[nameEl] = (valEl === '') ? null : valEl; 
		} else {
			o[nameEl] = (valEl === null) ? [] : valEl;
		}
	});
	inputs = f.find(':radio, :checkbox').each(function(i, el) {
		var nameEl = $(el).attr('name');
		if ($(el).attr('type') === 'radio') {
			p[nameEl] = $(el).is(':checked') ? $(el).val() : null;
		} else if ($(el).attr(type') === 'checkbox') {
			o[nameEl] = $(el).prop('checked');
		}
	});
	return JSON.stringify(o);
}

function JSONToForm(f, texte) {
	var o = (typeof texte === 'string' || texte instanceof String) ? JSON.parse(texte) : texte);
	for (var key in o) {
		var v = o[key];
		var input = $(':input[name=' + key + ']');
		if ($(input).is('select')) {
			if ($(input).attr('multiple') === undefined) {
				$(input).val(v.length === 0 ? null : v);
			} else {
				$(input).val(v === null ? '' : v);
			}
		} else if ($(input).attr('type' === 'checkbox') {
			$(input).prop('checked', v);
		} else if ($(input).attr('type') === 'radio') {
			if (v === null) {
				$(input).prop('checked', false);
			} else {
				$(input).filter('[value="' + v + '"]').prop('checked', true);
			}
		} else {
			$(input).val(o[key]);
		}
	}
	var inputs = f.find(':input').each(function(i, el) {
		var nameEl = $(el).attr('name');
		var valEl = $(el).val();
		if (o[nameEl] === undefined) {
			$(el).val(valEl);
		}
	});
}

function clearForm(f) {
	var inputs = f.find(':input').each(function(i, el) {
		if (($(el).attr('type') === 'radio' || $(el).attr('type') === 'checkbox') {
			$(el).attr('checked', false);
		} else {
			if (!$(el).is('select[multiple]')) {
				$(el).val($(el).find('option:first').val());
			} else {
				$(el).val('');
			}
		}
	});
}

Exercice n° 8 :

Partie HTTPServlet :

Nous allons mettre sur pied un site Web qui calcule des expressions mathématiques.

L'architecture sera la suivante :

  1. Créez une page HTML contenant un champ textarea dans lequel l'utilisateur rentre sa formule.

  2. Ajoutez un bouton pour envoyer la requête. Cette dernière aura la forme d'un GET envoyé au serveur et fournissant la formule du textarea dans le paramètre expr. En d'autres termes, il faudra rediriger la page vers quelque chose ressemblant à http://127.0.0.1/Calc?expr=2+2.

    Comment redirige-t-on encore en JavaScript ?

    Ce que l'utilisateur entre dans le textarea finit par se retrouver dans l'URL. Il pourrait donc en profiter pour abuser le comportement de cette URL en y mettant des caractères non prévus, par exemple &. Tout paramètre envoyé par un GET doit être encodé. Consultez la documentation de la fonction encodeURIComponent pour la solution standard à ce problème.

  3. Créez un serveur en Java :

    1. Créez un répertoire lib à la racine de votre projet et décompressez-y le contenu de Jetty.zip qui se trouve sur l'ecampus. Faites refresh (F5) sur la racine de votre projet, sélectionnez les fichiers .jar du répertoire lib et click-droit/Build Path/Add To Build Path.

    2. Créez une classe et dans sa méthode main, copiez les lignes de code de la préservation permettant d'utiliser Jetty en mode embedded.

    3. Servez les fichiers requis pour le front-end à l'aide d'un DefaultServlet.

    4. Créez une servlet pour recevoir les GET de calcul d'expressions mathématiques.

    5. Pour le calcul à propremnt dit, vous pouvez utiliser la librairie exp4j.

    6. La réponse renvoyée doit être simplement un document HTML dont le body est la réponse.

    7. Configurez votre serveur pour que tout fonctionne !

  4. Bonus : améliorer le rendu du résultat :

    • Rappelez la formule évaluée (afficher 1+1=2.0 plutôt que se contenter d'afficher juste 2.0).

    • Rendez l'affichage plus joli.

    • En plus d'afficher le résultat, permettez d'entrer une nouvelle formule mathématique.

    • Suivez votre imagination...

Dans le fichier "index.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>HttpServlet</title>
    </head>
    <body>
            <textarea></textarea>
            <button>Calculer</button>
        <script src="jquery.js"></script>
        <script>
            $('button').click(function() {
                location.replace('/Calc?expr=' + encodeURIComponent($('textarea').val()));
            });
        </script>
    </body>
</html>

Dans le fichier "Main.java" :

import javax.servlet.http.HttpServlet;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        WebAppContext context = new WebAppContext();
        HttpServlet ms = new MaServlet();
        context.addServlet(new ServletHolder(ms), "/Calc");
        context.setResourceBase("www");
        HttpServlet ds = new DefaultServlet();
        context.addServlet(new ServletHolder(ds), "/");
        server.setHandler(context);
        server.start();
    }

}

Dans le fichier "MaServlet.java" :

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;


public class MaServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String expr = req.getParameter("expr");
            Expression e = new ExpressionBuilder(expr).build();
            String html = "<html lang="fr"><body>" + expr + " = " + e.evaluate() + "</body></html>";
            resp.setStatus(200);
            byte[] b = html.getBytes("UTF-8");
            resp.setContentLength(b.length);
            resp.setContentType("text/html");
            resp.setCharacterEncoding("utf-8");
            resp.getOutputStream().write(b);
        } catch (Exception e) {
            e.printStackTrace();
            resp.setStatus(500);
            resp.setContentType("text/html");
            byte[] msgBytes = e.getMessage().getBytes("UTF-8");
            resp.setContentLength(msgBytes.length);
            resp.setCharacterEncoding("utf-8");
            resp.getOutputStream().write(msgBytes);
        }
    }

}

Partie Genson :

Lisez attentivement la documentation de Genson.

  1. Dans un fichier calc.json, entrez ceci : {"x":4,"y":10,"eval":"x+y"}.

    L'idée de ce JSON est de décrire une expression mathématique avec des variables. La clef "eval" contient toujours l'expression à évaluer. Les autres clefs sont par contre libres, chacune représentant une variable de l'expression mathématique.

    Écrivez un programme Java qui lit le fichier calc dans une chaîne de caractères.

    String content = new String(Files.readAllBytes(Path.get("calc.json")));

    Utilisez Genson et exp4j pour créer l'expression, placer les valeurs des variables et afficher le résultat.

    Bonis : changerl'application Web de votre point précédent pour qu'elle aussi fonctionne sur base d'un tel JSON.

    Dans le fichier "calc.json" :

    {"x":4,"y":10,"eval":"x + y + c","c":16}
  2. Une machine de l'IPL est définie par :

    • Son local = un numéro de local

    • Sa localisation dans ce local = une chaîne de caractères

    • Un modèle de CPU

    • Une fréquence de CPU

    • Une taille de mémoire en GB

    • Une taille de disque dur en GB

    • Un modèle de carte graphique

    • Une date d'achat

    Décidez d'une classe Java et d'un JSON qui décrivent cette machine. Faites attention au format de date : dans le JSON, il faut utiliser une chaîne au format JJ/MM/AAAA et en Java utiliser une classe Date.

    Écrivez trois fichiers JSON : un pour votre machine, un pour la machine à votre gauche (ou derrière ou devant), un pour la machine à votre droite (ou derrière ou devant).

    Écrivez une classe Java, modèle d'une machine, ainsi que sa méthode toString().

    Écrivez une application de test qui lit les fichiers JSON, les transforme en objets Java en utilisant vos classes, affiche le contenu avec le toString(), retransforme la classe en chaîne de caractères au format JSON proprement formaté.

    Dans le fichier "MachineIPL.java" :

    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    
    public class MachineIPL {
    
        private int local;
        private String localisation;
        private String modeleCPU;
        private double frequenceCPU;
        private double tailleMemoire;
        private double tailleDisqueDur;
        private String modeleCarteGraphique;
        private LocalDate dateAchat;
    
        public int getLocal() {
            return local;
        }
    
        public void setLocal(int local) {
            this.local = local;
        }
    
        public String getLocalisation() {
            return localisation;
        }
    
        public void setLocalisation(String localisation) {
            this.localisation = localisation;
        }
    
        public String getModeleCPU() {
            return modeleCPU;
        }
    
        public void setModeleCPU(String modeleCPU) {
            this.modeleCPU = modeleCPU;
        }
    
        public double getFrequenceCPU() {
            return frequenceCPU;
        }
    
        public void setFrequenceCPU(double frequenceCPU) {
            this.frequenceCPU = frequenceCPU;
        }
    
        public double getTailleMemoire() {
            return tailleMemoire;
        }
    
        public void setTailleMemoire(double tailleMemoire) {
            this.tailleMemoire = tailleMemoire;
        }
    
        public double getTailleDisqueDur() {
            return tailleDisqueDur;
        }
    
        public void setTailleDisqueDur(double tailleDisqueDur) {
            this.tailleDisqueDur = tailleDisqueDur;
        }
    
        public String getModeleCarteGraphique() {
            return modeleCarteGraphique;
        }
    
        public void setModeleCarteGraphique(String modeleCarteGraphique) {
            this.modeleCarteGraphique = modeleCarteGraphique;
        }
    
        public LocalDate getDateAchat() {
            return dateAchat;
        }
    
        public void setDateAchat(String dateAchat) {
            this.dateAchat = LocalDate.parse(dateAchat, DateTimeFormatter.ofPattern("dd/MM/yyyy"));
        }
    
        @Override
        public String toString() {
            return "MachineIPL [local=" + local + ", localisation=" + localisation + ", modeleCPU=" + modeleCPU
                    + ", frequenceCPU=" + frequenceCPU + ", tailleMemoire=" + tailleMemoire + ", tailleDisqueDur="
                    + tailleDisqueDur + ", modeleCarteGraphique=" + modeleCarteGraphique + ", dateAchat="
                    + dateAchat + "]";
        }
    
    }

    Dans le fichier "TestMachineIPL.java" :

    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.text.SimpleDateFormat;
    
    import com.owlike.genson.GensonBuilder;
    
    public class TestMachineIPL {
    
        public static void main(String[] args) throws IOException {
            String content = new String(Files.readAllBytes(Paths.get("MineMachine.json")), StandardCharsets.UTF_8);
            MachineIPL machine = new GensonBuilder().useDateFormat(new SimpleDateFormat("dd/MM/yyyy")).useIndentation(true).useConstructorWithArguments(true).create().deserialize(content, MachineIPL.class);
            for (String s : machine.toString().split(",")) {
                System.out.println(s);
            }
        }
    
    }

    Dans le fichier "MineMachine.json" :

    {"local":026,"localisation":"première rangée","modeleCPU":"AMD A8","frequenceCPU":17.6,"tailleMemoire":1024,"tailleDisqueDur":65536,"modeleCarteGraphique":"A8 AMD","dateAchat":"22/11/2017"}

Exercice n° 9 :

Nous allons créer une interface permettant de retenir l'information des machines IPL. Chaque machine sera sauvée dans un fichier JSON dans un répertoire que vous choisissez.

  1. Créer une page contenant un select pour sélectionner une machine et un formulaire pour l'éditer.

  2. Au chargement de la page, un premier appel Ajax permet d'obtenir la liste de toutes les machines afin de remplir le select.

  3. À la sélection d'une machine, il faut un appel Ajax permettant d'en obtenir la spécification, et peupler le formulaire en conséquence

    • N'hésitez pas à récupérer vos résultats des exercices précédents : classe Java modèle d'une machine, fonction JSON→Formulaire et Formulaire→JSON, etc.

  4. Lors de la sauvegarde, il faut un appel Ajax pour donner les nouvelles spécifications au serveur qui met alors son fichier à jour.

  5. Bonus : ajouter des boutons "Créer une nouvelle machine" et "Effacer la machine sélectionnée" et rendez-les fonctionnels.

Dans le fichier "index.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Ajax</title>
    </head>
    <body>
        <label for="machines">Machine :</label><select id="machines"></select>
        <form style="display: none;" id="form_machine">
            <p&>lt;label for="local">Local :</label><input type="text" name="local" id="local"></p>
            <p><label for="localisation">Place :</label><input type="text" name="localisation" id="localisation"></p>
            <p><label for="modeleCPU">CPU :</label><input type="text" name="modeleCPU" id="modeleCPU"></p>
            <p><label for="frequenceCPU">Fréquence :</label><input type="number" name="frequenceCPU" id="frequenceCPU"></p>
            <p><label for="tailleMemoire">Mémoire :</label><input type="number" name="tailleMemoire" id="tailleMemoire"></p>
            <p>><label for="tailleDisqueDur">HDD :</label><input type="number" name="tailleDisqueDur" id="tailleDisqueDur"></p>
            <p><label for="modeleCarteGraphique">GUI :</label><input type="text" name="modeleCarteGraphique" id="modeleCarteGraphique"></p>
            <p><label for="dateAchat">Date d'achat :</label><input type="text" name="dateAchat" id="dateAchat"></p>
        </form>
        <button id="save">Sauver</button>
        <script src="jquery.js"></script>
        <script src="form.js"></script>
        <script src="machines.js"></script>
    </body>
</html>

Dans le fichier "form.js"(même chose qu'en haut) :

function formToJSON(f) {
	var o = {};
	var inputs = f.find('input:not(:radio):not(:checkbox), textarea').each(function(i, el) {
		var nameEl = $(el).attr('name');
		var valEl = $(el).val();
		if ($(el).attr('type') === 'number') {
			valEl = parseFloat(valEl);
		}
		o[nameEl] = valEl;
	});
	inputs = f.find('select').each(function(i, el) {
		var nameEl = $(el).attr('name');
		var valEl = $(el).val();
		if ($(el).attr('multiple') === undefined) {
			o[nameEl] = (valEl === '') ? null : valEl; 
		} else {
			o[nameEl] = (valEl === null) ? [] : valEl;
		}
	});
	inputs = f.find(':radio, :checkbox').each(function(i, el) {
		var nameEl = $(el).attr('name');
		if ($(el).attr('type') === 'radio') {
			p[nameEl] = $(el).is(':checked') ? $(el).val() : null;
		} else if ($(el).attr(type') === 'checkbox') {
			o[nameEl] = $(el).prop('checked');
		}
	});
	return JSON.stringify(o);
}

function JSONToForm(f, texte) {
	var o = (typeof texte === 'string' || texte instanceof String) ? JSON.parse(texte) : texte);
	for (var key in o) {
		var v = o[key];
		var input = $(':input[name=' + key + ']');
		if ($(input).is('select')) {
			if ($(input).attr('multiple') === undefined) {
				$(input).val(v.length === 0 ? null : v);
			} else {
				$(input).val(v === null ? '' : v);
			}
		} else if ($(input).attr('type' === 'checkbox') {
			$(input).prop('checked', v);
		} else if ($(input).attr('type') === 'radio') {
			if (v === null) {
				$(input).prop('checked', false);
			} else {
				$(input).filter('[value="' + v + '"]').prop('checked', true);
			}
		} else {
			$(input).val(o[key]);
		}
	}
	var inputs = f.find(':input').each(function(i, el) {
		var nameEl = $(el).attr('name');
		var valEl = $(el).val();
		if (o[nameEl] === undefined) {
			$(el).val(valEl);
		}
	});
}

function clearForm(f) {
	var inputs = f.find(':input').each(function(i, el) {
		if (($(el).attr('type') === 'radio' || $(el).attr('type') === 'checkbox') {
			$(el).attr('checked', false);
		} else {
			if (!$(el).is('select[multiple]')) {
				$(el).val($(el).find('option:first').val());
			} else {
				$(el).val('');
			}
		}
	});
}

Dans le fichier "machines.js" :

$(function() {
	
	$.ajax({
		url: '/Machines',
		type: 'POST',
		data: {action: 'list'},
		success: function(reponse) {
			for (var i = 0; i < reponse.length; i++) {
				var option = $('<option>').text(reponse[i]);
				$('#machines').append(option);
			}
			$('#machines').trigger('change');
			console.info(reponse);
		}
	});
	
	$('#machines').on('change', function() {
		$.ajax({
			url: '/Machines',
			data: {machine: $('#machine option:checked').text(), action: 'get'},
			type: 'POST',
			success: function(reponse) {
				$('#form_machine').css('display', 'block');
				JSONToForm($('#form_machine'), reponse);
			}
=		});
	});
	
	$('#save').click(function() {
		var machine = $('#machines option:checked').text();
		var json = formToJSON($('#form_machine'));
		$.ajax({
			url: '/Machines',
			data: {machine: machine, json: json, action: 'save'},
			type: 'POST',
			success: function(reponse) {
				alert('Machine sauvée');
			}
		});
	});
});

Dans le fichier "Main.java" :

import javax.servlet.http.HttpServlet;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        WebAppContext context = new WebAppContext();
        HttpServlet ms = new MaServlet();
        context.addServlet(new ServletHolder(ms), "/Machines");
        context.setResourceBase("www");
        HttpServlet ds = new DefaultServlet();
        context.addServlet(new ServletHolder(ds), "/");
        server.setHandler(context);
        server.start();
    }

}

Dans le fichier "MaServlet.java" :

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.owlike.genson.Genson;

public class MaServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String action = req.getParameter("action");
            switch (action) {
            case "list":
                String[] machines = new File("machines").list();
                String json = new Genson().serialize(machines);
                resp.setStatus(200);
                byte[] b = json.getBytes("UTF-8");
                resp.setContentLength(b.length);
                resp.setContentType("application/json");
                resp.setCharacterEncoding("utf-8");
                resp.getOutputStream().write(b);
                break;
            case "get":
                String machine = req.getParameter("machine");
                File m = new File("machines", machine);
                byte[] bytes = Files.readAllBytes(m.toPath());
                resp.setCharacterEncoding("utf-8");
                resp.setContentType("application/json");
                resp.getOutputStream().write(bytes);
                break;
            case "save":
                machine = req.getParameter("machine");
                m = new File("machines", machine);
                String contenu = req.getParameter("json");
                Files.write(m.toPath(), contenu.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            resp.setStatus(500);
            resp.setContentType("text/html");
            byte[] msgBytes = e.getMessage().getBytes("UTF-8");
            resp.setContentLength(msgBytes.length);
            resp.setCharacterEncoding("utf-8");
            resp.getOutputStream().write(msgBytes);
        }
    }

}

Exercice n° 10 :

Les séances 9 à 12 sont consacrées à perfectionner votre apprentissage par la réalisation d'un projet.

Il est attendu de votre part que vous maîtrisiez :

  • JavaScript et l'organisation du code en pseudo-objets et pseudo-classes.

  • jQuery.

  • JSON et Genson.

  • Le démarrage d'un serveur utilisant Jetty en mode embedded, configuré avec une ou plusieurs servlets.

  • Les requêtes Ajax.

  • L'utilisation de JWT pour signer l'authentification d'un utilisateur.

  • Les cookies.

  • HttpSession.

Ceci compose aussi la matière de l'examen.

De plus, vous pouvez profiter du projet pour découvrir Bootstrap, Select2, jQuery UI et jQuery datatables, mais ce n'est pas obligatoire.

Plusieurs sujets sont proposés, vous en choisissez un seul. Cependant, pour tous les projets, il faut :

  • Permettre l'enregistrement est l'authentification des utilisateurs, en utilisant JWT. Utilisez un cookie pour retenir la chaîne JWT et les HTTPSession pour éviter de valider la signature à chaque requête.

  • Utiliser des pseudo-classes et des pseudo-objets en JavaScript.

  • Utiliser jQuery, en réutilisant vos fonctions form↔json quand cela s'y prête.

  • Le serveur doit être en Java, utiliser Jetty en mode embedded et être pensé en stateless. Les données seront enregistrées dans des fichiers textes contenant du JSON.

Ce projet ne sera pas évalué. Vous pouvez donc explorer et prendre des risques sans que cela ne se prête à réelle conséquence. Nous insistons une fois encore sur l'asppect auto-formation : l'examen pourra vous demander de faire n'importe quel point ci-dessus, et vu le temps pour le réaliser vous ne pourrez pas espérer vous auto-former sur le moment même.

Projet A : Dessinez-c'est gagné !

Votre application sera capable de :

  • Enregistrer un nouvel utilisateur.

  • Authentifier un utilisateur existant.

  • Lorsque l'utilisateur est authentifié, il peut soit dessiner, soit jouer :

  • S'il dessine :

    • Une interface à la paint (en très simplifié) lui permet de faire un dessin. Chaque trait a une couleur. Chaque trait retient l'ensemble des points par lequel la souris passe. L'ordre des traits est retenu.

    • Il peut enregister un ou plusieurs mot-clefs : c'est ce que le joueur doit deviner par le dessin.

  • S'il joue :

    • Le back-end choisit un dessin au hasard.

    • Les traits sont dessinés dans leur ordre successif, avec un peu d'attente entre chaque trait 100 ms par exemple).

    • Il rentre dans un champ texte des propositions. Le serveur l'accepte si la proposition correspond à un des mots clés enregistrés par le dessinateur.

  • Imaginez un système de points : au plus vite c'est trouvé, au plus grand les points par exemple. Ajoutez une page 'Highscore' listant les utilisateurs classés par leurs points du plus grand au plus petit.

  • Faites preuve d'imagination pour améliorer tout cela.

Dans le fichier "index.html" :

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <title>Dessinez-c'est gagné</title>
    </head>
<body>
    <div id="page1" style="display: none;"> 
        <form>
            <p><label for="login">Login : </label><input type="text" name="login" id="login"></p>
            <p><label for="mdp">Mot de passe :</label><input type="password" name="mdp" id="mdp"></p>
        </form>
        <p><button id="connect">Connectez-vous</button></p>
    </div>
    <div id="page2" style="display: none;">
        <button id="deco">Déconnexion</button>
        <canvas style="background-color: #808080;" id="monCanvas">
        </canvas>
        <br>
        <button id="envoiDessin">Envoie le dessin</button>
    </div>
    <div id="page3" style="display: none;">
        <button id="deco1">Déconnexion</button>
        <canvas style="background-color: #808080;" id="monCanvas2">
        </canvas>
    </div>
    <script src="jquery.js"></script>
    <script src="frontend.js"></script>
    <script src="myscript.js"></script>
</body>
</html>

Dans le fichier "Main.java" :

import javax.servlet.http.HttpServlet;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        WebAppContext context = new WebAppContext();
        HttpServlet ms = new MaServlet();
        context.addServlet(new ServletHolder(ms), "/login");
        context.setResourceBase("www");
        HttpServlet ds = new DefaultServlet();
        context.addServlet(new ServletHolder(ds), "/");
        server.setHandler(context);
        server.start();
    }

}

Dans le fichier "MaServlet.java" :

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.JWTVerifyException;

public class MaServlet extends HttpServlet {
    
    private static final long serialVersionUID = 1L;
    private static String JWTSECRET = "jkdh lqjk lcx wuich liuhza";
    private Map<String, String> users = new HashMap<String, String>() {{
        put("toto", "toto");
        put("toto1", "toto1");
        put("toto2", "toto2");
        put("toto3", "toto3");
    }};
    private String dessin = null, dessinateur = null;
    
    @Override
    protected void doPost(HttpServlet req, HttpServletResponse reponse) {
        try {
            String action = req.getParameter("action"), token = null;
            switch (action) {
            case "login":
                String user = req.getParameter("user"), mdp = req.getParameter("password");
                if (users.containsKey(user) && users.get(user).equals(mdp)) {
                    if (dessinateur == null) {
                        dessinateur = user;
                        resp.getOutputStream().print("dessinateur");
                        resp.getOutputStream().flush();
                    } else if (dessinateur.equals(user)) {
                        resp.getOutputStream().print("devineur");
                        resp.getOutputStream().flush();
                    }
                    Map<String, Object> claims = new HashMap<String; Object>();
                    claims.put("username", user);
                    claims.put("ip", req.getRemoteAddr());
                    token = new JWTSigner(JWTSECRET).sign(claims);
                    Cookie cookie = new Cookie("user", token);
                    cookie.setPath("/");
                    cookie.setMaxAge(60 * 60);
                    resp.addCookie(cookie);
                    req.getSession().setAttribute("userSession", user);
                } else {
                    resp.getOutputStream().write("menteur".getBytes());
                    resp.getOutputStream().flush();
                }
                System.out.println("dessinateur = " + dessinateur + " et dessin = " + dessin);
                break;
            case "isconnected" :
                String session = (String) req.getSession().getAttribute("userSession");
                Cookie[] cookies = req.getCookies();
                if (cookies != null) {
                    for (Cookie c : cookies) {
                        if ("user".equals(c.getName()) && (c.getSecre()|| token == null) {
                            token = c.getValue();
                        }
                    }
                }
                Map<String, Object> decodedPayload = new JWTVerifier(JWTSECRET).verify(token);
                System.out.println(decodedPayload);
                if (session == null) {
                    resp.getOutputStream().print("personne");
                } else if (session.equalss(dessinateur)) {
                    resp.getOutputStream().print("dessinateur");
                } else {
                    resp.getOutputStream().print("devineur");
                }
                resp.getOutputStream().flush();
                System.out.println("dessinateur = " + dessinateur + " et dessin = " + dessin);
                break;
            case "dessine":
                session = (String) req.getSession().getAttribute("userSession");
                if (session == null || !session.equald(dessinateur)) {
                    resp.getOutputStream().print("tricheur");
                    resp.getOutputStream().flush();
                } else {
                    dessin = req.getParameter("donnees");
                    resp.getOutputStream().write("ok".getBytes());
                    resp.getOutputStream().flush();
                }
                System.out.println("dessinateur = " + dessinateur + " et dessin = " + dessin);
                break;
            case "joue":
                resp.setContentType("application/json");
                resp.getOutputStream().write(dessin.getBytes());
                resp.getOutputStream().flush();
                break;
            case "deco":
                if (req.getSession() != null) {
                    req.getSession().invalidate();
                }
                System.out.println("dessinateur = " + dessinateur + " et dessin = " + dessin);
                break;
            default:
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action invalide");
            }
        } catch (SignatureException signatureException) {
            System.err.println("Invalid signature!");
        } catch (IllegalStateException illegalStateException) {
            System.err.println("Invalid Token! " + illegalStateException);
        } catch (InvalidKeyException e) {
            System.err.println("Invalid Key! " + e);
        } catch (NoSuchAlgorithmException e) {
            System.err.println("Invalid Algorithm! " + e);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JWTVerifyException e) {
            System.err.println("JWT Verify Exception! " + e);
        }
    }
    
}

Dans le fichier "frontend.js" :

/*!
* Framework Front-End minimaliste
*
* Copyright 2017 Donatien Grolaux
* Released under the MIT license
* https://opensource.org/licenses/MIT
*
* Date: 2017-03-12
*/

var FrontEnd = (function() {

    var onInits = {};
    var onLeaves = {};
    var onDisplays = {};
    var inited = {};
    var currentPage = null;

    function onInitPage(page, f) {
        if (page instanceof String || typeof page == 'string') {
            page=$('#' + page);
        }
        if (page.length != 1) return false;
        onInits[page.attr('id')] = f;
        return true;
    }

    function onLeavePage(page, f) {
        if (page instanceof String || typeof page == 'string') {
            page=$('#' + page);
        }
        if (page.length != 1) return false;
        onLeaves[page.attr('id')] = f;
        return true;
    }

    function onDisplayPage(page, f) {
        if (page instanceof String || typeof page == 'string') {
            page = $('#' + page);
        }
        if (page.length != 1) return false;
        onDisplays[page.attr('id')] = f;
        return true;
    }

    function getCurrentPage() {
        return currentPage;
    }

    function canDisplayPage(page) {
        if (page instanceof String || typeof page == 'string') {
            page = $('#' + page);
        }
        if (page.length != 1) return false;

        if (currentPage !== null) {
            if (onLeaves[currentPage.attr('id')] != undefined) {
                if (onLeaves[currentPage.attr('id')](page) === false) {
                    return false;
                }
            }
        }

        if (inited[page.attr('id')] !== true) {
            if (onInits[page.attr('id')] != undefined) {
                if (onInits[page.attr('id')](page) === false) {
                    return false;
                }
            }
            inited[page.attr('id')] = true;
        }

        if (onDisplays[page.attr('id')] != undefined) {
            if (onDisplays[page.attr('id')](page) === false) {
                return false;
            }
        }

        return true;
    }

    function displayPage(page) {
        if (page instanceof String || typeof page == 'string') {
            page = $('#' + page);
        }

        if (!canDisplayPage(page)) return false;

        if (currentPage !== null) {
            currentPage.css('display', 'none');
        }
        page.css('display', 'block');
        currentPage = page;

    }

    function alert(title, question, message, callback) {
        var modal = $('>div class="modal">>div class="modal-content">>div class="modal-header">>span class="close">×>/span>>h2>M>/h2>>/div>>div class="modal-body">>div>>div class="modal-footer">>/div>  >/div>>/div>');
        modal.find('.modal-header h2').text(title);
        modal.find('.modal-body').text(message);
        var btn=question.split("|");
        modal.find('.modal-header span').css('display', 'none');
        $.each(btn, function(i, item) {
            var btn = $('>button type="button">');
            switch(item) {
            case 'YES':
                btn.text('Oui');
                break;
            case 'NO':
                btn.text('Non');
                break;
            case 'ALWAYSYES':
                btn.text('Toujours oui');
                break;
            case 'ALWAYSNO':
                btn.text('Toujours non');
                break;
            case 'CANCEL':
                btn.text('Annuler');
                modal.find('.modal-header span').css('display', 'block').on('click', function() {
                    btn.trigger('click');
                });
                break;
            }
            btn.on('click', function() {
                modal.remove();
                modal=null;
                if (callback !== undefined) {
                    callback(item);
                }
            });
            modal.find('.modal-footer').append(btn);
        });
        $('body').append(modal);
        modal.css('display', 'block');
    }

    function ajax(config) {
        var fail = config.error;
        config.error = function(jqxhr, status, error) {
            if (fail !== undefined) {
                if (fail(jqxhr, status, error) === true) {
                    return;
                }
            }
            if (jqxhr.status == 400) {
                FrontEnd.alert('Erreur', 'CANCEL', jqxhr.responseText);
            } else {
                FrontEnd.alert('Erreur grave', 'CANCEL', jqxhr.responseText, function() {location.reload();});
            }
        }
        if (config.type === undefined) config.type = 'POST';
        if (config.url === undefined) config.url = '/dispatch';
        if (config.action!==undefined) {
            if (config.data === undefined) config.data = {};
            config.data.action = config.action;
            delete config.action;
        }
        
        $.ajax(config);
    }

    $('body > .container > .row').css('display', 'none');

    return {
        onInitPage: onInitPage,
        onLeavePage: onLeavePage,
        onDisplayPage: onDisplayPage,
        displayPage: displayPage,
        canDisplayPage: canDisplayPage,
        currentPage: getCurrentPage,
        ajax: ajax,
        alert: alert
    }

})();

Dans le fichier "myscript.js" :

$(function() {
	
	$.ajax({
		url: '/login',
		type: 'POST',
		data: {action: 'isconnected'},
		success: function(reponse) {
			console.log(reponse);
			if (reponse === 'dessinateur') {
				FrontEnd.displayPage('page2');
			} else if (reponse === 'devineur') {
				FrontEnd.displayPage('page3');
			} else {
				FrontEnd.displayPage('page1');
			}
		}
	});	
	
	$('#deco').click(function() {
		deco();
	});
	
	$('#deco1').click(function() {
		deco();
	});
	
	function deco() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'deco'},
			success: function (reponse) {
				console.log(reponse);
				location.reload();
			}
		});
	}
	
	$('#connect').click(function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'login', user: $('#login').val(), password: $('#mdp').val()},
			success:function (reponse) {
				console.log(reponse);
				if (reponse === 'dessinateur') {
					FrontEnd.displayPage('page2');
				} else if (reponse === 'devineur') {
					FrontEnd.displayPage('page3');
				} else {
					alert('MENTEUR !', 'YES');
				}
			}
		});
	});
	
	var canvas = $('#monCanvas');
		console.log(canvas);
		var ctx = canvas[0].getContext('2d');
		var dessine = false;
		var x, y;
		var traits = [];
		var traitEnCours;
		
		canvas.on('mousedown', function(event) {
			dessine = true;
			x = event.offsetX;
			y = event.offsetY;
			traitEnCours = [x, y];
		});
		
		canvas.on('mouseup', function(event) {
			dessine = false;
			traits.push(traitEnCours);
		});
		
		canvas.on('mousemove', function(event) {
			if(dessine) {
				ctx.beginPath();
				ctx.moveTo(x,y);
				ctx.lineTo(event.offsetX, event.offsetY);
				ctx.stroke();
				x = event.offsetX;
				y = event.offsetY;
				traitEnCours.push(x);
				traitEnCours.push(y);
			}
		});
	
	$('#envoiDessin').click(function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'dessine', donnees: JSON.stringify(traits)},
			success: function (reponse) {
				console.log(reponse);
				if (reponse === 'ok') {
					alert('Envoyé');
				}
			}
		});
	});
	
	FrontEnd.onDisplayPage('page3', function() {
		$.ajax({
			url:'/login',
			type:'POST',
			data:{action:'joue'},
			success:function (reponse) {
				console.log(reponse);
				if (reponse !== null) {
					affiche(reponse, $('#monCanvas2'));
				}		
			}
		});
	});
	
	function affiche(traits, canvas) {	
		var ctx = canvas[0].getContext('2d');
		var lignes = 0;
		var idx = 0;
		
		function callBack() {
			if (lignes >= traits.length) {
				clearInterval(id);
				return;
			}
			if (idx >= traits[lignes].length - 2) {
				lignes++;
				idx = 0;
				return;
			}
			ctx.beginPath();
			ctx.moveTo(traits[lignes][idx], traits[lignes][idx + 1]);
			ctx.lineTo(traits[lignes][idx + 2], traits[lignes][idx + 3]);
			ctx.stroke();
			idx += 2;
		}
		
		var id = setInterval(callBack, 10);
	}
});

Examen janvier 2018 : Mini-pixel-draw :

Ce mini-programme consiste à implémenter une grille sur laquelle les utilisateurs peuvent cliquer afin d'en colorier les cellules.

Dans le répertoire "Question1" :

Créez une page HTML contenant une table initialement vide (aucun <tr> dans l'HTML). En JavaScript, insérer dans cette table 100 lignes contenant 100 cellules afin d'obtenir une grille de 100x100. De plus, chaque fois que l'utilisateur clique sur une cellule, celle-ci sera coloriée :

  • Si la cellule est déjà coloriée, rien ne se passe.

  • Au premier click de l'utilisateur, la cellule est coloriée en rouge.

  • Au click suivant, la cellule est coloriée en vert.

  • Au click suivant, la cellule est coloriée en bleu.

  • Les clicks suivants cyclent ces trois couleurs (rougek vert et bleu).

Attention : l'esthétique n'est pas évaluée ! Faites au fonctionnel le plus rapidement possible. Votre code doit utiliser jQuery à bon escient. Il n'y a pas besoin de serveur pour cette question.

Dans le fichier "index.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Examen Janvier 2018</title>
        <style type="text/css">
            td {
                width: 5px;
                height: 5px;
            }
        </style>
    </head>
    <body>
        <table id="mytable" border="1">
        </table>
        <script src="jquery.js"></script>
        <script src="myscript.js"></script>
    </body>
</html>

Dans le fichier "myscript.js" :

$(function() {
	
	var nbClicks = 0;
	var couleurs = ['red', 'green', 'blue'];
	var nbLignes = 100;
	var nbColonnes = 100;
	
	for (var ligne = 0; ligne < nbLignes; ligne++) {
		var tr = $('<tr>');
		for (var colonne = 0; colonne < nbColonnes; colonne++) {
			var td = $('<td>');
			tr.append(td);
			colorierTd(td);
		}
		$('#mytable').append(tr);
	}
	
	function colorierTd (td) {
		td.click(function() {
			var backgroundColor = $(this).css('backgroundColor');
			if (backgroundColor === 'rgba(0, 0, 0, 0)') {
				var indice = nbClicks % couleurs.length;
				$(this).css('backgroundColor', couleurs[indice]);
			}
			nbClicks++;
		});
    }
});

Dans le répertoire "Question2" :

Créez une page HTML contenant un champ login (pas de mot de passe) et un bouton "S'authentifier". Créez un back-end Java à l'aide de Jetty pour servir les ficiers nécessaires.

En JavaScript et à l'aide de jQuery, programmez que le click du bouton fasse un appel Ajax à votre back-end. Ce dernier retiendra l'utilisateur dans la session uniquement (pas de token JWT). Une fois authentifié, l'interface affichera un message de bienvenue et un bouton "Se déconnecter" qu'il faut rendre fonctionnel. Attention à ce qu'un refresh de la page neforce pas l'utilisateur à s'authentifier s'il l'est déjà.

Dans le fichier "Main.java" :

import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

    public static void main(String[] args) throws Exception {
    Server server = new Server(8080);
    WebAppContext context = new WebAppContext();
    HttpServlet ms = new MaServlet();
    context.addServlet(new ServletHolder(ms), "/login");
    context.setResourceBase("www");
    HttpServlet ds = new DefaultServlet();
    context.addServlet(new ServletHolder(ds), "/");
    context.setWelcomeFiles(new String[] {"login.html"});
    server.setHandler(context);
    server.start();
    }

}

Dans le fichier "MaServlet.java" :

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class MaServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String action = req.getParameter("action");
    switch (action) {
        case "login":
        String user = req.getParameter("login");
        if (user != null) {
            req.getSession().setAttribute("userSession", user);
            resp.getWriter().print(true);
        } else {
            resp.getWriter().print("menteur");
        }
        break;
        case "isconnected":
        String userSession = (String) req.getSession().getAttribute("userSession");
        resp.getWriter().print((userSession != null));
        break;
        case "deco":
        if (req.getSession() != null) {
            req.getSession().invalidate();
        }
        break;
        default:
        super.doPost(req, resp);
    }
    }

}

Dans le fichier "login.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Login</title>
    </head>
    <body>
        <div id="page1" style="display: none;">
            <p><label for="login">Votre login :</label><input type="text" id="login" name="login" placeholder="votre login"></p>
            <p><button id="connexion">S'authentifier</button></p>
        </div>
        <div id="page2" style="display: none;">
            <p>Bienvenue</p>
            <p><button id="deco">Déconnexion</button></p>
        </div>
        <script src="jquery.js"></script>
        <script src="frontend.js"></script>
        <script src="login.js"></script>
    </body>
</html>

Dans le fichier "login.js" :

$(function() {

	$.ajax({
		url: '/login',
		type: 'POST',
		data: {action: 'isconnected'},
		success: function(reponse) {
			console.log(reponse);
			if (reponse === 'true') {
				FrontEnd.displayPage('page2');
			} else {
				FrontEnd.displayPage('page1');
			}
		}
	});
	
	$('#connexion').click(function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'login', login: $('#login').val()},
			success: function(reponse) {
				console.log(reponse);
				if (reponse === 'true') {
					FrontEnd.displayPage('page2');
				} else {
					alert('Menteur !!!');
				}
			}
		});
	});
	
	$('#deco').click(function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'deco'},
			success: function(reponse) {
				location.reload();
			}
		});
	});
	
});

Dans le répertoire "Question3" :

Vous pouvez partir du code de la question 2 pour commencer la question 3; pensez à le recopier dans le répertoire "Question3". Lorsqu'un utilisateur est authentifié, le back-end doit en plus choisir une couleur : rouge pour le premier utilisateur authentifié, vert pour le second, bleu pour le troisième, puis on cycle entre ces 3 couleurs. De plus, dans le navigateur, une grille 100x100 cellules s'affiche à l'utilisateur, comme à l'étape 1. Lorsque ce dernier clique sur une cellule, un message est envoyé au serveur. Ce dernier essaie de colorier dans un tableau à 2 dimensions retenu en mémoire (aucune sauvegarde dans un fichier ou BD) :

  • Si la cellule est déjà coloriée, rien ne se passe.

  • Sinon, la cellule est coloriée de la couleur de l'utilisateur. Le premier utilisateur authentifié colorie donc toujours en rouge, le second toujours en vert, etc.

Le serveur renvoie alors tout le contenu du tableau à l'utilisateur (les couleurs de 100x100 cases). Lorsque le navigateur reçoit cette information, il met sa grille complètement à jour.

Astuce : pour tester plusieurs utilisateurs, pensez au mode de navigation privée (un utilisateur en normal et un autre en navigation privée); pensez à déconnecter un utilisateur pour changer de couleur, etc.

Dans le fichier "login.html" :

<!DOCTYPE HTML>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>Examen Janvier 2018</title>
        <style type="text/css">
            td {
                width: 5px;
                height: 5px;
            }
        </style>
    </head>
    <body>
        <div id="page1" style="display: none;">
            <p><label for="login">Votre login :</label><input type="text" id="login" name="login" placeholder="votre login"></p>
            <p><button id="connexion">S'authentifier</button></p>
        </div>
        <div id="page2" style="display: none;">
            <table id="mytable" border="1">
            </table>
            <p><button id="deco">Déconnexion</button></p>
        </div>
        <script src="jquery.js"></script>
        <script src="frontend.js"></script>
        <script src="login.js"></script>
    </body>
</html>

Dans le fichier "login.js" :

$(function() {

	var couleur = null;
	var nbLignes = 10;
	var nbColonnes = 10;
	var couleurs = ['red', 'blue', 'green'];
	var matrice;
	
	$.ajax({
		url: '/login',
		type: 'POST',
		data: {action: 'isconnected'},
		success: function(reponse) {
			if (reponse !== 'null') {
				couleur = reponse;
				FrontEnd.displayPage('page2');
			} else {
				FrontEnd.displayPage('page1');
			}
		}
	});
	
	$('#connexion').click(function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'login', login: $('#login').val()},
			success: function(reponse) {
				console.log(reponse);
				if (reponse !== 'menteur') {
					FrontEnd.displayPage('page2');
					couleur = reponse;
				} else {
					alert('Menteur !!!');
				}
			},
			error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR.responseText)
	        }
		});
	});
	
	$('#deco').click(function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'deco'},
			success: function(reponse) {
				couleur = null;
				location.reload();
			},
			error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR.responseText)
	        }
		});
	});
	
	FrontEnd.onInitPage('page2', function() {
		$.ajax({
			url: '/login',
			type: 'POST',
			data: {action: 'initPage'},
			success: function(reponse) {
				matrice = JSON.parse(reponse);
				FrontEnd.displayPage('page2');
			},
			error: function(jqXHR, textStatus, errorThrown) {
	            console.log(jqXHR.responseText)
	        }
		});
	});
	
	FrontEnd.onDisplayPage('page2', function() {
		$('#mytable tr').remove();
		for (var ligne = 0; ligne < nbLignes; ligne++) {
			var tr = $('<tr>');
			for (var colonne = 0; colonne < nbColonnes; colonne++) {
				var td = $('<td>');
				tr.append(td);
				if (typeof matrice === 'object') {
					console.log(matrice[ligne][colonne]);
					td.css('backgroundColor', matrice[ligne][colonne]);
				}
				colorierTd(td, ligne, colonne);
			}
			$('#mytable').append(tr);
		}
	});
	
	function colorierTd (td, ligne, colonne) {
		td.click(function() {
			/*var backgroundColor = $(this).css('backgroundColor');
			if (backgroundColor === 'rgba(0, 0, 0, 0)') {
				$(this).css('backgroundColor', couleur);
			}*/
			$.ajax({
				url: '/login',
				type: 'POST',
				data: {action: 'click', couleur: couleur, ligne: ligne, colonne: colonne},
				success: function(reponse) {
					matrice = JSON.parse(reponse);
					FrontEnd.displayPage('page2');
				},
				error: function(jqXHR, textStatus, errorThrown) {
		            console.log(jqXHR.responseText)
		        }	
			});
		});
	}
	
});

Dans le fichier "Main.js" :

import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

    public static void main(String[] args) throws Exception {
    Server server = new Server(8080);
    WebAppContext context = new WebAppContext();
    HttpServlet ms = new MaServlet();
    context.addServlet(new ServletHolder(ms), "/login");
    context.setResourceBase("www");
    HttpServlet ds = new DefaultServlet();
    context.addServlet(new ServletHolder(ds), "/");
    context.setWelcomeFiles(new String[] {"login.html"});
    server.setHandler(context);
    server.start();
    }

}

Dans le fichier "MaServlet.java" :

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.owlike.genson.Genson;

public class MaServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final String[] couleurs = {"red", "blue", "green"};
    private int nbConnexions = 0;
    private String[][] tab = new String[10][10];

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String action = req.getParameter("action");
    switch (action) {
        case "initPage":
        resp.getWriter().print(new Genson().serialize(tab));
        break;
        case "login":
        String user = req.getParameter("login");
        if (user != null) {
            String couleur = couleurs[(nbConnexions++) % couleurs.length];
            req.getSession().setAttribute("userSession", user);
            req.getSession().setAttribute("couleur", couleur);
            resp.getWriter().print(couleur);
        } else {
            resp.getWriter().print("menteur");
        }
        break;
        case "isconnected":
        resp.getWriter().print(req.getSession().getAttribute("couleur"));
        break;
        case "deco":
        if (req.getSession() != null) {
            req.getSession().invalidate();
        }
        break;
        case "click":
        String couleur = req.getParameter("couleur");
        int ligne = Integer.parseInt(req.getParameter("ligne"));
        int colonne = Integer.parseInt(req.getParameter("colonne"));
        if (tab[ligne][colonne] == null) {
            tab[ligne][colonne] = couleur;
        }
        resp.getWriter().print(new Genson().serialize(tab));
        break;
        default:
        super.doPost(req, resp);
    }
    }

}

TypeScript :

TypeScript est un langage de programmation cousin du JavaScript. C'est précisément du JavaScript fortement typé. Pour utiliser TypeScript, il faut avoir installé Node.JS.

Pour créer un projet Node.JS, on utilise la commande suivante :

npm init -y

Pour ajouter TypeScript au projet, on utilise la commande ci-dessous :

npm install typescript --save-dev

Pour compiler le fichier my-file.ts, on utilise le Node Package eXecute (NPX) TypeScript Compiler (TSC) en précisant la compilation en ES6 :

npx tsc my-file.ts --target es6

Exemple du fichier my-file.ts :

function addition(x: number, y: number): number {
    return x + y;
}

const = resumt: number = addition('10', '20');
console.log(result);

Faites attention car number !== Number et lance TypeScript Error Translator !

Pour les types de tableaux, il y a deux notations :

type MyArrayOfNumbers = number[];
# ou
type MyArrayOfNumbers = Array<number>;

Il existe un type unknown quand on ne sait pas quel type on attend.