11540 sujets

JavaScript, DOM et API Web HTML5

Bonsoir à tous

Suite aux passionnantes discussions sur le sujet "comment apprendre un nouveau langage informatique quand on a dépassé 70 ans", vous avez attiré mon attention sur l'intérêt d'attacher un événement dynamiquement à un ensemble d'objets.
Si je comprends bien, il est préférable de créer une classe "toBeChecked" qui ne fait rien en CSS de façon à pouvoir écrire quelque part "$('toBeChecked').change()..." plutôt que onchange='...'
En conséquence, quand on crée un objet dynamiquement il faut relancer la commande "$('toBeChecked').change()..." Est-ce bien cela?

Dans mes pages, il y a un formulaire de saisie dont le nombre de champs peut être modifié dynamiquement, par exemple un formulaire qui prévoit de saisir un numéro de téléphone est suivi d'un http://polyphonies.idf.free.fr/images/add.png . Quand on clique sur cette image, on ajoute un champ de saisie téléphonique permettant à la personne de donner plusieurs numéros de téléphone.
Pour faire cela je pars d'une copie de la zone de saisie téléphonique (d'où ma question précédente sur le clonage). Si je comprends bien je dois refaire un "$('toBeChecked').change()..." pour les zones de saisie de la zone?
Modifié par PapyJP (13 Feb 2015 - 19:35)
Bonsoir PapyJP,

Ce que vous décrivez la est une problématique connue : Comment déclarer des évènements sur des éléments qui n'existent pas encore au document.ready ?

Il y a plusieurs techniques, comme déclarer un nouvel évènement à chaque fois que l'on crée un élément ou encore rajouter un attribut onQuelqueChose="" à cet élément (déconseillé Smiley langue ) ...

Heureusement jQuery a pensé à tout, ça s'appelle la délégation d'évènement. Le principe est que l'on enregistre l'évènement sur un élément parent de l'élément ciblé. Exemple ici un div conteneur des checkboxes. Comme ça que les éléments ciblés existent ou pas lors de l'enregistrement de l'évènement, il se propageront jusqu'au parent (qui lui existait au chargement de la page) qui donnera l'alerte.

Je vous ai fait un cas représentatif pour mieux comprendre comment ça se met en place avec jQuery, c'est très simple vous verrez :
Exemple sans délégation d'évènement (fail)
Exemple avec délégation d'évènement

Sinon la doc à ce sujet est assez explicite
http://learn.jquery.com/events/event-delegation/
Modifié par Freez (13 Feb 2015 - 21:02)
Merci de la réponse, mais généralement la façon (à mon avis) propre de traiter la vérification d'un élément input est de traiter l'événement "change" sur CET ÉLÉMENT (onchange="checkItem(this)" par exemple). Sinon on peut bien entendu vérifier tous les champs d'un formulaire à chaque "blur", ou pourquoi pas toutes les 10ms avec un timer, ou bien attendre la fin de la saisie (ce qu'on fait de toute façon), ou bien attendre que le serveur le fasse, car de toute façon il est indispensable de le faire.

Cette approche ressemble à un "patch conceptuel".
Je retrouve dans cette attitude de "recommandation" d'utiliser certains mécanismes coûteux en ressources des choses que j'ai connues dans d'autres contextes et qui se traduisent par "pourquoi faire simple quand on peut faire compliqué?" Ou bien par des anathèmes tels que le bannissement des "tables" lors de la sortie de la première version du CSS.

Mais ne jetons pas le bébé avec l'eau du bain... Je poursuis mes réflexions et "j'vous dis quoi" comme on dit ché mi!
Bonjour PapyJP,

Mais non bien au contraire, ce qui justement est coûteux en ressources est d'ajouter un événement sur chaque input plutôt que d'en ajouter juste un seul sur un parent.

