【问题标题】:Attach event to dynamic elements in javascriptAttach event to dynamic elements in javascript
【发布时间】:2022-11-27 14:07:21
【问题描述】:

I'm trying to insert html data dynamically to a list that is dynamically created, but when i try to attach an onclick event for the button that is dynamically created the event is not firing. Solution would be really appreciated.

Javascript code:

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 Code:

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

【问题讨论】:

  • How are you creating the html?
  • I would say its because the element doesnt exist when you try to attach the event listener. - have a look at this learn.jquery.com/events/event-delegation
  • Move your addEventListener into the event listener of btnSubmit
  • Hey, I just wanted to mention that it seems like you're trying to create a ul element with li elements within it a hard way. Instead, you could just use `` (backticks) and put elements in the way you'd normally do it in HTML.

标签: javascript


【解决方案1】:

This is due to the fact that your element is dynamically created, so it is attached to the DOM later, but your addEventListener call already occurred in the past. You should use event delegation to handle the event.

document.addEventListener("click", function(e){
  const target = e.target.closest("#btnPrepend"); // Or any other selector.

  if(target){
    // Do something with `target`.
  }
});

closest ensures that the click occurredanywhereinside the target element oristhe target element itself. This is useful if, for example, instead of your &lt;input id="btnPrepend"/&gt; you had a &lt;button id="btnPrepend"&gt;&lt;i class="icon"&gt;+&lt;/i&gt; prepend&lt;/button&gt; and you clicked the &lt;i class="icon"&gt;+&lt;/i&gt;.

jQuery makes it easier:

$(document).on("click", "#btnPrepend", function(){
  // Do something with `$(this)`.
});

Here is an article about event delegation.

【讨论】:

  • Thank you! Sometimes pure JS solution does not work on iphone, but if change document on some another parent div, for example, it works. What the best solution for delegation ?
  • @Kholiavko I would say as long as the "parent" element is not dynamically created, it should work. I would bind event handler to the first parent of dynamic create d element so that there is no conflicts between different event handlers
  • But sometimes it does not work on iphone, even "parent" element is not created dynamically. Here is exapmle, it works on all browser, but does not on iphone (safary and even chrome). And I don`t understand why.
  • @Kholiavko this is just because codepen use a sandbox to wrap everything. If you really write those code in a normal page it will work
  • Consider adding an extra equal sign (===) to prevent unexpected type coercion
【解决方案2】:

There is a workaround by capturing clicks on document.body and then checking event target.

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

【讨论】:

  • Or nearest container
  • Note: event.srcElement is deprecated. It's recommended to use event.target instead.
【解决方案3】:

The difference is in how you create and append elements in the DOM.

If you create an element via document.createElement, add an event listener, and append it to the DOM. Your events will fire.

If you create an element as a string like this: html += "<li>test</li>"`, the elment is technically just a string. Strings cannot have event listeners.

One solution is to create each element with document.createElement and then add those to a DOM element directly.

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

