Joindre un événement à des éléments dynamiques en javascript

90

J'essaie d'insérer des données html dynamiquement dans une liste créée dynamiquement, mais lorsque j'essaie de joindre un événement onclick pour le bouton qui est créé dynamiquement, l'événement ne se déclenche pas. La solution serait vraiment appréciée.

Code Javascript:

document.addEventListener('DOMContentLoaded', function () {
document.getElementById('btnSubmit').addEventListener('click', function () {
    var name = document.getElementById('txtName').value;
    var mobile = document.getElementById('txtMobile').value;
    var html = '<ul>';
    for (i = 0; i < 5; i++) {
        html = html + '<li>' + name + i + '</li>';
    }
    html = html + '</ul>';

    html = html + '<input type="button" value="prepend" id="btnPrepend" />';
    document.getElementsByTagName('form')[0].insertAdjacentHTML('afterend', html);
});

document.getElementById('btnPrepend').addEventListener('click', function () {
    var html = '<li>Prepending data</li>';
    document.getElementsByTagName('ul')[0].insertAdjacentHTML('afterbegin', html);
});

});

Code HTML:

<form>
    <div class="control">
        <label>Name</label>
        <input id="txtName" name="txtName" type="text" />
    </div>
    <div class="control">
        <label>Mobile</label>
        <input id="txtMobile" type="text" />
    </div>
    <div class="control">
        <input id="btnSubmit" type="button" value="submit" />
    </div>
</form>
Manju
la source
Comment créez-vous le html?
jim0thy
1
Je dirais que c'est parce que l'élément n'existe pas lorsque vous essayez de joindre l'écouteur d'événements. - jetez un œil à ce learn.jquery.com/events/event-delegation
Craicerjack
1
Déplacez votre addEventListener dans l'écouteur d'événements de btnSubmit
slebetman

Réponses:

199

Cela est dû au fait que votre élément est créé dynamiquement. Vous devez utiliser la délégation d'événements pour gérer l'événement.

 document.addEventListener('click',function(e){
    if(e.target && e.target.id== 'brnPrepend'){
          //do something
     }
 });