Imagine par exemple ce code :
<form action="">
  <input type="checkbox" name="c1" id="" />
  <input type="checkbox" name="c2" id="" />
  <input type="checkbox" name="c3" id="" />
  <input type="checkbox" name="c4" id="" />
  <input type="checkbox" name="c5" id="" />
  <input type="checkbox" name="c6" id="" />
  <input type="checkbox" name="c7" id="" />
  <input type="checkbox" name="c8" id="" />
  <input type="checkbox" name="c9" id="" />
  <input type="checkbox" name="c10" id="" />
  <input type="checkbox" name="c11" id="" />
  <input type="checkbox" name="c12" id="" />
  <input type="checkbox" name="c13" id="" />
  <input type="checkbox" name="c14" id="" />
  <input type="checkbox" name="c15" id="" />
  <input type="checkbox" name="c16" id="" />
  <input type="checkbox" name="c17" id="" />
  <input type="checkbox" name="c18" id="" />
  <input type="checkbox" name="c19" id="" />
  <input type="checkbox" name="c20" id="" />
</form>
<div></div>

var $form = $('form');
$form.on('change', 'input', function(){
  var $el = $(this);
  $('div').text( $el.attr('name')+' '+$el.prop('checked') )
})

Si tu devais mettre un onchange sur chaque input !
@PapyJP Il ne faut pas refuser le progrès, ce n'est pas parce que ça marche (ou que ça eut été LA solution) que c'est comme ça qu'il faut faire aujourd'hui.

C'est bien plus couteux en performance d'instancier 25 évènements que d'en instancier un seul sur le parent des éléments, c'est d'une logique implacable. Lors du changement de valeur, l'évènement va de toute manière se propager jusqu'a <html>.

La chose à ne pas faire, c'est abuser de ce système ou de l'utiliser dans toute circonstance. A chaque problème sa solution, il n'y a pas UNE manière de procéder, mais en fonction du problème rencontré il vaut mieux faire comme ci ou comme ça.

L'exemple de SolidSnake ou mon exemple (deuxième lien), illustrent des cas ou il est conseillé d'utiliser la délégation d'évènement.

Si vous prenez l'exemple du post ci-dessus et que vous voulez le faire en pure html cela donnerait quelque chose comme ça :
document.forms[0].addEventListener('change', function(e){
    var thisElement = e.target;
    var divElement = document.querySelector('div');
    divElement.textContent = thisElement.getAttribute('name') + ' ' + thisElement.checked;
});

C'est quand même plus agréable (et compatible Smiley cligne ) avec la version raccourcie jQuery donnée par SolidSnake.
Merci à tous le deux, mais je ne comprends pas grand chose à ce que vous dites. Ce doit être mon grand âge!
En gros: si je délègue le traitement d'un évènement à un "parent" plus ou moins lointain dans la hiérarchie, y a-t-il un moyen simple de savoir lequel des enfants a déclenché l'évènement?

Ce que vous devez savoir, c'est que je ne travaille pas (ou rarement) en HTML. Mes sites travaillent sur des objets traités par des programmes PHP. C'est mon programme qui génère les "onchange", et peu m'importe qu'il y en ait 5, 50 ou 500 dans la page. Je dirais même que c'est chaque objet ou sous-objet qui décide, mon programme n'en a rien à faire.

Afficher le formulaire de modification des coordonnées d'une personne se dit

    echo $Members[$userID] - > updateItem();

et s'il s'agit d'un formulaire de saisie, ce sera simplement

    echo new Member('') -> updateItem();

Mettre à jour les coordonnées de cette personne se dit

    $newMember = new Member($_POST);
    if($newMember -> isempty()) { /* envoyer un message d'erreur */} // c'est ça le plus emm...
    else $newMember -> update();

Le HTML est généré objet par objet, et sous objet par sous objet.
Dans ces circonstances, il est "naturel" que les événements soient inclus dans le HTML de chaque objet, puisque la balise d'input est une propriété de l'objet et la fonction à appeler une méthode de l'objet. Le problème à traiter, sur le fond, consiste à déléguer des méthodes au client sous la forme de fonctions JS. L'alternative est de n'avoir que les données sur le serveur et les objets sur le client. J'ai testé les deux méthodes et j'en suis resté à déléguer le moins possible au client (après avoir longtemps fait le contraire), ne serait-ce que pour des raisons de confidentialité: si je veux limiter l'affichage d'informations pour certaines catégories d'utilisateurs, je n'ai pas envie d'envoyer la totalité de l'objet sous forme de JSON à un client. Il n'est pas nécessaire d'être compétent en informatique pour afficher le contenu d'un fichier Javascript!

