¿Enlace de evento en elementos creados dinámicamente?

1677

Tengo un poco de código en el que estoy recorriendo todos los cuadros de selección en una página y enlazando un .hover evento para que hagan un poco de giro con su ancho activado mouse on/off.

Esto sucede en la página lista y funciona bien.

El problema que tengo es que cualquier cuadro de selección que agregue a través de Ajax o DOM después del bucle inicial no tendrá el evento vinculado.

He encontrado este complemento ( jQuery Live Query Plugin ), pero antes de agregar otros 5k a mis páginas con un complemento, quiero ver si alguien sabe una forma de hacerlo, ya sea con jQuery directamente o por otra opción.

Eli
fuente

Respuestas:

2250

A partir de jQuery 1.7 debe usar jQuery.fn.on:

$(staticAncestors).on(eventName, dynamicChild, function() {});

Antes de esto , el enfoque recomendado era usar live():

$(selector).live( eventName, function(){} );

Sin embargo, live()fue desaprobado en 1.7 a favor on()y eliminado por completo en 1.9. La live()firma:

$(selector).live( eventName, function(){} );

... se puede reemplazar con la siguiente on()firma:

$(document).on( eventName, selector, function(){} );

Por ejemplo, si su página estaba creando dinámicamente elementos con el nombre de la clase dosomething, vincularía el evento a un padre que ya existe (este es el meollo del problema aquí, necesita algo que existe para vincularlo, no se enlace al contenido dinámico), esto puede ser (y la opción más fácil) es document. Aunque tenga en cuenta que documentpuede no ser la opción más eficiente .

$(document).on('mouseover mouseout', '.dosomething', function(){
    // what you want to happen when mouseover and mouseout 
    // occurs on elements that match '.dosomething'
});

Cualquier padre que exista en el momento en que se vincula el evento está bien. Por ejemplo

$('.buttons').on('click', 'button', function(){
    // do something here
});

se aplicaría a

<div class="buttons">
    <!-- <button>s that are generated dynamically and added here -->
</div>
Popnoodles
fuente
77
Tenga en cuenta que el método en vivo solo funciona para ciertos eventos y no para otros, como los metadatos cargados. (Consulte la sección de advertencias en la documentación.)
Sam Dutton
48
Obtenga más información sobre la delegación de eventos aquí: learn.jquery.com/events/event-delegation .
Felix Kling
99
¿Alguna forma de lograr esto con javascript / vanilla js puro?
Ram Patra
15
@Ramswaroop todo lo que puede hacer en jQuery se puede lograr sin jQuery. Aquí hay un buen ejemplo de delegación de evento sin jQuery
Dave
55
@dave Me pregunto por qué la respuesta que señaló no figura aquí. Eli claramente ha pedido una solución sin ningún complemento si es posible.
Ram Patra
377

Hay una buena explicación en la documentación de jQuery.fn.on.

En breve:

Los controladores de eventos están vinculados solo a los elementos seleccionados actualmente; deben existir en la página en el momento en que su código realiza la llamada .on().

Por lo tanto, en el siguiente ejemplo #dataTable tbody trdebe existir antes de que se genere el código.

$("#dataTable tbody tr").on("click", function(event){
    console.log($(this).text());
});

Si se está inyectando HTML nuevo en la página, es preferible usar eventos delegados para adjuntar un controlador de eventos, como se describe a continuación.

Los eventos delegados tienen la ventaja de que pueden procesar eventos de elementos descendientes que se agregan al documento más adelante. Por ejemplo, si la tabla existe, pero las filas se agregan dinámicamente usando código, lo manejará lo siguiente:

$("#dataTable tbody").on("click", "tr", function(event){
    console.log($(this).text());
});

Además de su capacidad para manejar eventos en elementos descendientes que aún no se han creado, otra ventaja de los eventos delegados es su potencial para una sobrecarga mucho menor cuando se deben monitorear muchos elementos. En una tabla de datos con 1,000 filas en su tbody, el primer ejemplo de código adjunta un controlador a 1,000 elementos.

Un enfoque de eventos delegados (el segundo ejemplo de código) adjunta un controlador de eventos a un solo elemento tbody, y el evento solo necesita subir un nivel (desde el clic trhastatbody ).

Nota: Los eventos delegados no funcionan para SVG .

Ronen Rabinovici
fuente
11
esta sería una respuesta mejor aceptada porque sería más rápido delegar desde la tabla específica en lugar de desde el documento (el área de búsqueda sería mucho más pequeña)
msanjay
55
@msanjay: Aunque se prefiere orientar la búsqueda más cerca de los elementos, la diferencia de búsqueda / velocidad es muy pequeña en la práctica. Tendrías que hacer clic 50,000 veces por segundo para notar algo :)
Gone Coding
2
¿Se puede aplicar este enfoque para manejar los clics de las casillas de verificación en una fila cambiando tra un selector apropiado, como 'input[type=checkbox]', y esto se manejaría automáticamente para las filas recién insertadas?
JoeBrockhaus
1
¡Gracias! Esto resuelve mi problema. Esta simple declaración fue mi problema, "Los controladores de eventos están vinculados solo a los elementos seleccionados actualmente; deben existir en la página en el momento en que su código hace la llamada a ..."
Dennis Fazekas
216