【讨论】:

    【解决方案4】:

    You must attach the event after insert elements, like that you don't attach a global event on your document but a specific event on the inserted elements.

    e.g.

    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>

    【讨论】:

    • This approach will only work for CSR (Client Side Rendering). This will not work for SSR (Server-Side Rendering). To make it work for both CSR and SSR, Event Delegation is the solution: javascript document.addEventListener('click',function(e){ if(e.target &amp;&amp; e.target.id== 'brnPrepend'){ //do something } });
    • But for performance reason, this approach is better. So it's depend of your use case.
    • My take: I used appendChild to append my element to a div, and ON THE NEXT LINE messed with innerHTML to add a whitespace after the element: Result the event listener right the next line after innerHTML, was blocked! My solution (apart from your answer) was avoiding innerHTML altogether: the normal event listener on the variable worked
    【解决方案5】:

    Here's a reusable function that takes advantage of element.matches:

    function delegate_event(event_type, ancestor_element, target_element_selector, listener_function)
    {
        ancestor_element.addEventListener(event_type, function(event)
        {
            if (event.target && event.target.matches && event.target.matches(target_element_selector))
            {
                (listener_function)(event);
            }
        });
    }
    

    Here's how you would use it for a click event:

    delegate_event('click', document, '.alert-button', your_function_here);
    

    【讨论】:

      【解决方案6】:

      You can do something similar to this:

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

      【讨论】:

        【解决方案7】:

        I have created a small library to help with this: Library source on 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>
        

        The functionality is similar to jQuery.on().

        The library uses the Element.matches() method to test the target element against the given selector. When an event is triggered the callback is only called if the target element matches the selector given.

        【讨论】:

          【解决方案8】:
          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;
              }
          

          Now you can register event like,

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

          Browser Support

          chrome - 4.0, Edge - 9.0, Firefox - 3.5 Safari - 3.2, Opera - 10.0 and above

          【讨论】:

            【解决方案9】:

            Here's another solution I came up with. I found this much easier to deal with compared to event targets as they do not fire if a child is clicked. The method is to set event listeners and reset them after appending new elements to the document:

            function listen() {
                document.querySelectorAll('.btn').forEach(btn => {
                    btn.addEventListener("click", function(){
                        console.log("Hello World");
                    });
                });
            }
            
            listen();
            
            document.body.innerHTML += "<p class='btn'>Click Here 2.0 .</p>";
            
            listen();
            <!DOCTYPE html>
            <html>
            <body>
            
            <p class='btn'>Click Here.</p>
            
            </body>
            </html>

            Edit: To avoid stacked listeners of same event see this: Answer

            【讨论】:

            • There are 2 problems, 1) You will have to trigger listen() function every time the new Item has to be added, 2) You are binding multiple events of type click every time to previous elements when you run listen() function.
            【解决方案10】:

            I know that the topic is too old but I gave myself some minutes to create a very useful code that works fine and very easy using pure JAVASCRIPT. Here is the code with a simple example:

            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>

            【讨论】:

              【解决方案11】:

              I have found the solution posted byjillykateworks, but only if the target element is the most nested. If this is not the case, this can be rectified by iterating over the parents, i.e.

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

              Also note we can handle events by any attribute, or attach our listener at any level. The code above uses a custom attribute and window. I doubt there is any pragmatic difference between the various methods.

              【讨论】:

                【解决方案12】:

                I've made a simple function for this.

                The _case function allows you to not only get the target, but also get the parent element where you bind the event on.

                The callback function returns the event which holds the target (evt.target) and the parent element matching the selector (this). Here you can do the stuff you need after the element is clicked.

                I've not yet decided which is better, the if-else or the 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>

                Hope it helps!

                【讨论】:

                  【解决方案13】:
                  First of all add the dynamic class to the dynamically created inputboxes
                  var ele = document.createElement('textarea');
                  ele.className = "css-class-name"; // set the CSS class
                  ele.setAttribute('type', 'textarea');
                  ele.setAttribute('value', '');
                  ele.setAttribute("id", `row${rcount}_${c}`);
                   then do the following 
                              const btns = document.querySelectorAll('.css-class-name');
                              for (let i = 0; i < btns.length; i++) {
                                  btns[i].addEventListener('keyup', function (e) {
                                      console.log(e.target.id);
                                      let textValues = $(`#${e.target.id}`).val()
                                      console.log("=============values =====", textValues)
                                      //on key press take id and set value of that id what i am inputting.
                                  });
                              }
                  

                  【讨论】:

                    【解决方案14】:

                    I like @Pikamander2 solution because it does not involve an event binding on document, or body (you will wake up someday with each click on document triggering dozens of event handlers...).

                    Here is an improvement of Pikamander2 solution.

                    If the dynamically added child is itself a dom element with children (ex: &lt;button&gt;&lt;u&gt;label&lt;/u&gt;&lt;/button&gt;, the e.target may return the &lt;u&gt; element. So you may use :

                    function delegateEvent( eventType, ancestorElem, childSelector, eventHandler ) {
                    
                        // Cancel if ancestorElem does not exists
                        if( ! ancestorElem || ( typeof ancestorElem === 'string' && ! ( ancestorElem = document.querySelector( ancestorElem ) ) ) ) {
                            return
                        }
                    
                      ancestorElem.addEventListener( eventType, e => {
                        if( e.target && e.target.closest && e.target.closest( childSelector ) ) {
                          ( eventHandler )( e )
                        }
                      } )
                    
                    }
                    

                    I also added a sn-p to make the function accept a selector for ancestor instead of an element only

                    【讨论】:

                      【解决方案15】:

                      This is an old question - but I spent a lot of hours with this problem. Maybe my solution helps someone.

                      Context: Attaching event listeners to elements dynamically created from an Ajax response. Making an selectable by click, or by keyboard navigation: In both cases, the same function is called.

                      The first line worked, the second line did not allow me to click on the item:

                      item.addEventListener("mouseover", this.mouseOver.bind(this) )
                      item.addEventListener("click", this.clickedItem.bind(this) )
                      

                      However, this continued to work:

                      item.click();
                      

                      (Suggesting the click event was added, and calling the function - but the mouse was not dispatching a click event)

                      The solution was to change :

                      item.addEventListener("click", this.clickedItem.bind(this) )
                      

                      to this:

                      item.addEventListener("mousedown", this.clickedItem.bind(this) )
                      

                      I saw on old SO message that suggested that &lt;div&gt;s do not respond to mouse click events - but all the documentation I read contradicted that.

                      So, I don't know what's going on here.

                      【讨论】:

                        猜你喜欢
                        • 2022-11-20
                        • 2023-02-25
                        • 2018-05-26
                        • 2022-12-15
                        • 2017-03-11
                        • 2022-12-27
                        • 2022-12-27
                        • 2019-06-27
                        • 1970-01-01
                        相关资源
                        最近更新 更多