Je peux évidemment changer le code et remplacer les "onchange", par quelque chose du genre ajouter une classe dans la balise input, mais ensuite il faut "activer" ces évènements dans le code JS de la page alors que dans mon approche il n'y a pas d'interférence. De plus si je crée un objet, il ne suffit pas de le cloner, il faut dynamiquement activer les évènements, ce qui à mon sens n'est pas le rôle du client.

Bon, je réfléchis toujours.

Le concept de "refuser le progrès" me fait bien sourire, et ferait franchement rigoler les membres de mes anciennes équipes!
Ce n'est certainement pas parce que quelque chose marche bien qu'il ne faut pas le remplacer par quelque chose d’autre, mais ce n'est pas non plus parce que quelque chose est à la mode qu'il faut se précipiter dessus.

Je suppose que les personnes qui ont écrit les pages sur les évènements ont des raisons pour les avoir écrites, mais je sais aussi, pour avoir dirigé des équipes internationales de développement de logiciel, que les équipes américaines sont promptes à définir des "concepts" qui d'après elles sont sensés révolutionner l'industrie et qui sont remplacés quelques temps plus tard par d'autres concepts tout aussi géniaux ... ou fumeux.

Le problème avec les Anglo-Saxons, c'est qu'ils sont très mauvais à expliquer simplement les choses. Les Anglais se moquent des "démonstrations" à la française, qu'ils prennent pour des enfantillages, les Américains sont rarement capables de simplicité.

Tout le monde n'est pas Edsger Dijkstra (l'homme qui a entre autres choses formalisé la programmation structurée) ou Bjarne Stroustrup (celui qui a formalisé le C++ et sorti les langages objets de leur niche universitaire). A remarquer que le premier est Néerlandais et le second Danois, de même que Linus Torvalds est finlandais. Pas beaucoup de place pour les US dans ce palmarès (pour les Français non plus, au grand dam de mon ex collègue Jean Ichbiah et son "Ada", le langage qui vint trop tard...)
Modifié par PapyJP (14 Feb 2015 - 18:40)
@PapyJP J'y ai peut être été un peu fort, je me doute que vous ne refusez pas le progrès Smiley cligne et je ne vous demande pas non plus de changer votre code (qui a priori fonctionne). Juste que si vous abordez de nouvelles problématique il est toujours intéressant de regarder de nouvelles manières de faire.

PapyJP a écrit :
En gros: si je délègue le traitement d'un évènement à un "parent" plus ou moins lointain dans la hiérarchie, y a-t-il un moyen simple de savoir lequel des enfants a déclenché l'évènement?

Oui bien sur que l'on peut savoir quel est l'enfant qui a déclenché l'évènement :
$('#container').on('change', ':checkbox', function(){
   // ici c'est un évènement qui se déclenche quand une checkbox enfant #container change d'état
   // du coup this est l'élément du DOM correspondant à la checkbox changée
   var laCheckbox = this; 
});

PapyJP a écrit :
C'est mon programme qui génère les "onchange", et peu m'importe qu'il y en ait 5, 50 ou 500 dans la page.

C'est justement ça le problème, si votre programme génère 500 évènements onchange c'est qu'il y a des axes d'optimisation car le client est inutilement sollicité.
PapyJP a écrit :
Le HTML est généré objet par objet, et sous objet par sous objet.
Dans ces circonstances [...] De plus si je crée un objet, il ne suffit pas de le cloner, il faut dynamiquement activer les évènements, ce qui à mon sens n'est pas le rôle du client.

