viernes, 11 de julio de 2014

JQuery: Añadir eventos a elementos creados de forma dinámica

Me comentaban en el artículo JQuery: Evitar lanzamientos múltiples de eventos el siguiente escenario, por otra parte bastante común: Tenemos un contenedor donde de forma dinámica vamos a ir añadiendo botones y queremos que al pulsar uno de estos botones se realice una acción concreta.

Si te interesa saber más sobre eventos en JQuery puedes leer el desarrollo completo, con fallos y aciertos que explico a continuación, pero si quieres ir directo al grano y ver como se soluciona de forma elegante puedes ir al final del artículo directamente.

En principio parece sencillo.

<a href="#" id="nuevoBoton">Añadir botón</a>
<div id="contenedor">
<a class="boton" data-val="1" href="#">Pulsa</a>
</div>


<script>
$("a.boton").on('click', function() {
 alert('El valor es: ' + $(this).data('val')); 
});

$("#nuevoBoton").on('click',function() {
 
 var boton = '<a href="#" class="boton" data-val="' + parseInt(Math.random()*10) + '">Pulsa</a>';
 
 $('#contenedor').append(boton); 
});
</script>

¿Qué ocurre?


Si hacemos click en el primer botón todo funciona correctamente. Pero cuando añadimos botones nuevos, en estos no se ejecuta el evento. Esto se debe a que hemos vinculado al selector a.boton al cargar la página, por lo tanto los botones que añadimos dinámicamente no tienen este evento vinculado.

De acuerdo, esto tiene fácil solución, cada vez que añada un botón tengo que vincular el evento click:

<script>
$("a.boton").on('click', function() {
 alert('El valor es: ' + $(this).data('val')); 
});

$("#nuevoBoton").on('click',function() {
 
 var boton = '<a href="#" class="boton" data-val="' + parseInt(Math.random()*10) + '">Pulsa</a>';
 
 $('#contenedor').append(boton); 
    
    $("a.boton").on('click', function() {
  alert('El valor es: ' + $(this).data('val')); 
 });
});
</script>

Ahora los botones que añadimos ejecutan muestran el 'alert', pero cada vez que añadimos un botón, estamos vinculando el evento a todos los elementos del DOM que coinciden con el selector, por lo que en el caso del primer botón, el evento se lanzará tantas veces como nuevos botones hayamos añadido.

¿Cómo lo solucionamos?


Sencillo, desvinculamos todos los eventos antes de añadir los nuevos usando el método .off de Jquery:

<script>
$("a.boton").on('click', function() {
 alert('El valor es: ' + $(this).data('val')); 
})

$("#nuevoBoton").on('click',function() {
 
 var boton = '<a href="#" class="boton" data-val="' + parseInt(Math.random()*10) + '">Pulsa</a>';
 
 $('#contenedor').append(boton); 
    
    $("a.boton").off('click');
    $("a.boton").on('click', function() {
     alert('El valor es: ' + $(this).data('val')); 
    });
});
</script>

Y ahora sí conseguimos el resultado deseado. Solo salta una vez el mensaje por cada vez que pulsamos el botón. No obstante hacerlo de esta forma sobrecarga el navegador, pues estamos vinculando y desvinculando todos los eventos para cada nuevo botón que añadimos, que pueden llegar a ser muchos. Pero vamos a ver que se puede hacer de una forma más elegante y eficiente:

Añadir eventos a elementos creados de forma dinámica de forma elegante


En este caso vamos a hacer uso de lo que en Jquery se llaman delegados. Esto nos permite vincular eventos a un selector de manera que cada vez que se añadan nuevos elementos, se vincule automáticamente el evento:

<script>
$("#nuevoBoton").on('click',function() { 
 var boton = '<a href="#" class="boton" data-val="' + parseInt(Math.random()*10) + '">Pulsa</a>';
 
 $('#contenedor').append(boton);    
});

$("#contenedor").on('click','a.boton', function() {
 alert('El valor es: ' + $(this).data('val')); 
})
</script>

El evento se vincula al contenedor, pero se delega en los botones. De esta forma cuando añadamos nuevos elementos automáticamente se vincularán sus eventos correspondientes.

Para terminar os añado un jsfiddle para que podáis probar el resultado final:

http://jsfiddle.net/cvdta/

6 comentarios:

  1. Muy buen aporte, me sacaste de un aprieto, GRACIAS :D

    ResponderEliminar
  2. Gracias! Genial! Muy Bien comentado!

    ResponderEliminar
  3. Buenas tardes una pregunta,suponiendo que el boton que se crea dinamicamente al clickearlo ejecuta una peticion ajax, tengo que declarar dentro del clousure nuevamente la peticion.
    si me hago entender?

    ResponderEliminar
    Respuestas
    1. Hola Ricardo, entiendo que creas el botón con una petición ajax inicial, y luego lo bindead a una acción que hace otra petición ajax. Lo apropiado es que utilices un método público dentro del closure al que bindearas en el click del botón. Recuerda que para hacer un método público en el closure tienes que ponerlo dentro del return. Si aún tienes dudas envíame un jdfiddle con el código y así seguro que es mas fácil de ver, un saludo:)

      Eliminar
  4. Que tal, tengo un formulario con select dependientes
    Zona, Municipio, Localidad
    Puedo crear los selects, pero de que manera podria asignarle las funciones o eventos
    para llenarlos

    ResponderEliminar