Grégory Bourguin
SysReIC - LISIC - ULCO
Javascript (Formulaires)

Formulaires & Javascript

Les formulaires et leurs champs possèdent aussi une représentation objet dans le DOM.

Il est donc possible à Javascript de les manipuler sur le client, avant la soumission (submit) au serveur des données entrées par l'utilisateur.

Cette fonctionnalité pourra se révéler très utile pour ajouter de la dynamicité à la page et faire de la vérification de formulaires avant envoi au serveur.

Les sélecteurs

Sélection par id

Comme pour tout élément du DOM, il est possible d'accéder à un form ou un de ses input en lui donnant un id, et donc en utilisant une méthode de sélection "classique" du type getElementById ou encore querySelector.

Une fois l'élément récupéré, on peut accéder à sa valeur en lecture et/ou écriture.

<form id="user-infos-01" style="width: 300px "
      action="http://web.gregory-bourguin.fr/teaching/php/requests/ajax/02_generateUserID.php"
      method="post">

    <div class="form-group">
        <label for="firstname-01">Prénom</label>
        <input type="text" class="form-control" name="firstname-01" id="firstname-01" value="Gregory">
    </div>

    <div class="form-group">
        <label for="lastname-01">Nom</label>
        <input type="text" class="form-control" name="firstname-01" id="lastname-01" value="Bourguin">
    </div>

</form>

<hr>

<p class="console_output">
    Valeur du champ au chargement : <span id="output-01"></span>
</p>

<script>
    document.addEventListener('DOMContentLoaded', function (){

        let fnameInput = document.getElementById("firstname-01")

        let output = document.getElementById("output-01")
        output.innerText = fnameInput.value        
       
    })
</script>
Resultat

Valeur du champ au chargement :

Sélection par name

Une particularité des champs est qu'ils sont généralement désignés par un nom (name).
Ce nom est utilisé au niveau du serveur (par exemple en php) pour littéralement "nommer" des données reçues.

La valeur de l'attribut name peut (aussi) être utilisée en Javascript pour désigner un champ d'un formulaire.

<form id="user-infos-02" style="width: 300px"
      action="http://web.gregory-bourguin.fr/teaching/php/requests/ajax/02_generateUserID.php"
      method="post">

    <div class="form-group">
        <label for="firstname">Prénom</label>
        <input type="text" class="form-control" name="firstname" id="firstname" value="Gregory">
    </div>

    <div class="form-group">
        <label for="lastname">Nom</label>
        <input type="text" class="form-control" name="lastname" id="lastname" value="Bourguin">
    </div>


</form>
<hr>

<p class="console_output">
    Valeur du champ au chargement : <span id="output-02"></span>
</p>

<script>
    document.addEventListener('DOMContentLoaded', function (){

        // récupération du formulaire
        let form = document.getElementById("user-infos-02")

        // accès grâce au nom du champ
        let fnameInput = form.firstname

        let output = document.getElementById("output-02")
        output.innerText = fnameInput.value

    })
</script>
Resultat

Valeur du champ au chargement :

NB: en Javascript les éléments DOM de type form possèdent aussi tous un attribut nommé elements qui est un tableau contenant tous les champs du formulaire.
Dans l'exemple précédent, on aurait pu accéder au champ grâce à : form.elements[0]

Valeur de champ

Comme nous l'avons vu dans les exemples ci-avant, il est généralement possible de récupérer la valeur d'un champ grâce à l'attribut value.