Esta es una solución de JavaScript pura sin bibliotecas ni complementos:

document.addEventListener('click', function (e) {
    if (hasClass(e.target, 'bu')) {
        // .bu clicked
        // Do your thing
    } else if (hasClass(e.target, 'test')) {
        // .test clicked
        // Do your other thing
    }
}, false);

donde hasClassesta

function hasClass(elem, className) {
    return elem.className.split(' ').indexOf(className) > -1;
}

Live demo

El crédito va para Dave y Sime Vidas

Usando JS más moderno, hasClassse puede implementar como:

function hasClass(elem, className) {
    return elem.classList.contains(className);
}
Ram Patra
fuente
66
Puede usar Element.classList en lugar de dividirse
Eugen Konkov
2
@EugenKonkov Element.classListno es compatible con los navegadores más antiguos. Por ejemplo, IE <9.
Ram Patra
2
Un buen artículo sobre cómo hacer las cosas usando el script de vainilla en lugar de jQuery - toddmotto.com/…
Ram Patra
2
¿Qué tal burbujear? ¿Qué sucede si el evento de clic ocurrió en un elemento secundario del elemento que le interesa?
Andreas Trantidis
73

Puede agregar eventos a los objetos cuando los cree. Si está agregando los mismos eventos a varios objetos en diferentes momentos, crear una función con nombre podría ser la mejor opción.

var mouseOverHandler = function() {
    // Do stuff
};
var mouseOutHandler = function () {
    // Do stuff
};

$(function() {
    // On the document load, apply to existing elements
    $('select').hover(mouseOverHandler, mouseOutHandler);
});

// This next part would be in the callback from your Ajax call
$("<select></select>")
    .append( /* Your <option>s */ )
    .hover(mouseOverHandler, mouseOutHandler)
    .appendTo( /* Wherever you need the select box */ )
;
nickf
fuente
49

Simplemente puede envolver su llamada de enlace de evento en una función y luego invocarla dos veces: una vez en el documento listo y una vez después de su evento que agrega los nuevos elementos DOM. Si lo hace, querrá evitar vincular el mismo evento dos veces en los elementos existentes, por lo que deberá desvincular los eventos existentes o (mejor) solo vincular los elementos DOM que se acaban de crear. El código se vería así:

function addCallbacks(eles){
    eles.hover(function(){alert("gotcha!")});
}

$(document).ready(function(){
    addCallbacks($(".myEles"))
});

// ... add elements ...
addCallbacks($(".myNewElements"))
Greg Borenstein
fuente
2
Esta publicación realmente me ayudó a comprender un problema que tenía al cargar el mismo formulario y obtener 1,2,4,8,16 ... envíos. En lugar de usar .live () acabo de usar .bind () en mi devolución de llamada .load () Problema resuelto. ¡Gracias!
Thomas McCabe
42

Intenta usar en .live()lugar de .bind(); el .live()se unirá .hovera su casilla de verificación después de la solicitud se ejecuta Ajax.

usuario670265
fuente
32
El método live()fue desaprobado en la versión 1.7 a favor ony eliminado en la versión 1.9.
chridam
27

Enlace de eventos en elementos creados dinámicamente

Elemento único:

$(document.body).on('click','.element', function(e) {  });

Elemento hijo:

 $(document.body).on('click','.element *', function(e) {  });

Observe el agregado *. Se activará un evento para todos los elementos secundarios de ese elemento.

Me he dado cuenta que:

$(document.body).on('click','.#element_id > element', function(e) {  });

Ya no funciona, pero funcionaba antes. He estado usando jQuery de Google CDN , pero no sé si lo cambiaron.

MadeInDreams
fuente
Yeap y no están diciendo (document.body) dice ancestro que podría ser casi cualquier cosa
MadeInDreams
25

Puede usar el método live () para vincular elementos (incluso los recién creados) a eventos y controladores, como el evento onclick.

