แนบเหตุการณ์กับองค์ประกอบแบบไดนามิกในจาวาสคริปต์


92

ฉันกำลังพยายามแทรกข้อมูล 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

คำตอบ:


211

เนื่องจากองค์ประกอบของคุณถูกสร้างขึ้นแบบไดนามิก คุณควรใช้การมอบหมายเหตุการณ์เพื่อจัดการเหตุการณ์

 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 พาเรนต์อื่น ๆ ก็ใช้ได้ อะไรคือทางออกที่ดีที่สุดสำหรับการมอบหมายงาน
Kholiavko

3
@Kholiavko ฉันจะบอกว่าตราบใดที่องค์ประกอบ "หลัก" ไม่ได้ถูกสร้างขึ้นแบบไดนามิกมันควรจะใช้งานได้ ฉันจะผูกตัวจัดการเหตุการณ์กับพาเรนต์แรกขององค์ประกอบสร้างไดนามิก d เพื่อไม่ให้เกิดความขัดแย้งระหว่างตัวจัดการเหตุการณ์ที่แตกต่างกัน
jilykate

1
แต่บางครั้งก็ไม่สามารถใช้งานได้บน iPhone แม้แต่องค์ประกอบ "หลัก" ก็ไม่ได้สร้างขึ้นแบบไดนามิก นี่คือexapmleมันใช้งานได้กับทุกเบราว์เซอร์ แต่ไม่ได้ใช้บน iphone (ซาฟารีและแม้แต่โครเมี่ยม) และฉันไม่เข้าใจว่าทำไม
Kholiavko

2
@Kholiavko นี่เป็นเพียงเพราะ codepen ใช้ sandbox เพื่อห่อทุกอย่าง หากคุณเขียนโค้ดเหล่านั้นในหน้าปกติมันจะใช้ได้
jilykate

2
พิจารณาเพิ่มเครื่องหมายเท่ากับพิเศษ (===) เพื่อป้องกันการบังคับแบบที่ไม่คาดคิด
Staccato

19

มีวิธีแก้ปัญหาโดยการจับการคลิกdocument.bodyจากนั้นตรวจสอบเป้าหมายเหตุการณ์

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

2
หรือตู้คอนเทนเนอร์ที่ใกล้ที่สุด
mplungjan

ตอบโจทย์เป๊ะ!
Rahul Purohit

ว้าวมันวิเศษมากประหยัดเวลาและรหัสมาก! :)
Anna Medyukh

3
หมายเหตุ: 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
วิธีนี้ใช้ได้กับ CSR (การแสดงผลฝั่งไคลเอ็นต์) เท่านั้น สิ่งนี้จะใช้ไม่ได้กับ SSR (การแสดงผลฝั่งเซิร์ฟเวอร์) เพื่อให้ทำงานได้ทั้ง CSR และ SSR Event Delegationคือทางออก:javascript document.addEventListener('click',function(e){ if(e.target && e.target.id== 'brnPrepend'){ //do something } });
Yatendra Goel

แต่ด้วยเหตุผลด้านประสิทธิภาพแนวทางนี้ดีกว่า ขึ้นอยู่กับกรณีการใช้งานของคุณ
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. ฉันสงสัยว่ามีความแตกต่างในทางปฏิบัติระหว่างวิธีการต่างๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.