Je ne comprends pas bien ou vous voulez en venir, si vous souhaitez une application client riche il faut évidemment la sécuriser comme il se doit, les entités que vous récupérez ne doivent pas comporter plus d'informations que vous n'auriez souhaité à la base.
Pour ce qui est des évènements il y a peu de différence entre un onclick et un évènement délégué au parent. Si vous avez un exemple concret de ce qui vous pose problème je veux bien vous expliquer plus en détail.

PapyJP a écrit :
Le problème avec les Anglo-Saxons, c'est qu'ils sont très mauvais à expliquer simplement les choses.

C'est un poil généralisé, je trouve que justement pour les technologies front-end, les choses sont généralement bien expliquées et très simple dans l'approche, si vous lisez un peu la documentation jQuery ou le site learn.jquery.com vous y verrez des articles assez didactiques et simples.
Freez a écrit :
Oui bien sur que l'on peut savoir quel est l'enfant qui a déclenché l'évènement :
$('#container').on('change', ':checkbox', function(){
   // ici c'est un évènement qui se déclenche quand une checkbox enfant #container change d'état
   // du coup this est l'élément du DOM correspondant à la checkbox changée
   var laCheckbox = this; 
});

Merci, Freez, c'était le point qui me manquait. Il faut dire que ce point n'était pas l'objet principal de mes réflexions au moment où nous avons divergé vers les événements à partir d'un test idiot sur la fonction de clonage et le bouton qui lançait le test.
Je pense que je vais essayer de remplacer mes "onchange" par une classe du genre "toBeChecked". A chaque changement d'input dans le formulaire, j'irai analyser la classe de l'input et en déduire si elle doit ou non être verifiee, et avec quel algorithme.
Si je comprends bien, il faut lancer le mécanisme au démarrage. Je suppose qu'il n'est pas politiquement correct de mettre ça dans "onload" et qu'il y a un moyen plus-tant-mieux de le faire?
Freez a écrit :
C'est justement ça le problème, si votre programme génère 500 évènements onchange c'est qu'il y a des axes d'optimisation car le client est inutilement sollicité.

Dans certaines limites, ce n'est pas ça qui me préoccupe au premier chef. Le client n'est pas vraiment sensé faire grand chose d'autre pendant que l'utilisateur remplit le formulaire. Je dirais même que dans certains cas je préfèrerais pouvoir prendre la main pendant la saisie, ce qui se fait couramment dans les applis "non web" mais c'est trop de boulot pour pas grand chose
Freez a écrit :
C'est un poil généralisé, je trouve que justement pour les technologies front-end, les choses sont généralement bien expliquées et très simple dans l'approche, si vous lisez un peu la documentation jQuery ou le site learn.jquery.com vous y verrez des articles assez didactiques et simples.

C'est vrai que la généralisation est abusive, il y a aux US dans les équipes de logiciel des gens de toutes cultures et de tout niveau, mais dans l'ensemble ils ont plutôt tendance à faire des encyclopédies que des synthèses, il me faut donc mettre la main sur le bon manuel, ce qui n'a pas été le cas jusqu'à présent.
Je vais me plonger avec intérêt dans les infos indiquées.

Merci encore pour l'aide que vous m'apportez tous dans cette approche d'une techno nouvelle pour moi.
Euh!!! vous allez dire que je pinaille, mais où est-ce que je mets l'appel aux fonctions d'initialisation?
Pour que ça marche, j'ai mis <script>....</script> juste avant </body>, mais je suppose qu'il y a un meilleur moyen de faire ça?
La bonne pratique est de le mettre dans un fichier JS inclut dans votre <head>, et d'initialiser vos évènements dans le document.ready (c'est le principe du onload, ça veut dire que le DOM sera chargé et que les éléments sur lesquels vous allez placer vos évènements existeront).
$(function(){
    // Cette fonction sera exécutée lorsque le document sera prêt.
    // il s'agit d'une syntaxe raccourcie de $(document).ready(function(){ ... });
    // C'est donc ici que vous devez initialiser vos évènements
});
Freez a écrit :
La bonne pratique est de le mettre dans un fichier JS inclut dans votre &lt;head&gt;, et d'initialiser vos évènements dans le document.ready (c'est le principe du onload, ça veut dire que le DOM sera chargé et que les éléments sur lesquels vous allez placer vos évènements existeront).
$(function(){
    // Cette fonction sera exécutée lorsque le document sera prêt.
    // il s'agit d'une syntaxe raccourcie de $(document).ready(function(){ ... });
    // C'est donc ici que vous devez initialiser vos évènements
});

