Appel à avis : Handlers d’événement inline en Javascript

À la suite de mon dernier article, au sujet des technos navigateur, je prépare un autre billet consacré aux langage alternatifs qui génèrent du Javascript. Et au détour de tout ça, je suis tombé sur une page de la documentation Dart qui m’a un peu interloqué.

Cette page, consacrée à l’intégration de Dart dans du HTML, parle entre autre des handlers d’événement inline (désolé pour le franglais, je n’ai pas trop de synonyme correct qui me vienne spontanément à l’esprit). Vous savez, c’est le fait de mettre un “onclick” ou un “onchange” sur un élément HTML pour appeler une fonction Javascript lorsque l’événement en question est exécuté.

With JavaScript, programmers can embed inline event listener code directly onto HTML nodes. However, this is typically discouraged in modern JavaScript applications. HTML pages generally render more quickly if listeners are added programmatically afterwards. Modern security guidelines also discourage inline code. As such, we propose to not allow inline Dart listeners.

J’ai été un peu étonné de lire ça. En cherchant 30 secondes sur le web, je suis tombé sur une question/réponse du site StackOverflow :

Most experienced developers shun this method, but it does get the job done; it is simple and direct.

Je fais donc appel à l’aide des lecteurs de ce blog.
Je code pas mal de Javascript ; pas autant que de PHP, mais ce doit être au final mon deuxième langage en quantité de code écrit (eh oui, même devant le C). J’écris du code que j’estime propre, en créant des objets et en les rangeant dans des namespaces, utilisant des callbacks là où ça se justifie, tout en se gardant du « callback hell ».

Et personnellement, j’ai toujours trouvé qu’il était plus facile de maintenir du code avec les appels d’événements indiqués dans le HTML, plutôt que de les positionner par Javascript. Quand on cherche ce qu’il se passe quand on clique sur tel élément, ou quand on tape du texte dans un champ texte, il est très facile de regarder le code HTML, voir quel méthode de quel objet est appelée, et enfin aller voir directement dans cette méthode.

Donnez-moi votre avis s’il-vous-plait.

Préférez-vous ce type de code :

<script>
var site = new function() {
    this.init = function() {
        // quelques initialisations
    };
    this.checkContent = function(text) {
        // un peu de code applicatif
    };
};
</script>
<input type="text" name="content"
 onblur="site.checkContent(this.value)" />

Ou plutôt celui-ci :

<script>
var site = new function() {
    this.init = function() {
        // initialisation du handler d'événement
        $("#edit").on("blur", function() {
            site.checkContent();
        });
    };
    this.checkContent = function() {
        var text = $("#edit").val();
        // un peu de code applicatif
    };
};
</script>
<input id="edit" type="text" name="content" />

Et pourquoi ?

