Приєднати подію до динамічних елементів у javascript


90

Я намагаюся динамічно вставляти html-дані до списку, який динамічно створюється, але коли я намагаюся приєднати подію onclick для кнопки, яка динамічно створюється, подія не запускається. Рішення було б дуже вдячне.

Код 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);
});

});

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>

Як ви створюєте html?
jim0thy

1
Я б сказав, що його не існує, коли елемент намагається приєднати слухач події. - подивіться це learn.jquery.com/events/event-delegation
Craicerjack

1
Перемістіть addEventListener у прослуховувач подій btnSubmit
slebetman

Відповіді:


199

Це пов’язано з тим, що ваш елемент динамічно створюється. Для обробки події слід використовувати делегування події.

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

jquery спрощує:

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

Ось стаття про статтю делегування події


1
Дякую! Іноді чисте рішення JS не працює на iphone, але якщо змінити документ на іншому батьківському div, наприклад, воно працює. Яке найкраще рішення для делегування?
Холявко

3
@Kholiavko Я б сказав, поки елемент "батько" не створюється динамічно, він повинен працювати. Я б прив’язав обробник подій до першого батьківського елемента динамічного create d, щоб не було конфліктів між різними обробниками подій
jilykate,

1
Але іноді це не працює на iphone, навіть "батьківський" елемент не створюється динамічно. Ось exapmle , він працює на всіх браузерах, але не на iphone (сафарі і навіть хром). І я не розумію, чому.
Холявко

2
@Kholiavko це просто тому, що codepen використовує пісочницю, щоб обернути все. Якщо ви справді
напишете

2
Подумайте про додавання додаткового знака рівності (===) для запобігання несподіваному примусу типу
Стаккато,

17

Існує обхідний шлях, зафіксувавши кліки, document.bodyа потім перевіривши ціль події.

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

2
Або найближчий контейнер
mplungjan

Ідеальна відповідь!
Рахул Пурохіт,

WOW це дивно, економить стільки часу та коду! :)
Анна Медюх

Примітка: event.srcElement застарілий. Натомість рекомендується використовувати event.target.
dsanchez

12

Ви повинні вкласти подію після елементів вставки, наприклад, ви не прикріплюєте глобальну подію до своїх, documentа конкретну подію до вставлених елементів.

напр

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
Цей підхід буде працювати лише для КСВ (візуалізація на стороні клієнта). Це не буде працювати для SSR (візуалізація на стороні сервера). Щоб це працювало як для КСВ, так і для РСР, рішенням делегування подій є:javascript document.addEventListener('click',function(e){ if(e.target && e.target.id== 'brnPrepend'){ //do something } });
Ятендра Гоель,

Але з міркувань ефективності такий підхід є кращим. Отже, це залежить від вашого випадку використання.
R3tep

8

Різниця полягає в тому, як ви створюєте та додаєте елементи в DOM.

Якщо ви створюєте елемент за допомогою document.createElement, додайте прослуховувач подій та додайте його до DOM. Ваші події відбуватимуться.

Якщо ви створюєте елемент у вигляді рядка наступним чином: html + = "<li> test </li>" `, elment технічно є просто рядком. Рядки не можуть мати прослуховувачі подій.

Одне з рішень - створити кожен елемент за допомогою, document.createElementа потім безпосередньо додати його до елемента DOM.

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

3

Ви можете зробити щось подібне до цього:

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

Тепер ви можете зареєструвати подію, наприклад,

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

  });

Підтримка браузера

chrome - 4.0, Edge - 9.0, Firefox - 3.5 Safari - 3.2, Opera - 10.0 і вище


2

Я створив невелику бібліотеку, яка допоможе з цим: Джерело бібліотеки на 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>

Функціонал схожий на jQuery.on ().

Бібліотека використовує метод Element.matches () для тестування цільового елемента щодо даного селектора. Коли ініціюється подія, зворотний виклик викликається, лише якщо цільовий елемент відповідає заданому селектору.


0

Я знаю, що тема занадто стара, але я дав собі кілька хвилин, щоб створити дуже корисний код, який чудово працює і дуже простий у використанні JAVASCRIPT. Ось код із простим прикладом:

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

Я зробив для цього просту функцію.

_caseФункція дозволяє не тільки отримати мета, а й отримати батьківський елемент , де ви зв'язати захід.

Функція зворотного виклику повертає подію, яка містить ціль ( evt.target) та батьківський елемент, що відповідає селектору ( this). Тут ви можете робити необхідні речі після натискання елемента.

Я ще не вирішив, що краще, те if-elseчи іншеswitch

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>

Сподіваюся, це допоможе!


0

Я знайшов рішення, опубліковане jillykate, працює, але лише якщо цільовий елемент є найбільш вкладеним. Якщо це не так, це можна виправити шляхом ітерації над батьками, тобто

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

Також зверніть увагу, що ми можемо обробляти події за будь-яким атрибутом або приєднувати наш слухач на будь-якому рівні. У наведеному вище коді використовується спеціальний атрибут і window. Я сумніваюся, що існує якась прагматична різниця між різними методами.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.