Merci Freez
Je préfère effectivement regrouper au maximum dans <head>.

Je vois que les "bonnes pratiques" à la mode consistent à atomiser les informations en autant de petits fichiers, alors que les miennes consistent au contraire à les organiser par objet, mais c'est un détail.
Bon! j'ai fait les modifs dans mon gestionnaire de mise à jour.

Dire que je suis convaincu serait beaucoup.
Les fonctions anonymes, ça va bien quand elles sont courtes, mais j'ai renoncé à les imbriquer, ça devient difficile à lire, et donc à tester et à maintenir.

J'ai donc gardé

            $('#recordButton').on('click', 
                                              function() {
                                                     if(checkAll(this.form)) this.form.submit();
                                          });

//avec plus loin:

        function checkAll(formItem) {
                var result = true;
                $(':input', formItem).each(function(index) {
                    if(this.type == 'hidden') return;
                    result = result && checkField(this);
                })
                return result;
            }

bien que "checkAll" ne soit appelé qu'une fois.

Pour en revenir à Dijkstra et Stroustrup, je n'ai pas eu autant de problèmes à mettre leurs écrits en application. Mais il est vrai que j'étais plus jeune!
Bonsoir à tous

Je pense commencer à comprendre comment utiliser cette couche supplémentaire de logiciel due constitue jQuery.
Merci pour m'avoir aidé au démarrage.
Il est vraisemblable que je reviendrai vers vous pour d'autre conseils dans ce domaine.
Bonjour.

Je rajouterai que cette notion de délégation est très utile (et même quasi obligatoire si on ne veut pas s'arracher les cheveux) quand on ajoute un élément dans le DOM via JS, et que l'on veut qu'il hérite directement de l'événement qui serait déjà prévu à un élément frère par exemple (c'est un problème maintenant récurrent avec l'Ajax).

Si je reprends mon exemple avec les checkbox et que je fais :
$('form').append('<input type="checkbox" name="c21" id="" />')

L'événement change lui sera directement délégué.

Alors que si j'avais fait avant un simple :
$(':checkbox').change(function() {})

L'événement n'aurait pas été pris en compte sur le nouveau.
Modifié par SolidSnake (17 Feb 2015 - 08:57)
SolidSnake a écrit :
Bonjour.

Je rajouterai que cette notion de délégation est très utile (et même quasi obligatoire si on ne veut pas s'arracher les cheveux) quand on ajoute un élément dans le DOM via JS, et que l'on veut qu'il hérite directement de l'événement qui serait déjà prévu à un élément frère par exemple (c'est un problème maintenant récurrent avec l'Ajax).

Si je reprends mon exemple avec les checkbox et que je fais :
$('form').append('<input type="checkbox" name="c21" id=""/>)

L'événement change lui sera directement délégué.

Alors que si j'avais fait avant un simple :
$(':checkbox').change(function() {})

L'événement n'aurait pas été pris en compte sur le nouveau.

Oui, c'est très clair... une fois qu'on a compris!

Comme je l'ai dit au début de cette série de discussions, je manque toujours d'un document "jQuery pour les nuls" dans lequel on aurait en premier les concepts expliqués en quelques pages, avant d'entrer dans les détails de la syntaxe. Et une fois qu'on a compris, ça devient tellement évident qu'on n'en a plus besoin.
Le document que j'ai trouvé, qui plus est sous la forme de livre électronique, a beau être en Français, sa structure est plutôt américaine.
Maintenant je pense avoir saisi l'essentiel. Je vais progressivement migrer mes pages "dynamiques" vers jQuery, ça me permettra de saisir les choses par leur côté pratique. Mais y'a du taf, entre ce qu'on peut faire par CSS3, les transitions et jQuery...