23 commentaires pour “Appel à avis : Handlers d’événement inline en Javascript

  1. Je préfère éviter les événements en ligne , pour la simple et bonne raison que je trouve que ça devient très vite un bordel incroyable dans le code si on à beaucoup d’événements.
    Je regroupe tout dans un fichier js inclus à la fin de ma page et ne pas avoir de JS du tout dans l’html

    Sans trop dire de connerie je pense aussi que tous le code inline ne peut pas être mis en cache , du coup c’est toujours ça en plus à servir à chaque page, certes c’est pas grand chose mais sur un fort traffic ca peut ne pas être négligeable.

  2. Je suis d’accord avec Grunk, sur l’aspect bordélique du inline.

    De plus, les handlers inline peuvent faire appel à des méthodes de lib Javascript pas encore chargé (inclusion asynchrone, ou en fin de fichier), du coup si on le déclenche avant la fin du chargement de la page, au mieux ça fait une erreur, au pire on se retrouve avec des résultats inattendus.

    Pour l’aspect cache, cela dépend si l’on a une couche de cache HTML devant ou pas, mais c’est vrai que côté navigateur c’est pas top.

  3. J’ai l’habitude de mettre l’écoute des événements dans le code js d’ajouter un commentaire html à côté de l’élement input pour rappeler que j’écoute tel ou tel évènement (commentaire html qui sera quoi qu’il arrive supprimé avec uglify)

  4. Vous trouvez vraiment que c’est si bordélique que ça ?
    Au niveau de la maintenance, je trouve quand même qu’il est plus simple de regarder le onclick d’un bouton, plutôt que de naviguer dans du code Javascript pour retrouver où le handler est positionné (ce qui peut parfois être très long).

    Sur l’aspect synchrone/asynchrone, je suis d’accord d’un point de vue théorique. Mais, en pratique, pour des événements qui sont générés par l’utilisateur (onclick, onchange, onkeypress, onfocus, onblur, …), il n’y a aucune problème ; et comme c’est le cas général….

    Et quant au cache, on parle d’un nombre d’octets tellement faible que ça ne me semble pas vraiment pertinent. Ou alors c’est que vous développez une véritable « application web », auquel cas il vaut mieux utiliser un framework comme AngularJS.

  5. Je trouve qu’il est plus propre de découpler le code javascript et html, peut-être un reliquat du temps ou un site devait absolument fonctionner sans javascript.

    Et pour pouvoir faire de cette manière nous utilisons beaucoup l’attribut html data-*.
    Cela permet d’attribuer facilement la même action à plusieurs éléments sans ce baser sur une classe CSS qui n’est pas fait pour cela.

    Par exemple

    <a href="/quelque-chose/" data-action="supprimer">Supprimer </a>

    et en fin de fichier html on attaches les éléments comportant ‘data-action= »supprimer »‘ à une action (une boite de dailogue, par exemple).

    Le code est mieux découplé, donc plus facile à réutiliser et à tester.

  6. Je ne mets pas non plus d’événements en ligne dans ma sémantique HTML car ainsi nous pouvons dissocier ce qu’est un élément, de ce à quoi il ressemble, et de comment il se comporte.

    Exemple avec la structure ci- dessous :

    <span class="more-info">Plus d'informations</span>
    <div class="content">
    ... Le détail des informations ...
    </div>

    Le « .content » est par défaut caché.

    – Aussi sur Desktop (Grand écran, pas de support touch) un JS externe associera à cet élément l’équivalent d’un « onmouseover/onmousedowm » sur « .more-info » pour afficher au survole de la souris le contenu de « .content » par exemple en tooltip.
    – Sur mobile au contraire (Petit écran, support du touch) un JS externe associera à cet élément l’équivalent d’un « onclick » sur « .more-info » pour afficher/cacher au clique le contenu de « .content » par exemple en-dessous.

    On peut également envisager que d’autres comportement sirait à prévoir en fonction du périphérique/résolution d’écran. Ou même que pour un changement de design le comportement est toujours celui du clique.

    Cela permet plus d’avoir de souplesse à la maintenance et de conserver une structure unique quelque soit l’habillage ou les fonctionnalités qu’on décide d’associer.

  7. Lo,

    La raison pour laquelle cette méthode est découragée est surtout qu’elle implique, de fait, la création de variables globales.

    Pour reprend ton exemple, dans onclick= »site.fonction() », si est une vriable globale et c’est le mal ;). Utiliser les écouteurs d’évènements te permet de wrapper tout ton code dans une fonction anonyme qui ne sera pas accessible dans du code tiers.

    (function() {
    var site = new function() {
    this.init = function() {
    // initialisation du handler d'événement
    $("#edit").on("blur", function() {
    site.checkContent();
    });
    };
    this.checkContent = function() {
    var text = $("#edit").val();
    // un peu de code applicatif
    };
    };
    })();

  8. @Waldo : Tu trouves que c’est plus propre parce que bien découpé. Je comprends. Mais en terme de maintenance (tu sais, quand tu dois revenir sur du code 2 ans après qu’il ait été écrit par quelqu’un d’autre), je trouve que ça complique les choses. Dans ton exemple, il faut ouvrir le code HTML, voir le data-action, puis rechercher dans le code Javascript où le handler est défini pour ce data-action, et enfin on peut rechercher le code applicatif de ce handler. Ça fait quand même plus d’étapes.

    @Haeresis : Ah, là je prends cet argument. Le fait de pouvoir mettre en place des comportements différents en fonction du device est une vraie amélioration, impossible avec des handlers inline. Ça c’est un deal-breaker que j’accepte 🙂

  9. Salut, je suis d’accords avec ce qui est écris précédemment. Personnellement voila comment j’écrirai ton exemple:

    var site = (function(){
        var $edit = $("#edit");
    
        function checkContent () {
            var text = $edit.val();
            // un peu de code applicatif
        };
    
        return {
            init: function() {
                // initialisation du handler d'événement
                $edit.on("blur", function() {
                    checkContent();
                });
            }
        }
    })()
    site.init();

    J’utilise ici le « module pattern » dont les avantage sont entre autre:
    1: Seule l’api exposé dans le return se retrouvera accessible dans la variable « site » car la protée des variables checkContent et $edit est limitée par l’utilisation de (function(){…}) et peuvent ainsi être considéré comme privé (notamment checkContent qui n’est utilisé que dans le init )
    -> organisation propre (fonction et variable publique et privé)
    2: les parenthèses finales font que la fonction s’exécutera au chargement du script donc en déclarant $edit, celle ci sera chargé une seule fois et gardé en cache au lieu d’aller chercher la balise dans le DOM systématiquement
    -> Performance

    Pour en revenir à la question initiale, le javascript est à bannir du HTML que ce soit au niveau des attributs ou même des balise car :
    1: les performances de l’interpréteur javascript sont moindre ne serait ce que parce qu’il faut trier le html du javascript
    2: avoir tout le code dans des fichiers .js permet d’avoir en un seul endroit toute la logique javascript (écouteur, event, callback…) et de ne pas disperser la logique. Et puis ce n’est pas parce que’on veut accrocher un écouteur à une balise que l’on veut que se soit systématique ou définitif. On peut décider de l’enlever ou de ne l’accrocher que lorsque d’une condition est rempli se que n’offre pas l’inclusions dans le HTML

  10. @Nicolas : Mouais. Bof. Mauvais argument à mon avis. La seule variable globale, dans mon exemple, c’est “site”, dont le but est justement de gérer les interactions effectuées sur la page. Plus que de savoir si le code pourrait être appelé par un élément qui ne devrait pas le faire (attention à ne pas faire de code défensif), je pense que l’important est la facilité de lecture et de maintenance du code.

  11. Armel Fauveau m’a pointé l’article de Wikipédia «Unobtrusive JavaScript». C’est très intéressant. Il y a certains arguments qui me semblent pleinement recevables, notamment les questions de dégradation et d’accessibilité.

    Par contre, je reste hermétique à la plupart des arguments concernant la séparation, sauf avec la vision apportée par le commentaire de Haeresis.

  12. @Marko : Je comprends ce que tu veux dire, et c’est intéressant. Pour me faire l’avocat du diable, je peux quand même répondre à certains de tes arguments :
    1.1 : Le manque de gestion de la visibilité est l’un des gros reproches que j’ai pour le Javascript. Donc plutôt que de mettre en place ce genre d’écriture assez verbeuse et difficile à maintenir, je m’en tiens à préfixer les éléments privés avec un underscore. Pour en avoir discuté encore vendredi dernier au Forum PHP, j’ai l’impression que c’est une pratique très répandue.
    Pour moi, la maintenance est quelque chose de très important, et donc la lisibilité du code. En fait, je regarde plutôt du côté des alternatives qui proposent de la visibilité et qui génèrent du JS (Dart, TypeScript et consort).

    1.2 : Oui, on est bien d’accord, moi aussi je récupère les éléments nécessaires à l’initialisation. Là, pour l’exemple, c’est un peu inutile…

    2.1 : Je ne sais pas pour toi, mais je n’ai jamais eu la moindre baisse de performance à cause de quelques (dizaines) de onclick/onchange/onfocus dans une page HTML.

    2.2 : Pour moi, l’important est que le code applicatif soit proprement rangé dans des fichiers JS séparés. Je suis d’accord qu’un handler peut être temporaire, et dans ce cas je le gère aussi en le positionnant par le code. Sauf que c’est loin d’être le cas général. Dans la grande majorité des cas, on place surtout des onclick qui seront valables dans tous les cas.

    Je le répète, si c’est pour faire de véritables applications web, autant utiliser un framework comme AngularJS, non ?

  13. Personnellement je pense que la séparation est indispensable car elle correspond à deux usages différents et je rejoins donc le point de vue de l’unobtrusive JS.

    Le HTML est là pour une bonne raison et se doit de fonctionner seul.
    Ton input #edit doit donc valider un formulaire et déclencher une action serveur.
    Ça c’est le rôle du HTML, et il n’y a aucune raison de mettre du JS là dedans.

    Le rôle du JS dans ton exemple va être par exemple de faire ça en AJAX pour améliorer l’expérience utilisateur.
    Dans ce cas là ton module JS est indépendant et n’a aucune raison d’être dépendant de ce que tu aurais du mettre dans ton code HTML. Il sera de fait aussi plus facilement réutilisable.

    Dans l’autre (mauvais) exemple cité de « Plus d’informations » avec une classe « more-info », je pense que le span « More-info » n’a rien à faire dans le HTML.
    Par défaut le HTML se doit d’afficher le contenu du div et c’est tout.
    En JS on va « améliorer » l’expérience utilisateur en masquant le contenu puis en ajoutant au DOM un bouton d’action (et pas un span) dont on définira directement l’action.

    Pour résumer :
    – Ecrivez le HTML sémantiquement et de manière autonome, sans aucune balise inutile possiblement utilisée plus tard par le JS.
    – Puis au besoin ajoutez une couche de JS bien séparée et indépendante qui viendra en mettre plein les yeux à tous les aficionados de sliderparalajax.

  14. Là tu es dans le cas où tout le JS de ta page est fait par toi. Mais dans le cas de plus en plus fréquent où tu charges n modules tiers tu comprends vite le problème des variables globales.

    Vraiment, les variables globales sont à proscrire.

  15. Effectivement tu as raison pour le coté performance et les points que tu évoquesne sont pas faux mais pour moi tu fais une erreur philosophique. Comme beaucoup de gens tu cherches à rendre JavaScript lisible et compréhensible au lieu d’apprendre à le lire et à le comprendre. Comme tu considères qu’il est verbeux tu voudras trouver un moyen de le générer à l’aide d’une surcouche (CoffeScript ou autre) au lieu de véritablement apprendre le pourquoi du comment.

    Attention rien de pédant là dedans, il y a 3 ans le js me servait à valider des formulaires, point. J’ai cherché tous les moyens de ne pas en écrire (génération par PHP, GWT etc…) jusqu’au jour où j’ai vraiment dû m’y mettre et que le javascript c’est éclairé à la lumière chose aussi simple que la porté des variables ou des closures.

    Si on en revient aux handlers il y a des dizaines d’arguments en faveur de les avoir dans le HTML. Simplicité, facilité et puis « c’est comme ça que j’en ai l’habitude ». Mais comme le CSS/xHTML en sont temps, ça ne vas pas dans le sens de l’histoire et de l’évolution. HTML5 à officialisé la séparation totale du JS et du HTML avec les attributs data notamment.

    Pour finir, c’est une question d’état d’esprit. On trouve toujours en 2013 du CSS dans l’attribut « style » et des fois ça se justifie! Comme pour le js dans le onclick, ça peut se justifier. Mais se ne doit pas être la norme

  16. @Marko : OK, je comprends la logique. Un grand merci à tous d’avoir pris le temps d’échanger de manière constructive avec moi et de m’avoir donné des exemples concrets.

    Je pense quand même que chercher la plus grande lisibilité reste une quête qui n’est pas vaine 😉

  17. Juste pour faire avancer (quoique) la discussion, je vous rapporte un échange sur Twitter.
    En résumé, il m’est opposé l’argument que mettre un listener en inline dans du HTML, c’est équivalent à écrire du style CSS en inline. Ma réponse est que non, ce serait la même chose uniquement si on mettait du code applicatif dans le onclick ; pas quand on met juste un appel à une méthode.

    Pour expliquer un peu ce point de vue, on peut voir le nom d’une classe comme un point d’entrée dans un fichier CSS. Ainsi, quand on écrit :

    <div class="pouet"> ... </div>

    On affecte le style “pouet” à la div en question. On indique au navigateur le comportement à adopter pour l’affichage de cet élément, en lui disant où il doit aller chercher le complément d’information.

    Il n’est pas complètement idiot de se dire que les noms de fonctions et méthodes sont là encore des points d’entrée, cette fois vers un fichier Javascript. Et quand on écrit :

    <div onclick="site.trululu()"> ... </div>

    On indique au navigateur le comportement à adopter lorsque cet élément est cliqué.

    Je ne remet pas en question les bons arguments qui m’ont été donnés pour ne pas pratiquer l’inlining. Je dis juste que mon analogie me semble plus juste que celle exprimée…

  18. Juste pour montrer que ma question n’était pas complètement idiote, et que d’autres se la posent, voici des liens vers des questions posées sur StackOverflow. Elles sont relatives au framework AngularJS, car il propose des directives du type ng-click similaires dans leur sémantique aux onclick et consorts. Et je me permets d’extraire des morceaux de réponses (mais je vous invite à lire les pages en entier).

    AngularJS: Is ng-click “a good practice”? Why is there no ng-{event} in AngularJS?

    Well, people who really like Unobtrusive JavaScript might say it is bad. Angularians (and those with a flex background) see value in the more declarative approach.

    Don’t the data attribute options used in Bootstrap, Angular.js, and Ember.js conflict with Unobtrusive Javascript principles?

    Unobtrusive Javascript is a good practice for many places on the web. The frameworks you mentioned are often used for creating full-blown Javascript applications. In many of these apps, the experience without Javascript is often a blank page. In that environment, the value of separating your markup from Javascript is relatively low.

  19. Les gestionnaires d’évènements embarqués dans le HTML avaient leur logique du temps où le code JavaScript ajoutait un peu de confort à une page capable de toute manière d’être affichée en pur HTML/CSS. Et ce cas était la norme avant HTML 5.

    À partir du moment où le page embarque en fait une application JavaScript, et que cette page n’existe qu’au travers de cette application, alors il faut changer de perspective. On a un logiciel, écrit en JS, qui manipule des templates écrits en HTML. Et c’est alors que d’ajouter des évènements dans la structure des templates devient une mauvaise pratique.

  20. Je suis assez d’accord avec Cpag.
    Avant j’étais full inline car plus facile pour moi pour la maintenance. Depuis je préfère la deuxième méthode car tout est dans le fichier js, plus la peine de switcher entre le code html et js. On a d’un côté une App JS de l’autre un template HTML

  21. Bonsoir,

    Je suis tombé sur votre blog à la suite du visionnage d’une conf sur le noSql…

    Mon retour d’expérience avec Extjs est que ni la première et ni la deuxième ‘écriture n’est pertinent(bien que possible) pour une ‘grosse ‘application web full JS.
    En effet, pour la première écriture, (code JS dans une balise), il faut savoir que l’on peut être amené à créer nous même nos propres évènements et qui ne seront pas interprété par le navigateur(ex, destroy d’une popup). On sera amené alors à gérer des events à la fois dans la vue et dans le code JS. (dans ce cas, la deuxième écriture est pertinente).
    Deuxièmement, je me pose la question lors de la manipulation du DOMcréation de checkbox dynamique), vas-t-on mettre une chaîne de caractère pour remplir l »attribut onclick ou on blur ? Le mieux est d’utiliser la deuxième écriture en récupérant l’event onblur (sur des id générés dynamiquement) et d’y mettre un callback.
    Enfin, le dernier argument est de pouvoir exécuter ta fonction checkContent dans un état spécifique (configuration précise) , la notion de  »closure »devient vitale lorsque l on a plus de 200 objets à manipuler.

    Enfin, la deuxième écriture est grosso modo la base architecturale de EXTJS 1 à 3.4 et elle a été un vrai frein en matière de création d’application Web. A l’époque, on ne savait pas faire une application maintenable facilement.(ce qui n’est plus le cas avec la version 4, avec l’apparition du MCV côté client)

    Sinon, les deux se valent dans le cadre d’un travail au niveau de la vue. Par contre, il ne faut pas perdre de vue que le DOM n’est qu’une partie visible de l’iceberg… en effet, le langage JS manipule le DOM, mais aussi le LocalStorage, le fileSystem API, le cache manifest, le webSQL, les cookies, le caches, les websockets …, c’est la raison pour laquelle on a comme même tendance à tout centraliser au niveau du JS.

    voila, bonne nuit …

  22. En fait les gestionnaires d’événements (EventListener ou attributs HTML) sont à aborder sereinement dès lors qu’on admet deux visions d’une même problématique :

    • La vision du concepteur de documents : il doit voir le source des actions « élémentaires » qu’il gère, que ce soit lui qui les crée, ou qu’elles soient construites par un module applicatif ; c’est sa prérogative d’auteur documentaire, au sens Web 3.O du terme si l’on peut dire ainsi (c’est à dire un document où l’impression est anecdotique et la réactivité/interactivité sémantique primordiale).

    • La vision du concepteur d’applicatif : il doit voir le document dans son ensemble, donc être capable de le scruter (« écouter ») avant d’agir avec sagesse ; d’où les EventListeners, mais avec avec le dernier paramètre à « true », pour ne pas masquer des actions que devraient connaître l’auteur documentaire.

    Le gros inconvénient des EventListeners est de ne pas être des objets et donc de ne pas être attachés aux éléments. En particulier on ne peut connaître a posteriori les actions associées ainsi à un élément donné. En revanche la définition d’une action en attribut permet à tous les concepteurs œuvrant autour d’un même document de le savoir.

    Le gros avantage des EventListeners réside dans leur côté éphémère : on peut rapidement installer un Listener sur un élément, puis le jeter après usage. Dans cette situation on suppose que le concepteur documentaire n’a pas besoin de connaître ces Listeners, puisqu’ils sont pour lui anecdotiques, seulement techniques.

    Cette double vision d’une « page HTML » (documentaire + applicative) ne peut être claire que dans une démarche de construction d’un « docapp », un document applicatif, où deux auteurs —même s’ils sont pilotés par un seul cerveau— doivent travailler a priori indépendamment mais où l’auteur documentaire induit une mise en œuvre —ou une conception— de modules applicatifs, selon ses besoins.

    Elle n’est possible et efficace que dans un principe de sémantisation d’un document, c’est à dire en préférant les classes et des identifiants compréhensibles par le concepteur documentaire qui n’a pas vocation à être développeur de Javascript, aux classes et identifiants techniques et abscons que le développeur d’applicatif sème souvent à tout va pour les besoins de sa cause..

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Notifiez-moi des commentaires à venir via email. Vous pouvez aussi vous abonner sans commenter.