jquery facilite les choses:

 $(document).on('click','#btnPrepend',function(){//do something})

Voici un article sur l'article sur la délégation d' événements sur la délégation d' événements

jilykate
la source
1
Merci! Parfois, la solution JS pure ne fonctionne pas sur iphone, mais si le document change sur un autre div parent, par exemple, cela fonctionne. Quelle est la meilleure solution pour la délégation?
Kholiavko
3
@Kholiavko Je dirais que tant que l'élément "parent" n'est pas créé dynamiquement, cela devrait fonctionner. Je
lierais
1
Mais parfois cela ne fonctionne pas sur iphone, même l'élément "parent" n'est pas créé dynamiquement. Voici un exemple , cela fonctionne sur tous les navigateurs, mais pas sur iphone (safary et même chrome). Et je ne comprends pas pourquoi.
Kholiavko
2
@Kholiavko c'est juste parce que codepen utilise un bac à sable pour tout envelopper. Si vous écrivez vraiment ce code dans une page normale, cela fonctionnera
jilykate
2
Pensez à ajouter un signe égal supplémentaire (===) pour éviter une contrainte de type inattendue
Staccato
17

Il existe une solution de contournement en capturant les clics sur document.bodypuis en vérifiant la cible de l'événement.

document.body.addEventListener( 'click', function ( event ) {
  if( event.srcElement.id == 'btnSubmit' ) {
    someFunc();
  };
} );
Manasov Daniel
la source
2
Ou conteneur le plus proche
mplungjan
Réponse parfaite!
Rahul Purohit
WOW c'est incroyable, économise tellement de temps et de code! :)
Anna Medyukh le
Remarque: event.srcElement est obsolète. Il est recommandé d'utiliser event.target à la place.
dsanchez
12

Vous devez attacher l'événement après avoir inséré des éléments, de sorte que vous n'attachez pas d'événement global à votre document mais un événement spécifique sur les éléments insérés.

par exemple

document.getElementById('form').addEventListener('submit', function(e) {
  e.preventDefault();
  var name = document.getElementById('txtName').value;
  var idElement = 'btnPrepend';
  var html = `
    <ul>
      <li>${name}</li>
    </ul>
    <input type="button" value="prepend" id="${idElement}" />
  `;
  /* Insert the html into your DOM */
  insertHTML('form', html);
  /* Add an event listener after insert html */
  addEvent(idElement);
});

const insertHTML = (tag = 'form', html, position = 'afterend', index = 0) => {
  document.getElementsByTagName(tag)[index].insertAdjacentHTML(position, html);
}
const addEvent = (id, event = 'click') => {
  document.getElementById(id).addEventListener(event, function() {
    insertHTML('ul', '<li>Prepending data</li>', 'afterbegin')
  });
}
<form id="form">
  <div>
    <label for="txtName">Name</label>
    <input id="txtName" name="txtName" type="text" />
  </div>
  <input type="submit" value="submit" />
</form>

R3tep
la source
2
Cette approche ne fonctionnera que pour le CSR (Client Side Rendering). Cela ne fonctionnera pas pour SSR (Server-Side Rendering). Pour que cela fonctionne à la fois pour la RSE et la SSR, la délégation d'événements est la solution:javascript document.addEventListener('click',function(e){ if(e.target && e.target.id== 'brnPrepend'){ //do something } });
Yatendra Goel
Mais pour des raisons de performances, cette approche est meilleure. Cela dépend donc de votre cas d'utilisation.
R3tep
8

La différence réside dans la façon dont vous créez et ajoutez des éléments dans le DOM.

Si vous créez un élément via document.createElement, ajoutez un écouteur d'événement et ajoutez-le au DOM. Vos événements se déclencheront.

Si vous créez un élément sous forme de chaîne comme celle-ci: html + = "<li> test </li>" `, l'élément n'est techniquement qu'une chaîne. Les chaînes ne peuvent pas avoir d'écouteurs d'événements.

Une solution consiste à créer chaque élément avec document.createElement, puis à les ajouter directement à un élément DOM.

// Sample
let li = document.createElement('li')
document.querySelector('ul').appendChild(li)
Richard
la source
3

Vous pouvez faire quelque chose de similaire à ceci:

// Get the parent to attatch the element into
var parent = document.getElementsByTagName("ul")[0];

// Create element with random id
var element = document.createElement("li");
element.id = "li-"+Math.floor(Math.random()*9999);

// Add event listener
element.addEventListener("click", EVENT_FN);

// Add to parent
parent.appendChild(element);
PRDeving
la source
2
var __ = function(){
    this.context  = [];
    var self = this;
    this.selector = function( _elem, _sel ){
        return _elem.querySelectorAll( _sel );
    }
          this.on = function( _event, _element, _function ){
              this.context = self.selector( document, _element );
              document.addEventListener( _event, function(e){
                  var elem = e.target;
                  while ( elem != null ) {
                      if( "#"+elem.id == _element || self.isClass( elem, _element ) || self.elemEqal( elem ) ){
                          _function( e, elem );
                      }
                      elem = elem.parentElement;
                  }
              }, false );
     };

     this.isClass = function( _elem, _class ){
        var names = _elem.className.trim().split(" ");
        for( this.it = 0; this.it < names.length; this.it++ ){
            names[this.it] = "."+names[this.it];
        }
        return names.indexOf( _class ) != -1 ? true : false;
    };

    this.elemEqal = function( _elem ){
        var flg = false;
        for( this.it = 0; this.it < this.context.length;  this.it++ ){
            if( this.context[this.it] === _elem && !flg ){
                flg = true;
            }
        }
        return flg;
    };

}

    function _( _sel_string ){
        var new_selc = new __( _sel_string );
        return new_selc;
    }

Maintenant, vous pouvez enregistrer un événement comme,

_( document ).on( "click", "#brnPrepend", function( _event, _element ){
      console.log( _event );
      console.log( _element );
      // Todo

  });

Prise en charge du navigateur

chrome - 4.0, Edge - 9.0, Firefox - 3.5 Safari - 3.2, Opera - 10.0 et supérieur

paranjothi
la source
2

J'ai créé une petite bibliothèque pour vous aider: Source de la bibliothèque sur GitHub

<script src="dynamicListener.min.js"></script>
<script>
// Any `li` or element with class `.myClass` will trigger the callback, 
// even elements created dynamically after the event listener was created.
addDynamicEventListener(document.body, 'click', '.myClass, li', function (e) {
    console.log('Clicked', e.target.innerText);
});
</script>

La fonctionnalité est similaire à jQuery.on ().

La bibliothèque utilise la méthode Element.matches () pour tester l'élément cible par rapport au sélecteur donné. Lorsqu'un événement est déclenché, le rappel n'est appelé que si l'élément cible correspond au sélecteur donné.

XCS
la source
0

Je sais que le sujet est trop ancien mais je me suis donné quelques minutes pour créer un code très utile qui fonctionne très bien et très facile à utiliser pure JAVASCRIPT. Voici le code avec un exemple simple:

String.prototype.addEventListener=function(eventHandler, functionToDo){
  let selector=this;
  document.body.addEventListener(eventHandler, function(e){
    e=(e||window.event);
    e.preventDefault();
    const path=e.path;
    path.forEach(function(elem){
      const selectorsArray=document.querySelectorAll(selector);
      selectorsArray.forEach(function(slt){
        if(slt==elem){
          if(typeof functionToDo=="function") functionToDo(el=slt, e=e);
        }
      });
    });
  });
}

// And here is how we can use it actually !

"input[type='number']".addEventListener("click", function(element, e){
	console.log( e ); // Console log the value of the current number input
});
<input type="number" value="25">
<br>
<input type="number" value="15">
<br><br>
<button onclick="addDynamicInput()">Add a Dynamic Input</button>
<script type="text/javascript">
  function addDynamicInput(){
    const inpt=document.createElement("input");
          inpt.type="number";
          inpt.value=Math.floor(Math.random()*30+1);
    document.body.prepend(inpt);
  }
</script>

Adnane Ar
la source
0

J'ai créé une fonction simple pour cela.

La _casefonction vous permet non seulement d'obtenir la cible, mais également d'obtenir l'élément parent sur lequel vous liez l'événement.

La fonction de rappel retourne l'événement qui contient la cible ( evt.target) et l'élément parent correspondant au sélecteur ( this). Ici, vous pouvez faire ce dont vous avez besoin après avoir cliqué sur l'élément.

Je n'ai pas encore décidé lequel est le meilleur, le if-elseou leswitch

var _case = function(evt, selector, cb) {
  var _this = evt.target.closest(selector);
  if (_this && _this.nodeType) {
    cb.call(_this, evt);
    return true;
  } else { return false; }
}

document.getElementById('ifelse').addEventListener('click', function(evt) {
  if (_case(evt, '.parent1', function(evt) {
      console.log('1: ', this, evt.target);
    })) return false;

  if (_case(evt, '.parent2', function(evt) {
      console.log('2: ', this, evt.target);
    })) return false;

  console.log('ifelse: ', this);
})


document.getElementById('switch').addEventListener('click', function(evt) {
  switch (true) {
    case _case(evt, '.parent3', function(evt) {
      console.log('3: ', this, evt.target);
    }): break;
    case _case(evt, '.parent4', function(evt) {
      console.log('4: ', this, evt.target);
    }): break;
    default:
      console.log('switch: ', this);
      break;
  }
})
#ifelse {
  background: red;
  height: 100px;
}
#switch {
  background: yellow;
  height: 100px;
}
<div id="ifelse">
  <div class="parent1">
    <div class="child1">Click me 1!</div>
  </div>
  <div class="parent2">
    <div class="child2">Click me 2!</div>
  </div>
</div>

<div id="switch">
  <div class="parent3">
    <div class="child3">Click me 3!</div>
  </div>
  <div class="parent4">
    <div class="child4">Click me 4!</div>
  </div>
</div>

J'espère que cela aide!

ssten
la source
0

J'ai trouvé la solution publiée par jillykate fonctionne, mais seulement si l'élément cible est le plus imbriqué. Si ce n'est pas le cas, cela peut être corrigé en itérant sur les parents, c'est-à-dire

function on_window_click(event)
{
    let e = event.target;

    while (e !== null)
    {
        // --- Handle clicks here, e.g. ---
        if (e.getAttribute(`data-say_hello`))
        {
            console.log("Hello, world!");
        }

        e = e.parentElement;
    }
}

window.addEventListener("click", on_window_click);

Notez également que nous pouvons gérer les événements par n'importe quel attribut, ou attacher notre auditeur à n'importe quel niveau. Le code ci-dessus utilise un attribut personnalisé et window. Je doute qu'il y ait une différence pragmatique entre les différentes méthodes.

cz
la source