Adjuntar evento a elementos dinámicos en javascript

90

Estoy tratando de insertar datos html dinámicamente en una lista que se crea dinámicamente, pero cuando intento adjuntar un evento onclick para el botón que se crea dinámicamente, el evento no se activa. La solución sería muy apreciada.

Código 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);
});

});

Código 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
fuente
¿Cómo estás creando el html?
jim0thy
1
Yo diría que es porque el elemento no existe cuando intentas adjuntar el detector de eventos. - Eche un vistazo a this learn.jquery.com/events/event-delegation
Craicerjack
1
Mueva su addEventListener al detector de eventos de btnSubmit
slebetman

Respuestas:

199

Esto se debe al hecho de que su elemento se crea dinámicamente. Debe usar la delegación de eventos para manejar el evento.

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

jquery lo hace más fácil:

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

Aquí hay un artículo sobre delegación de eventos artículo de delegación de eventos

jilykate
fuente
1
¡Gracias! A veces, la solución JS pura no funciona en iphone, pero si cambia el documento en otro div principal, por ejemplo, funciona. ¿Cuál es la mejor solución para la delegación?
Kholiavko
3
@Kholiavko Yo diría que siempre que el elemento "padre" no se cree dinámicamente, debería funcionar.
Vincularía
1
Pero a veces no funciona en el iphone, incluso el elemento "padre" no se crea de forma dinámica. Aquí está un ejemplo , funciona en todos los navegadores, pero no en el iPhone (Safary e incluso Chrome). Y no entiendo por qué.
Kholiavko
2
@Kholiavko esto se debe a que Codepen usa una caja de arena para envolver todo. Si realmente escribe ese código en una página normal, funcionará
jilykate
2
Considere agregar un signo igual adicional (===) para evitar una coerción de tipo inesperada
Staccato
17

Existe una solución al capturar los clics document.bodyy luego verificar el destino del evento.

document.body.addEventListener( 'click', function ( event ) {
  if( event.srcElement.id == 'btnSubmit' ) {
    someFunc();
  };
} );
Manasov Daniel
fuente
2
O contenedor más cercano
mplungjan
¡Respuesta perfecta!
Rahul Purohit
¡GUAU, esto es increíble, ahorra mucho tiempo y código! :)
Anna Medyukh
Nota: event.srcElement está en desuso. Se recomienda utilizar event.target en su lugar.
dsanchez
12

Debe adjuntar el evento después de insertar elementos, así no adjunta un evento global en su document sino un evento específico en los elementos insertados.

p.ej

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
fuente
2
Este enfoque solo funcionará para CSR (representación del lado del cliente). Esto no funcionará para SSR (representación del lado del servidor). Para que funcione tanto para CSR como para SSR, Event Delegation es la solución:javascript document.addEventListener('click',function(e){ if(e.target && e.target.id== 'brnPrepend'){ //do something } });
Yatendra Goel
Pero por motivos de rendimiento, este enfoque es mejor. Entonces depende de su caso de uso.
R3tep
8

La diferencia está en cómo crea y agrega elementos en el DOM.

Si crea un elemento a través de document.createElement, agregue un detector de eventos y añádalo al DOM. Tus eventos se dispararán.

Si crea un elemento como una cadena como esta: html + = "<li> test </li>" `, el elemento es técnicamente solo una cadena. Las cadenas no pueden tener detectores de eventos.

Una solución es crear cada elemento con document.createElementy luego agregarlos a un elemento DOM directamente.

// Sample
let li = document.createElement('li')
document.querySelector('ul').appendChild(li)
Ricardo
fuente
3

Puedes hacer algo similar a esto:

// 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
fuente
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;
    }

Ahora puedes registrar eventos como,

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

  });

Soporte del navegador

chrome - 4.0, Edge - 9.0, Firefox - 3.5 Safari - 3.2, Opera - 10.0 y superior

paranjothi
fuente
2

He creado una pequeña biblioteca para ayudar con esto: Fuente de la biblioteca en 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 funcionalidad es similar a jQuery.on ().

La biblioteca usa el método Element.matches () para probar el elemento de destino con el selector dado. Cuando se activa un evento, la devolución de llamada solo se llama si el elemento de destino coincide con el selector dado.

XCS
fuente
0

Sé que el tema es demasiado antiguo, pero me di unos minutos para crear un código muy útil que funciona bien y es muy fácil de usar puro JAVASCRIPT. Aquí está el código con un ejemplo 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
fuente
0

Hice una función simple para esto.

La _casefunción le permite no solo obtener el objetivo, sino también obtener el elemento padre donde vincula el evento.

La función de devolución de llamada devuelve el evento que contiene el objetivo ( evt.target) y el elemento principal que coincide con el selector ( this). Aquí puede hacer las cosas que necesita después de hacer clic en el elemento.

Aún no he decidido cuál es mejor, el if-elseo elswitch

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>

¡Espero eso ayude!

ssten
fuente
0

He encontrado que la solución publicada por jillykate funciona, pero solo si el elemento de destino es el más anidado. Si este no es el caso, esto se puede rectificar iterando sobre los padres, es decir

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);

También tenga en cuenta que podemos manejar eventos por cualquier atributo, o adjuntar nuestro oyente en cualquier nivel. El código anterior usa un atributo personalizado y window. Dudo que haya alguna diferencia pragmática entre los distintos métodos.

cz
fuente