Wiązanie zdarzeń na dynamicznie tworzonych elementach?

1677

Mam trochę kodu, w którym przeglądam wszystkie zaznaczone pola na stronie i .hoverwiążę z nimi zdarzenie, aby zrobić kręcenie z ich szerokością mouse on/off.

Dzieje się tak na stronie gotowej i działa dobrze.

Problem, który mam, polega na tym, że wszystkie pola wyboru dodawane przez Ajax lub DOM po początkowej pętli nie będą powiązane ze zdarzeniem.

Znalazłem tę wtyczkę ( jQuery Live Query Plugin ), ale zanim dodam kolejne 5k do moich stron za pomocą wtyczki, chcę sprawdzić, czy ktoś wie, jak to zrobić, albo z jQuery bezpośrednio, albo inną opcją.

Eli
źródło

Odpowiedzi:

2249

Począwszy od jQuery 1.7 powinieneś używać jQuery.fn.on:

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

Wcześniej zalecanym podejściem było użycie live():

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

Jednak live()było przestarzałe w 1,7 za on()i całkowicie usuwa 1,9. live()Podpis:

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

... można zastąpić następującym on()podpisem:

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

Na przykład, jeśli twoja strona dynamicznie tworzy elementy z nazwą klasy dosomething, powiążesz zdarzenie z rodzicem, który już istnieje (jest to sedno problemu tutaj, potrzebujesz czegoś, co istnieje, aby się z nim połączyć, nie łącz z zawartość dynamiczna), może to być (i najłatwiejsza opcja) document. Pamiętaj documentjednak, że może nie być najbardziej wydajną opcją .

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

Każdy rodzic, który istnieje w momencie powiązania zdarzenia, jest w porządku. Na przykład

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

dotyczyłby

<div class="buttons">
    <!-- <button>s that are generated dynamically and added here -->
</div>
Popnoodles
źródło
7
Należy pamiętać, że metoda na żywo działa tylko w przypadku niektórych zdarzeń, a nie innych, takich jak loadmetadata. (Zobacz rozdział dotyczący zastrzeżeń w dokumentacji.)
Sam Dutton
48
Dowiedz się więcej o delegowaniu wydarzeń tutaj: learn.jquery.com/events/event-delegation .
Felix Kling
9
Jakiś sposób, aby to osiągnąć za pomocą czystego javascript / vanilla js?
Ram Patra,
15
@ Ramswaroop wszystko, co możesz zrobić w jQuery, można osiągnąć bez jQuery. Oto dobry przykład delegowania wydarzeń bez jQuery
Dave
5
@dave Zastanawiam się, dlaczego wskazanej przez ciebie odpowiedzi nie ma tutaj. Eli wyraźnie poprosił o rozwiązanie bez żadnej wtyczki, jeśli to możliwe.
Ram Patra,
377

Istnieje dobre wytłumaczenie w dokumentacji jQuery.fn.on.

W skrócie:

Procedury obsługi zdarzeń są powiązane tylko z aktualnie wybranymi elementami; muszą istnieć na stronie w momencie, gdy Twój kod wywołuje połączenie .on().

Dlatego w poniższym przykładzie #dataTable tbody trmusi istnieć przed wygenerowaniem kodu.

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

Jeśli do strony wstrzykuje się nowy kod HTML, lepiej jest użyć zdarzeń delegowanych w celu dołączenia procedury obsługi zdarzeń, jak opisano poniżej.

Zdarzenia delegowane mają tę zaletę, że mogą przetwarzać zdarzenia z elementów potomnych, które są dodawane do dokumentu w późniejszym czasie. Na przykład, jeśli tabela istnieje, ale wiersze są dodawane dynamicznie za pomocą kodu, następujące elementy ją obsłużą:

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

Oprócz zdolności do obsługi zdarzeń na elementach potomnych, które nie zostały jeszcze utworzone, kolejną zaletą zdarzeń delegowanych jest ich potencjał znacznie niższego obciążenia, gdy wiele elementów musi być monitorowanych. W tabeli danych zawierającej 1000 wierszy tbodypierwszy przykład kodu dołącza moduł obsługi do 1000 elementów.

Podejście z delegowanymi zdarzeniami (drugi przykład kodu) dołącza moduł obsługi zdarzenia do tylko jednego elementu tbody, a zdarzenie musi tylko przejść w górę o jeden poziom (od kliknięcia trdo tbody).

Uwaga: Zdarzenia delegowane nie działają w przypadku SVG .

