Si un script ajoute un listener

submit
sur un
<form>
et que ce formulaire a été soumis en utilisant un
<input formaction=.../>
, alors le listener n’a aucun moyen standard de connaître la vraie destination (
action
) du formulaire. Mieux vaut alors faire plusieurs
form
, ou poser un listener
click
sur ces
input
.

Javascript, et
form submit

Il arrive parfois, dans un jeu web ou un site en général, d’ajouter un listener javascript sur un formulaire

form
en se servant de
document.querySelector('form').addEventListener('submit', function (e) {});
. Cela permet, par exemple, de soumettre le formulaire en AJAX et de ne suivre la redirection de la réponse (Post-Redirect-Get) que si celle-ci est positive (
200 OK
). Dans le cas contraire, on laisse le navigateur sur la même page en affichant simplement l’erreur que l’utilisateur a faite. C’est ce que j’utilise sur Iamanoc.com, un réseau social destiné aux personnages de fiction (Own Characters).

Mais comment ce listener se comporte-t-il si l’un des bouton de soumission recourt à

? En effet, cet attribut permet de dire au navigateur que le formulaire doit être soumis à une URL différente de celle définie dans le
action
du
form
. Il faut donc être capable de la récupérer dans son listener, pour soumettre le formulaire en AJAX à la bonne adresse.

L’exemple

Prenons le code suivant:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Formaction hook</title>
        <script>
            window.addEventListener('load', function() {
                document.querySelector('form').addEventListener('submit', function (e) {
                    var data = {
                        "this": this,
                        "Event": e,
                        "Event.explicitOriginalTarget": e.explicitOriginalTarget
                    };
                    console.log('listener de "submit" sur le "<form">', data);
                   
                    var container = document.createElement('div');
                    container.appendChild(document.createTextNode(JSON.stringify(data)));
                    this.parentNode.appendChild(container);
                    e.preventDefault();
                });
            });
        </script>
    </head>
    <body>
        <form action="?action=original" method="POST">
            <input type="text" name="x" value="123"/>
            <input type="submit" value="Go!"/>
            <input type="submit" formaction="?action=formaction" value="FormAction!"/>
        </form>
        <p>Click one of these buttons, and see the event data below (more details in your browser console)</p>
    </body>
</html>

Dans cet exemple, si on soumet le formulaire, alors le listener se déclenche. Or, on s’aperçoit que le listener n’a pas accès au bouton ayant demandé la soumission du formulaire, et que l’

Event
de soumission ne donne pas d’information quant à la cible du formulaire. Au final, le listener ne sait pas où soumettre le formulaire: était-ce le bouton de soumission “normal” qui a été utilisé (il faut donc envoyer la requête AJAX vers
form.getAttribute('action')
) ou était-ce le bouton de soumission ayant un
formaction
(il faut donc envoyer la requête AJAX vers
input.getAttribute('formaction')
?

Solutions

explicitOriginalTarget

Il existe une propriété de l’

Event
que le listener récupère (le paramètre
e
dans le code ci-dessus):
explicitOriginalTarget
. Cette propriété indique le bouton qui a déclenché l’
Event
. On peut donc en lire l’attribut
formaction
(s’il existe) et savoir où envoyer la requête AJAX. Malheureusement, cet attribut n’est pas standard (propre à Firefox) et il faut donc l’éviter.

Listener sur l’
input

Une alternative consiste à poser un listener

click
sur les
input
possédant un
formaction
. Ce listener peut alors modifier l’attribut
action
du
form
avant de laisser poursuivre l’Event de soumission. Ce principe peut s’étendre aux autres attributs
form*
. Néanmoins, cela implique que tous les
input
d’un
form
doivent avoir leur propre
formaction
.


document.querySelector('input[formaction]').addEventListener('click', function (e) {
  this.form.setAttribute('action', this.getAttribute('formaction'));
});
Un listener sur les
input
peut corriger le problème

Plusieurs
form

Finalement, la solution la plus simple et fiable consiste à avoir plusieurs formulaires. Par exemple, au lieu d’avoir un seul formulaire pour changer le niveau d’un bâtiment de votre jeu web et 3 boutons correspondants à “construire” (

formaction="?level=+1"
), “démolir” (
formaction="?level=-1"
) et “rénover” ((
formaction="?level=0"
), on peut avoir trois formulaires, chacun renvoyant vers la bonne action. C’est un peu dommage, et il y a surement des cas plus complexes où
formaction
ne sera pas facilement remplaçable par plusieurs formulaires, mais c’est une solution suffisante.