29 Agosto 2018 clcanela
2335 0

Cómo asignar eventos a elementos creados dinámicamente con jQuery


2335 0

En versiones anteriores, lograr que todos los botones, links y cualquier elemento recientemente agregado al DOM respondiera a un evento era relativamente fácil gracias a la función .live() que como explica en su documentación: agrega un manejador de eventos a todos los elementos que coincidan con el selector, en este momento y en el futuro, lamentablemente la función live ha sido marcada como obsoleta desde jQuery 1.7 y removida en 1.9, afortunadamente a partir de jQuery 1.7, se introdujo algo llamado Manejadores de eventos delegados (Delegated event handlers) lo cual, sin entrar en mucho detalle, nos permite asignar un evento a un elemento padre y buscar el elemento dentro de sus descendientes a los que ha sido delegado el evento.

Tomemos el siguiente ejemplo: un grupo de un campo con un botón el cual realiza una suma del valor del campo sobre sí mismo y devuelve el resultado sobre el valor del mismo campo:

<div class="input-group mb-3">
        <input type="number" class="form-control" placeholder="Ingresa un valor para sumarlo a sí mismo">
        <div class="input-group-append">
          <button class="btn btn-outline-secondary sumar_btn" type="button" id="button-addon2">Sumar</button>
        </div>
      </div>
  $(".input-group .sumar_btn").on('click',function(){
  	var num=$( this).parents('.input-group').find('input[type="number"]').val();
    num=parseInt(num)+parseInt(num);
    $( this).parents('.input-group').find('input[type="number"]').val(num)
  })

Hasta este punto, nada fuera de lo ordinario, sólo explicaré rápidamente estas tres líneas de código:

Línea 1, asignamos el evento de click al botón con clase .sumar_btn
Línea 2, obtenemos el valor del input de tipo number que se encuentra dentro del padre input-group al que pertenece el botón al que se está haciendo clic (identificado por this)
Línea 3, convertimos el valor a entero (normalmente se obtiene como cadena, por lo que es necesario parseInt) y lo sumamos a sí mismo
Línea 4, actualizamos el valor del campo con el resultado de la suma actualizada en la variable num

Tal vez hace un poco de ruido usar el selector parents seguido de un find pero solamente lo hice para subir desde el botón al nivel de su contenedor padre y en ese nivel buscar el campo del mismo contenedor.

Ahora supongamos que requerimos agregar varios campos de este estilo a nuestro documento, para eso vamos a agregar un botón que se va a encargar de duplicar nuestro input-group:

<div class="container">
  <div class="row">
    <div class="col">
    <button type="button" id="add_row_btn" class="btn btn-primary btn-lg btn-block mt-3 mb-3">Agregar nueva Fila</button>
      <div class="input-group mb-3">
        <input type="number" class="form-control" placeholder="Ingresa un valor para sumarlo a sí mismo">
        <div class="input-group-append">
          <button class="btn btn-outline-secondary sumar_btn" type="button" id="button-addon2">Sumar</button>
        </div>
      </div>
    </div>
  </div>
</div>
$(function(){
//boton clonar elemento
	$("#add_row_btn").on('click',function(){
		$(".container .row .col").append($(".input-group:first").clone())
	})
	//funcion de suma en boton
  $(".container .row .col").on('click','.input-group .sumar_btn',function(){
  	var num=$( this).parents('.input-group').find('input[type="number"]').val();
    num=parseInt(num)+parseInt(num);
    $( this).parents('.input-group').find('input[type="number"]').val(num)
  })
})

Al HTML sólo le agregué el botón con ID add_row_btn, al cual le asigné el evento al clic de clonar el primer input-group y pegarlo en mi columna del contenedor, si no realizo ningún otro cambio al javascript, nuestro nuevo input-group no haría nada pues el evento se le asignó al botón sumar_btn cuando se inició el documento y cualquier otro elemento que se cree (se agregue al DOM) posterior a la asignación de los eventos, no tendrá ningún listener asociado. Para corregir esto y lograr que todos los nuevos renglones que agreguemos también respondan al evento del clic, debemos recurrir al delegado para el evento. Si se fijan, el javascript original del botón sumar_btn realmente no sufrió muchas modificaciones, lo primero es que cambié el selector, de ir directo al botón, a su contenedor $(".container .row .col") y a su vez, el selector 'input-group .sumar_btn' lo moví como segundo parámetro del método on, indicándole así al avento que existe un delegado del evento, en este caso el botón.

En realidad lo que estamos haciendo es agregarle el event listener al contenedor en lugar de al botón, y al decirle a la función on que existe un delegado, lo que sucede es que cualquier evento (en este caso el clic) que se dispare dentro del contenedor padre, incluídos sus descendientes, va a revisar el selector del delegado, por lo que es posible dar clic en cualquier zona o en cualquier hijo del contenedor, pero si el elemento que está recibiendo el evento no cumple con el selector designado como un delegado, el evento no lanzará la acción definida. Aprovechando ésta lógica, al crear varios botones dentro del contenedor, cada vez que se dispare el evento suscrito (ej.: click) se revisa si el elemento que recibe directamente la acción cumple con el selector definido, y al ser todos los botones clones del original y por ende compartir la misma clase/selector .input-group .sumar_btn todos cumpliran con la condición de delegado y darán paso a detonar la accion relacionada al evento. Aquí les dejo el ejemplo final corriendo: https://jsfiddle.net/clcanela/b36dt10q/

Espero haber aclarado tus dudas, como siempre estoy al pendiente de la sección de comentarios por cualquier comentario, reclamo, duda, sugerencia o felicitación ¡Felices Sentencias!

Quizá te interesen
¿Qué es JSON, para qué sirve y dónde se usa?
Cómo obtener los parámetros de la URL vía JavaScript o jQuery
Tutorial: Cómo leer un JSON con jQuery
Recibirás notificaciones de las respuestas a tu comentario y obtendrémos tu avatar de Gravatar.com Tu correo no será publicado