Ronen Rabinovici
źródło
11
to byłaby lepiej zaakceptowana odpowiedź, ponieważ szybciej byłoby delegować dane z określonej tabeli niż z samego dokumentu (obszar wyszukiwania byłby znacznie mniejszy)
msanjay
5
@ msanjay: Chociaż preferowane jest kierowanie wyszukiwania bliżej elementów, różnica wyszukiwania / prędkości jest w praktyce bardzo niewielka. Aby cokolwiek zauważyć, musiałbyś kliknąć 50 000 razy na sekundę :)
Gone Coding
2
Czy to podejście można zastosować do obsługi kliknięć pól wyboru w rzędzie, zmieniając trna odpowiedni selektor, na przykład 'input[type=checkbox]', i byłoby to automatycznie obsługiwane dla nowo wstawianych wierszy?
JoeBrockhaus,
1
Dziękuję Ci! To rozwiązuje mój problem. To proste stwierdzenie było moim problemem: „Procedury obsługi zdarzeń są powiązane tylko z aktualnie wybranymi elementami; muszą istnieć na stronie w momencie, gdy twój kod wywołuje ...”
Dennis Fazekas
216

To jest czyste rozwiązanie JavaScript bez bibliotek lub wtyczek:

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

gdzie hasClassjest

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

Live demo

Podziękowania należą się Dave'owi i Sime'owi Vidasowi

Za pomocą bardziej nowoczesnego JS hasClassmożna zaimplementować jako:

function hasClass(elem, className) {
    return elem.classList.contains(className);
}
Ram Patra
źródło
6
Możesz użyć Element.classList zamiast dzielić
Eugen Konkov
2
@EugenKonkov Element.classListnie jest obsługiwany w starszych przeglądarkach. Na przykład IE <9.
Ram Patra
2
Fajny artykuł o tym, jak załatwić sprawę za pomocą skryptu waniliowego zamiast jQuery - toddmotto.com/...
Ram Patra
2
co powiesz na bulgotanie? Co się stanie, jeśli zdarzenie kliknięcia miało miejsce w elemencie potomnym elementu, który Cię interesuje?
Andreas Trantidis,
73

Możesz dodawać zdarzenia do obiektów podczas ich tworzenia. Jeśli dodajesz te same zdarzenia do wielu obiektów w różnych momentach, dobrym rozwiązaniem może być utworzenie nazwanej funkcji.

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 */ )
;
pseudonim
źródło
49

Możesz po prostu zawinąć wywołanie powiązania zdarzenia w funkcję, a następnie wywołać go dwukrotnie: raz w dokumencie gotowy i raz po zdarzeniu, które dodaje nowe elementy DOM. Jeśli to zrobisz, będziesz chciał uniknąć podwójnego wiązania tego samego zdarzenia z istniejącymi elementami, więc musisz usunąć powiązanie istniejących zdarzeń lub (lepiej) powiązać tylko z nowo utworzonymi elementami DOM. Kod wyglądałby mniej więcej tak:

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

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

// ... add elements ...
addCallbacks($(".myNewElements"))
Greg Borenstein
źródło
2
Ten post naprawdę pomógł mi zrozumieć problem z ładowaniem tego samego formularza i otrzymywaniem 1,2,4,8,16 ... zgłoszeń. Zamiast używać .live () właśnie użyłem .bind () w moim wywołaniu zwrotnym .load (). Problem rozwiązany. Dzięki!
Thomas McCabe
42

Spróbuj użyć .live()zamiast .bind(); .live()zwiąże .hoverdo wyboru po zgłoszeniu wniosku sporządzi Ajax.

użytkownik670265
źródło
32
Metoda live()została wycofana w wersji 1.7 na korzyść oni usunięta w wersji 1.9.
Chridam
27

Wiązanie zdarzeń na dynamicznie tworzonych elementach

Pojedynczy element:

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

Element podrzędny:

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

Zwróć uwagę na dodane *. Wydarzenie zostanie uruchomione dla wszystkich dzieci tego elementu.

Zauważyłem to:

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

To już nie działa, ale działało wcześniej. Korzystam z jQuery z Google CDN , ale nie wiem, czy to zmienili.

MadeInDreams
źródło
Tak, a oni nie mówią (document.body), że mówi przodek, którym może być
właściwie
25

Możesz użyć metody live (), aby powiązać elementy (nawet te nowo utworzone) ze zdarzeniami i modułami obsługi, takimi jak zdarzenie onclick.

Oto przykładowy kod, który napisałem, gdzie możesz zobaczyć, w jaki sposób metoda live () wiąże wybrane elementy, nawet te nowo utworzone, ze zdarzeniami:

<!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
źródło
22

Wolę używać selektora i stosuję go na dokumencie.

To wiąże się z dokumentem i będzie miało zastosowanie do elementów, które będą renderowane po załadowaniu strony.

Na przykład:

$(document).on("click", 'selector', function() {
    // Your code here
});
Vatsal
źródło
2
selektor nie powinien być ujęty w $, więc poprawnym formatem będzie $ (dokument) .on („kliknij”, „selektor”, funkcja () {// Twój kod tutaj});
autopilot
1
Nie ma również sensu owijać obiektu jQuery wokół selectorzmiennej, gdy musi ona zawierać ciąg lub obiekt Element, który można po prostu przekazać bezpośrednio do argumentuon()
Rory
20

Innym rozwiązaniem jest dodanie detektora podczas tworzenia elementu. Zamiast wstawiać detektor do ciała, wstawiasz detektor do elementu w momencie jego tworzenia:

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
źródło
Twój kod zawiera 1 błąd: myElement.append('body');musi być myElement.appendTo('body');. Z drugiej strony, jeśli nie ma potrzeby dalszego stosowania zmiennej myElementjest to łatwiejsze i krótsze w ten sposób:$('body').append($('<button/>', { text: 'Go to Google!' }).bind( 'click', goToGoogle));
ddlab
17

Spróbuj w ten sposób -

$(document).on( 'click', '.click-activity', function () { ... });
Rohit Suthar
źródło
16

Zwróć uwagę na klasę „MAIN”, w której element jest umieszczony, na przykład

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

W powyższym scenariuszu GŁÓWNY obiekt, który będzie oglądać jQuery, to „kontener”.

Wtedy w zasadzie mają elementy nazwy pod pojemnikiem, takich jak ul, lioraz select:

$(document).ready(function(e) {
    $('.container').on( 'click',".select", function(e) {
        alert("CLICKED");
    });
 });
Aslan Kaya
źródło
14

możesz użyć

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

lub

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

te dwie metody są równoważne, ale mają inną kolejność parametrów.

patrz: jQuery Delegate Event

Mensur Grišević
źródło
2
delegate()jest teraz przestarzałe Nie używaj tego.
Rory McCrossan,
14

Możesz dołączyć zdarzenie do elementu, gdy jest tworzone dynamicznie za pomocą jQuery(html, attributes).

Począwszy od jQuery 1.8 , dowolna metoda instancji jQuery (metoda jQuery.fn) może być używana jako właściwość obiektu przekazanego do drugiego parametru:

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>

guest271314
źródło
14

Odbywa się to poprzez delegowanie wydarzeń . Zdarzenie zostanie powiązane z elementem klasy opakowania, ale zostanie delegowane do elementu klasy selektora. Tak to działa.

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

Uwaga:

elementem klasy otoki może być cokolwiek np. dokument, treść lub opakowanie. Opakowanie powinno już istnieć . Jednak selectorniekoniecznie musi być prezentowane podczas ładowania strony. Może przyjść później, a wydarzenie będzie się wiązało selector bezbłędnie .

Mustkeem K
źródło
11

Oto dlaczego dynamicznie tworzone elementy nie reagują na kliknięcia:

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>

Aby obejść ten problem, należy odsłuchać wszystkie kliknięcia i sprawdzić element źródłowy:

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>

Nazywa się to „delegowaniem zdarzeń”. Dobra wiadomość, jest to wbudowana funkcja w 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>

liść
źródło
10

Wszelkie strony, które istnieją w momencie powiązania zdarzenia, a jeśli twoja strona dynamicznie tworzy elementy za pomocą przycisku nazwy klasy , powiążesz zdarzenie z rodzicem, który już istnieje

$(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
źródło
7

Powiąż zdarzenie z rodzicem, który już istnieje:

$(document).on("click", "selector", function() {
    // Your code here
});
truongnm
źródło
5

Skorzystaj z .on()metody jQuery http://api.jquery.com/on/ aby dołączyć procedury obsługi zdarzeń do elementu live.

Również od wersji 1.9 .live()metoda jest usuwana.

Kalpesh Patel
źródło
3

Wolę mieć detektory zdarzeń wdrożone w sposób modułowy niż skrypty documentdetektora zdarzeń poziomu. Lubię to poniżej. Uwaga: nie można nadpisać elementu z tym samym detektorem zdarzeń, więc nie martw się o dołączenie detektora więcej niż jeden raz - tylko jeden drążek.

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
źródło
Wolę tę implementację; Muszę tylko oddzwonić
William,
3

Kolejne elastyczne rozwiązanie do tworzenia elementów i łączenia zdarzeń ( źródło )

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

Uwaga: Spowoduje to utworzenie instancji procedury obsługi zdarzeń dla każdego elementu (może mieć wpływ na wydajność, gdy jest używany w pętlach)

Prasad De Silva
źródło
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
źródło
3
Ten fragment kodu może rozwiązać problem, ale nie wyjaśnia, dlaczego ani w jaki sposób odpowiada na pytanie. Dołącz wyjaśnienie swojego kodu, ponieważ to naprawdę pomaga poprawić jakość Twojego postu. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu.
Palec
0

Szukałem rozwiązania, aby uzyskać $.bindi $.unbindpracować bez problemów w dynamicznie dodawanych elementach.

Jak na () robi sztuczkę, aby dołączyć zdarzenia, aby stworzyć fałszywe unbind na tych, do których przyszedłem:

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

// unbind the click
$('body').on('click', 'button.send', function(){} );
Evhz
źródło
Rozpakowywanie nie działa, to po prostu dodaje kolejne zdarzenie, które wskazuje na pustą funkcję ...
Fabian Bigler,