Dołącz zdarzenie do elementów dynamicznych w javascript


90

Próbuję dynamicznie wstawiać dane html do listy, która jest tworzona dynamicznie, ale kiedy próbuję dołączyć zdarzenie onclick do przycisku, który jest dynamicznie tworzony, zdarzenie nie jest uruchamiane. Rozwiązanie byłoby naprawdę mile widziane.

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

});

Kod 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>

Jak tworzysz HTML?
jim0thy

1
Powiedziałbym, że to dlatego, że element nie istnieje, gdy próbujesz dołączyć detektor zdarzeń. - spójrz na to learn.jquery.com/events/event-delegation
Craicerjack,

1
Przenieś swój addEventListener do detektora zdarzeń btnSubmit
slebetman

Odpowiedzi:


199

Wynika to z faktu, że Twój element jest tworzony dynamicznie. Do obsługi zdarzenia należy używać delegowania zdarzeń.

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

jquery ułatwia:

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

Oto artykuł dotyczący delegowania zdarzeń delegowania zdarzeń


1
Dziękuję Ci! Czasami czyste rozwiązanie JS nie działa na iPhonie, ale na przykład, jeśli zmienisz dokument w innym nadrzędnym divie, działa. Jakie jest najlepsze rozwiązanie dla delegacji?
Kholiavko

3
@Kholiavko Powiedziałbym, że dopóki element „nadrzędny” nie jest tworzony dynamicznie, powinien działać. Chciałbym powiązać procedurę obsługi zdarzeń z pierwszym rodzicem dynamicznego elementu create d, aby nie było konfliktów między różnymi
programami

1
Ale czasami nie działa na iPhonie, nawet element „nadrzędny” nie jest tworzony dynamicznie. Oto przykład , działa na wszystkich przeglądarkach, ale nie na iPhonie (bezpiecznym, a nawet chromowanym). I nie rozumiem dlaczego.
Kholiavko

2
@Kholiavko dzieje się tak tylko dlatego, że codepen używa piaskownicy do pakowania wszystkiego. Jeśli naprawdę napiszesz ten kod na normalnej stronie, zadziała
jilykate

2
Rozważ dodanie dodatkowego znaku równości (===), aby zapobiec nieoczekiwanemu wymuszeniu typu
Staccato

17

Można obejść ten problem, przechwytując kliknięcia, document.bodya następnie sprawdzając cel zdarzenia.

document.body.addEventListener( 'click', function ( event ) {
  if( event.srcElement.id == 'btnSubmit' ) {
    someFunc();
  };
} );

2
Lub najbliższy kontener
mplungjan

Doskonała odpowiedź!
Rahul Purohit

WOW, to jest niesamowite, oszczędza tyle czasu i kodu! :)
Anna Medyukh

Uwaga: element event.srcElement jest przestarzały. Zamiast tego zaleca się użycie event.target.
dsanchez

12

Musisz dołączyć zdarzenie po wstawianiu elementów, na przykład nie dołączasz globalnego zdarzenia do swojego, documentale określone zdarzenie do wstawianych elementów.

na przykład

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>


2
To podejście będzie działać tylko w przypadku CSR (renderowanie po stronie klienta). To nie zadziała w przypadku SSR (renderowanie po stronie serwera). Aby to działało zarówno dla CSR, jak i SSR, rozwiązaniem jest Event Delegation :javascript document.addEventListener('click',function(e){ if(e.target && e.target.id== 'brnPrepend'){ //do something } });
Yatendra Goel

Ale ze względu na wydajność to podejście jest lepsze. Więc to zależy od twojego przypadku użycia.
R3tep

8

Różnica polega na tym, jak tworzysz i dołączasz elementy w DOM.

Jeśli utworzysz element za pośrednictwem document.createElement , dodaj detektor zdarzeń i dołącz go do DOM. Twoje wydarzenia wybuchną.

Jeśli utworzysz element jako ciąg, taki jak ten: html + = "<li> test </li>" `, element jest technicznie tylko łańcuchem. Ciągi nie mogą mieć detektorów zdarzeń.

Jednym z rozwiązań jest utworzenie każdego elementu za pomocą, document.createElementa następnie dodanie go bezpośrednio do elementu DOM.

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

3

Możesz zrobić coś podobnego do tego:

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

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

Teraz możesz zarejestrować wydarzenie takie jak:

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

  });

Wsparcie przeglądarki

chrome - 4.0, Edge - 9.0, Firefox - 3.5 Safari - 3.2, Opera - 10.0 i nowsze


2

Stworzyłem małą bibliotekę, aby pomóc w tym: Źródło biblioteki na 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>

Funkcjonalność jest podobna do jQuery.on ().

Biblioteka używa metody Element.matches () do testowania elementu docelowego względem danego selektora. Gdy zdarzenie jest wyzwalane, wywołanie zwrotne jest wywoływane tylko wtedy, gdy element docelowy pasuje do podanego selektora.


0

Wiem, że temat jest zbyt stary, ale dałem sobie kilka minut na stworzenie bardzo użytecznego kodu, który działa dobrze i bardzo łatwo używając czystego JAVASCRIPT. Oto kod z prostym przykładem:

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>


0

Stworzyłem do tego prostą funkcję.

Plik _caseFunkcja pozwala uzyskać nie tylko cel, ale również uzyskać elementu nadrzędnego, w którym wiązania na imprezę.

Funkcja zwrotna zwraca zdarzenie, które zawiera element docelowy (evt.target ) i element nadrzędny pasujący do selektora (this ). Tutaj możesz robić rzeczy, których potrzebujesz po kliknięciu elementu.

Nie zdecydowałem jeszcze, co jest lepsze, if-elseczyswitch

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>

Mam nadzieję, że to pomoże!


0

Znalazłem rozwiązanie opublikowane przez jillykate , ale tylko wtedy, gdy element docelowy jest najbardziej zagnieżdżony. Jeśli tak nie jest, można to naprawić, iterując po rodzicach, tj

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

Zauważ również, że możemy obsługiwać zdarzenia za pomocą dowolnego atrybutu lub dołączać nasz detektor na dowolnym poziomie. Powyższy kod używa atrybutu niestandardowego i window. Wątpię, czy istnieje jakakolwiek pragmatyczna różnica między różnymi metodami.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.