Aquí hay un código de muestra que he escrito, donde puedes ver cómo el método live () une los elementos elegidos, incluso los recién creados, a los eventos:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Untitled Document</title>
    </head>

    <body>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/jquery-ui.min.js"></script>

        <input type="button" id="theButton" value="Click" />
        <script type="text/javascript">
            $(document).ready(function()
                {
                    $('.FOO').live("click", function (){alert("It Works!")});
                    var $dialog = $('<div></div>').html('<div id="container"><input type ="button" id="CUSTOM" value="click"/>This dialog will show every time!</div>').dialog({
                                                                                                         autoOpen: false,
                                                                                                         tite: 'Basic Dialog'
                                                                                                     });
                    $('#theButton').click(function()
                    {
                        $dialog.dialog('open');
                        return('false');
                    });
                    $('#CUSTOM').click(function(){
                        //$('#container').append('<input type="button" value="clickmee" class="FOO" /></br>');
                        var button = document.createElement("input");
                        button.setAttribute('class','FOO');
                        button.setAttribute('type','button');
                        button.setAttribute('value','CLICKMEE');
                        $('#container').append(button);
                    });
                    /* $('#FOO').click(function(){
                                                     alert("It Works!");
                                                 }); */
            });
        </script>
    </body>
</html>
Fazi
fuente
23

Prefiero usar el selector y lo aplico en el documento.

Esto se une al documento y será aplicable a los elementos que se mostrarán después de cargar la página.

Por ejemplo:

$(document).on("click", 'selector', function() {
    // Your code here
});
Vatsal
fuente
2
el selector no debe estar encerrado por $, por lo tanto, el formato correcto será $ (documento) .on ("click", "selector", function () {// Su código aquí});
piloto automático
1
Tampoco tiene sentido envolver un objeto jQuery alrededor de la selectorvariable, cuando debe contener una cadena o un objeto Element que puede pasar directamente a ese argumentoon()
Rory McCrossan
20

Otra solución es agregar el oyente al crear el elemento. En lugar de poner al oyente en el cuerpo, pones al oyente en el elemento en el momento en que lo creas:

var myElement = $('<button/>', {
    text: 'Go to Google!'
});

myElement.bind( 'click', goToGoogle);
myElement.append('body');


function goToGoogle(event){
    window.location.replace("http://www.google.com");
}
Martin Da Rosa
fuente
Su código contiene 1 error: myElement.append('body');debe ser myElement.appendTo('body');. Por otro lado, si no hay necesidad de un mayor uso de la variable myElement, es más fácil y más corto de esta manera:$('body').append($('<button/>', { text: 'Go to Google!' }).bind( 'click', goToGoogle));
ddlab
17

Intenta así:

$(document).on( 'click', '.click-activity', function () { ... });
Rohit Suthar
fuente
16

Tome nota de la clase "PRINCIPAL" en la que se coloca el elemento, por ejemplo,

<div class="container">
     <ul class="select">
         <li> First</li>
         <li>Second</li>
    </ul>
</div>

En el escenario anterior, el objeto PRINCIPAL que mirará jQuery es "contenedor".

A continuación, tendrá básicamente nombres de elementos dentro de un contenedor, como ul, liy select:

$(document).ready(function(e) {
    $('.container').on( 'click',".select", function(e) {
        alert("CLICKED");
    });
 });
Aslan Kaya
fuente
15

Esto se hace por delegación del evento . El evento se vinculará en el elemento de clase wrapper pero se delegará en el elemento de clase selector. Así es como funciona.

$('.wrapper-class').on("click", '.selector-class', function() {
    // Your code here
});

Nota:

El elemento wrapper-class puede ser cualquier cosa ex. documento, cuerpo o su envoltorio. El contenedor ya debería existir . Sin embargo, selectorno necesariamente debe presentarse al momento de cargar la página. Puede llegar más tarde y el evento continuará selector sin falta .

Mustkeem K
fuente
14

podrías usar

$('.buttons').on('click', 'button', function(){
    // your magic goes here
});

o

$('.buttons').delegate('button', 'click', function() {
    // your magic goes here
});

Estos dos métodos son equivalentes pero tienen un orden diferente de parámetros.

ver: jQuery Delegate Event

Mensur Grišević
fuente
2
delegate()ahora está en desuso. No lo uses.
Rory McCrossan
14

Puede adjuntar un evento al elemento cuando se crea dinámicamente usando jQuery(html, attributes).

A partir de jQuery 1.8 , cualquier método de instancia de jQuery (un método de jQuery.fn) se puede usar como una propiedad del objeto pasado al segundo parámetro:

function handleDynamicElementEvent(event) {
  console.log(event.type, this.value)
}
// create and attach event to dynamic element
jQuery("<select>", {
    html: $.map(Array(3), function(_, index) {
      return new Option(index, index)
    }),
    on: {
      change: handleDynamicElementEvent
    }
  })
  .appendTo("body");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>

invitado271314
fuente
11

Aquí es por qué los elementos creados dinámicamente no responden a los clics:

var body = $("body");
var btns = $("button");
var btnB = $("<button>B</button>");
// `<button>B</button>` is not yet in the document.
// Thus, `$("button")` gives `[<button>A</button>]`.
// Only `<button>A</button>` gets a click listener.
btns.on("click", function () {
  console.log(this);
});
// Too late for `<button>B</button>`...
body.append(btnB);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>A</button>

Como solución alternativa, debe escuchar todos los clics y verificar el elemento fuente:

var body = $("body");
var btnB = $("<button>B</button>");
var btnC = $("<button>C</button>");
// Listen to all clicks and
// check if the source element
// is a `<button></button>`.
body.on("click", function (ev) {
  if ($(ev.target).is("button")) {
    console.log(ev.target);
  }
});
// Now you can add any number
// of `<button></button>`.
body.append(btnB);
body.append(btnC);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>A</button>

Esto se llama "Delegación de eventos". Buenas noticias, es una característica incorporada en jQuery :-)

var i = 11;
var body = $("body");
body.on("click", "button", function () {
  var letter = (i++).toString(36).toUpperCase();
  body.append($("<button>" + letter + "</button>"));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>A</button>

hoja
fuente
10

Cualquier p existente que exista en el momento en que se vincula el evento y si su página estaba creando dinámicamente elementos con el botón de nombre de clase , vincularía el evento a un padre que ya existe

$(document).ready(function(){
  //Particular Parent chield click
  $(".buttons").on("click","button",function(){
    alert("Clicked");
  });  
  
  //Dynamic event bind on button class  
  $(document).on("click",".button",function(){
    alert("Dymamic Clicked");
  });
  $("input").addClass("button");  
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="buttons">
  <input type="button" value="1">
  <button>2</button>
  <input type="text">
  <button>3</button>  
  <input type="button" value="5">  
  </div>
<button>6</button>

Ankit Kathiriya
fuente
7

Enlace el evento a un padre que ya existe:

$(document).on("click", "selector", function() {
    // Your code here
});
truongnm
fuente
5

Use el .on()método de jQuery http://api.jquery.com/on/ para adjuntar controladores de eventos al elemento activo.

También a partir de la versión 1.9 .live()se elimina el método.

Kalpesh Patel
fuente
3

Prefiero tener oyentes de eventos implementados en una forma de función modular en lugar de escribir un documentoyente de eventos de nivel. Entonces, me gusta a continuación. Tenga en cuenta que no puede suscribirse en exceso a un elemento con el mismo oyente de eventos, así que no se preocupe por adjuntar un oyente más de una vez, solo uno se queda.

var iterations = 4;
var button;
var body = document.querySelector("body");

for (var i = 0; i < iterations; i++) {
    button = document.createElement("button");
    button.classList.add("my-button");
    button.appendChild(document.createTextNode(i));
    button.addEventListener("click", myButtonWasClicked);
    body.appendChild(button);
}

function myButtonWasClicked(e) {
    console.log(e.target); //access to this specific button
}

Ronnie Royston
fuente
Prefiero esta implementación; Solo tengo que configurar una llamada de regreso
William
3

Otra solución flexible para crear elementos y eventos de enlace ( fuente )

// creating a dynamic element (container div)
var $div = $("<div>", {id: 'myid1', class: 'myclass'});

//creating a dynamic button
 var $btn = $("<button>", { type: 'button', text: 'Click me', class: 'btn' });

// binding the event
 $btn.click(function () { //for mouseover--> $btn.on('mouseover', function () {
    console.log('clicked');
 });

// append dynamic button to the dynamic container
$div.append($btn);

// add the dynamically created element(s) to a static element
$("#box").append($div);

Nota: Esto creará una instancia de controlador de eventos para cada elemento (puede afectar el rendimiento cuando se usa en bucles)

Prasad De Silva
fuente
0
<html>
    <head>
        <title>HTML Document</title>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    </head>

    <body>
        <div id="hover-id">
            Hello World
        </div>

        <script>
            jQuery(document).ready(function($){
                $(document).on('mouseover', '#hover-id', function(){
                    $(this).css('color','yellowgreen');
                });

                $(document).on('mouseout', '#hover-id', function(){
                    $(this).css('color','black');
                });
            });
        </script>
    </body>
</html>
Fakhrul Hasan
fuente
3
Si bien este fragmento de código puede resolver el problema, no explica por qué o cómo responde la pregunta. Incluya una explicación para su código, ya que eso realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código.
Palec
0

Estaba buscando una solución para conseguir $.bindy$.unbind trabajar sin problemas en elementos añadidos de forma dinámica.

Como en () hace el truco para adjuntar eventos, a fin de crear una desvinculación falsa en aquellos a los que vine:

const sendAction = function(e){ ... }
// bind the click
$('body').on('click', 'button.send', sendAction );

// unbind the click
$('body').on('click', 'button.send', function(){} );
Evhz
fuente
La desvinculación no funciona, esto simplemente agrega otro evento que apunta a una función vacía ...
Fabian Bigler