Il existe cependant quelques subtilités sur son contenu selon le type du champ :

  • text, textarea : le texte du champ.
  • select : la value de l'option sélectionnée.
  • radio : la value du bouton sélectionnée.
    (NB: tous les boutons radio d'un même groupe doivent avoir le même name)
  • checkbox : la value de la case à cocher.

On pourra aussi noter que le cas de checkbox est un peu particulier : en effet, on s'intéressera en réalité plutôt à son attribut booléen checked pour savoir si la case a été cochée.

Vous trouverez des exemples d'accès à ces valeurs dans la partie suivante.

Évènements

Les formulaires et les champs sont capables de générer un (ou des) évènement(s).

La gestion d'écouteurs est similaire à celle que nous avons vue précédemment : il suffit de récupérer l'élément que l'on veut écouter (le formulaire ou un de ses champs), et d'indiquer ce qu'on veut faire au gestionnaire d'évènements ciblé.

Évènements de champ

Les champs de formulaires proposent des gestionnaires d'évènements qui permettent de réagir lors de leur modification par l'utilisateur.

Les moyens d'interactions avec ces champs diffèrent selon leur type (ex. taper du texte, cocher une case, ...).

En conséquence, les gestionnaires d'évènements disponibles peuvent aussi différer :

Pour text, textarea, checkbox, radio, select :

  • change : déclenché lors d'une modification "validée" (quand le champ perd le focus).
  • input : déclenché dès qu'une modification est apportée.

Spécifiques à text et textarea :

  • keydown : déclenché quand une touche est enfoncée.
  • keyup : déclenché quand une touche est relâchée.
  • keypress : déclenché quand touche produisant un caractère est pressée.

Ci-dessous quelques exemples...

Exemple text & keyup

<form id="user-infos-04" style="width: 300px">
    <div class="form-group">
        <label for="pseudo">Pseudo</label>
        <input type="text" class="form-control" name="pseudo" id="pseudo">
    </div>
</form>

<hr>

<div></div>

<script>
    document.addEventListener('DOMContentLoaded', function (){

        let form = document.getElementById("user-infos-04")
        let log = form.nextElementSibling.nextElementSibling // form -> hr -> div

        form.pseudo.addEventListener('keyup', function (event){
            log.innerText =  form.pseudo.value // ou this.value
        })

    })
</script>
Resultat

Exemple select & change

<form id="user-infos-06" style="width: 300px">
    <label>Moyen de paiement</label>
    <select name="pay" class="form-control">
        <option selected disabled>Choisissez...</option>
        <option value="carte">Carte</option>
        <option value="paypal">Paypal</option>
    </select>
</form>

<hr>

<div></div>

<script>
    document.addEventListener('DOMContentLoaded', function (){

        let form = document.getElementById("user-infos-06")
        let log = form.nextElementSibling.nextElementSibling // form -> hr -> div

        form.pay.addEventListener('change', function (event){
            log.innerText =  form.pay.value
        })

    })
</script>
Resultat

Exemple radio & change

<form id="user-infos-05" style="width: 300px">
    <div class="form-check">
        <input type="radio" class="form-check-input" name="sex" id="male" value="male">
        <label class="form-check-label" for="male">Homme</label>
        &nbsp;
        <input type="radio" class="form-check-input" name="sex" id="female" value="female">
        <label class="form-check-label" for="female">Femme</label>
    </div>
</form>

<hr>

<div></div>

<script>
    document.addEventListener('DOMContentLoaded', function (){

        let form = document.getElementById("user-infos-05")
        let log = form.nextElementSibling.nextElementSibling // form -> hr -> div

        form.sex.forEach(radio => radio.addEventListener('change', function (event){
            log.innerText =  form.sex.value
        }))

    })
</script>
Resultat
 

Exemple checkbox & change

<form id="user-infos-07" style="width: 300px">
    <div class="form-check">
        <input type="checkbox" class="form-check-input" name="share" id="share" value="share">
        <label class="form-check-label" for="share">Partager les données</label>
    </div>
    <div class="form-check">
        <input type="checkbox" class="form-check-input" name="mailing" id="mailing" value="mailing">
        <label class="form-check-label" for="mailing">Recevoir les emails</label>
    </div>
</form>

<hr>

<div></div>

<script>

    let log = undefined
    let checkboxes = undefined

    document.addEventListener('DOMContentLoaded', function (){

        let form = document.getElementById("user-infos-07")

        log = form.nextElementSibling.nextElementSibling // form -> hr -> div

        // récupération de toutes les checkbox du formulaire
        checkboxes = form.querySelectorAll("input[type='checkbox']")

        // ajout des écouteurs
        checkboxes.forEach(check => check.addEventListener('change', function (event){
            displayCheckboxes()
        }))

    })

    /**
     * Affiche l'état de toutes les checkboxes
     */
    function displayCheckboxes(){
        log.innerHTML = ""
        checkboxes.forEach(cb => {
            let div = log.appendChild(document.createElement("div"))
            div.innerHTML = cb.value + " : " + cb.checked
        })
    }
</script>
Resultat

Évènements de formulaire

Les formulaires proposent 2 principaux gestionnaires d'évènements :

  • sumbit : déclenché lorsque le formulaire est soumis.
  • reset : déclenché lorsque le formulaire est réinitialisé.

Le gestionnaire submit est particulièrement intéressant car il permet de conditionner l'envoi effectif des données (la soumission) du formulaire au serveur.

Une approche classique est en effet de vérifier en Javascript (donc avant envoi) que les valeurs des champs entrées par l'utilisateur correspondent à la forme attendue par le serveur. Une telle vérification permet d'alléger le fonctionnement du site (éviter les échanges/rechargements superflus entre le navigateur et le serveur du fait d'erreurs de saisie), et accessoirement d'alléger la charge du serveur (qui n'a plus qu'à procéder aux NECESSAIRES vérifications finales).

NB: Valider en Javascript n'empêche pas la vérification des données par le serveur !
Par exemple, dans un formulaire demandant un mot de passe, Javascript pourra vérifier si celui-ci possède au moins 3 caractères (etc.), mais ce n'est en aucun cas Javascript qui va vérifier le mot de passe de l'utilisateur : pour mémoire, tout ce qui est écrit en Javascript est lisible dans le navigateur -> on ne lui enverra donc en aucun cas la liste des mots de passe possibles !!!

Exemple de vérification simple

L'exemple suivant propose un formulaire de login/password.

L'action indiquée est un script php très simple qui :

  • Renvoie un message (html) de bienvenue lorsque le login n'est pas vide et le mot de passe est correct.
  • Renvoie un message (html) d'erreur lorsque le login est vide et/ou le mot de passe est erroné.

NB: ce script php est un "fake" -> le mot de passe attendu est toujours 'mdp'.


Le code ci-dessous présente une version sans vérification en Javascript :

Le formulaire SANS vérification Javascript
<form style="width: 300px"
      action="http://www.web.gregory-bourguin.fr/teaching/php/requests/ajax/03_fakeLogin.php"
      method="post"
      target="_blank">

    <div class="form-group">
        <label for="login-sans-verif">Login</label>
        <input type="text" class="form-control" name="login" id="login-sans-verif">
    </div>

    <div class="form-group">
        <label for="password-sans-verif">Mot de passe</label>
        <input type="password" class="form-control" name="password" id="password-sans-verif">
    </div>

    <button type="submit" class="btn btn-primary">Envoyer</button>
    <button type="reset" class="btn btn-secondary">Reset</button>

</form>
Formulaire SANS vérification Javascript

On ajoute maintenant un (Java)script de vérification qui :

  • empêche l'envoi tant que le nom est vide et/ou le mot de passe n'a pas au moins 3 caractères
  • affiche un message en cas d'erreur
Le formulaire AVEC vérification Javascript
<form id="user-infos-verif" style="width: 300px"
      action="http://www.web.gregory-bourguin.fr/teaching/php/requests/ajax/03_fakeLogin.php"
      method="post"
      target="_blank">

    <div class="form-group">
        <label for="login">Login</label>
        <input type="text" class="form-control" name="login" id="login">
    </div>

    <div class="form-group">
        <label for="password">Mot de passe</label>
        <input type="password" class="form-control" name="password" id="password">
    </div>

    <button type="submit" class="btn btn-primary">Envoyer</button>
    <button type="reset" class="btn btn-secondary">Reset</button>

</form>

<br>
<div id="message" style="color: red"></div>

<script>

    const PWD_MIN_LEN = 3

    document.addEventListener('DOMContentLoaded', function (){

        // récupération de la zone de messages
        let message = document.getElementById("message")

        // récupération du formulaire
        let form = document.getElementById("user-infos-verif")
        
        // enregistrement dans le gestionnaire d'évènements 'submit'
        form.addEventListener('submit', function (event){

            // on bloque le comportement par défaut (l'envoi automatique)
            event.preventDefault()

            // on procède aux vérifications
            if(form.login.value == ""){
                message.innerHTML = "Le <b>login</b> ne doit pas être vide !"
            }else if(form.password.value.length < PWD_MIN_LEN){
                message.innerHTML = "Le <b>mot de passe</b> doit avoir au moins " + PWD_MIN_LEN + " caractères !"
            }else{
                // les champs sont ok : on peut soumettre le formulaire
                form.submit()
                message.innerHTML = ""
            }

        })
        
        // ... pour le fun, on déclenche une alerte lors d'un reset
        form.addEventListener('reset', function (event){
            window.alert("Réinitialisation du formulaire")
        })

    })
</script>
Formulaire AVEC vérification Javascript

Dans cet exemple, lorsque le formulaire a bien été vérifié, il est soumis par Javascript grâce à l'appel form.submit(). Ce type d'envoi des données au serveur correspond à une soumission "classique" de formulaire, i.e comme si on n'avait pas fait appel à Javascript.

Il faut cependant noter qu'après la vérification (en Javascript), on aurait tout à fait aussi pu remplacer cet appel à .submit() par une requête avec XMLHttpRequest ou fetch, et ainsi récupérer la réponse du serveur pour l'intégrer dynamiquement à la page en cours de visualisation.

TP 05

Recréez la page ci-dessous en faisant attention aux détails aussi bien visuels que fonctionnels.

Informations à prendre en compte :

  • Le select affiche les noms des pays, mais la value associée est en réalité le code d'appel correspondant (32 pour la Belgique, 33 pour la france).
  • Vous remarquerez que lors d'une saisie, la partie "pré-visualisation" sous forme de carte de visite reformate les données.
    En particulier, la 1ere lettre du prénom est toujours en majuscule, le reste en minuscules, et le nom est quant à lui complètement en majuscules.
    Vous pourrez utiliser les méthodes Javascript toUpperCase() et toLowerCase().
  • Pour récupérer les prénom et nom(s) à partir du nom complet entré par l'utilisateur, sachez que la méthode Javascript split(...) permet découper une string en un tableau de mots.
    La méthode join(...) permet à l'inverse de reconstituer une string à partir d'un tableau de string.
  • La méthode Javascript trim() permet de "nettoyer" une string en enlevant les espaces qui éventuellement l'entourent.
  • L'image (le smiley) est un Bootstrap Icon.
  • L'action liée à ce formulaire est un script php dont l'adresse est :
    http://www.web.gregory-bourguin.fr/teaching/php/requests/ajax/00_test_REQ.php .
    NB: Ce script ne fait que renvoyer la liste des données qu'il a lui même reçues.
  • La soumission du formulaire lance une vérification qui, en cas d'erreur, colorie le(s) champ(s) incriminé(s), et affiche un message sous le formulaire.
  • La vérification du Téléphone (ne doit contenir que des chiffres) peut être réalisée grâce à RegExp.
  • Quand il n'y a pas d'erreur de remplissage, le formulaire n'est pas soumis "classiquement" : une requête fetch est lancée, et le résultat de l'action est injecté dans la page en cours.
  • Tout est bien "nettoyé" en cas de correction d'erreur/reset/validation...
Résultat attendu.

Upload Preview

Les formulaires peuvent aussi servir à uploader des fichiers vers le serveur.

Nous verrons dans un autre cours comment gérer cet upload en PHP .

Javascript et le DOM peuvent servir à offrir à l'utilisateur d'un formulaire une prévisualisation du fichier qui va être uploadé (donc avant envoi/soumission du formulaire).

Le code ci-dessous donne un exemple de prévisualisation d'un fichier sélectionné par l'utilisateur dans un formulaire d'upload : l'évènement change est déclenché lorsqu'un fichier a été sélectionné dans l'input ; un FileReader est utilisé pour lire le fichier et, quand le résultat est prêt (evènement onload du FileReader), le résultat est utilisé en tant que src d'une balise img.

NB : puisqu'on utilise le résultat dans une balise img, il faut que le fichier sélectionné soit bien une image, sinon on ne voit rien...

Prévilusalisation de fichier à uploader

<style>
    .form-label{
        height: 200px;
        display: flex ;
        justify-content: center
    }
    #preview-image{
        object-fit: contain;
    }
</style>

<form id="file-upload-form" action="" method="post" enctype="multipart/form-data">
    <div class="mb-3">
        <div class="card" id="preview">
            <label for="fileInput" class="form-label" style="display: flex ; justify-content: center">
                <img id="preview-image" src=""/>
            </label>
        </div>
        <input class="form-control" 
            type="file" id="fileInput" name="fileInput" 
            accept="image/png, image/gif, image/jpeg">
    </div>
    <div class="mb-3">
        <button id="submit-button" type="submit" class="btn btn-primary" style="width: 100%" disabled>Envoyer</button>
    </div>
</form>

<script>

    document.addEventListener('DOMContentLoaded', ()=>{
        const submitButton = document.getElementById("submit-button") ;
        const preview = document.getElementById("preview-image") ;

        const reader = new FileReader() ;
        reader.onload = (e)=>{
            preview.src = reader.result ;
        }

        const fileInput = document.getElementById("fileInput") ;
        fileInput.addEventListener('change', ()=>{
            let file = fileInput.files[0] ;

            if(file && file.type.split('/')[0] === "image"){
                reader.readAsDataURL(fileInput.files[0])
                submitButton.disabled = false ;
            }else{
                submitButton.disabled = true ;
                preview.src = "" ;
            }

        })

    })

</script>
Previsuaisation de fichier à uploader
(NB: la prévisualisation ne fonctionne ici qu'avec un fichier image...)