205

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>
5
  • How are you creating the html? Commented Jan 20, 2016 at 9:29
  • 1
    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 Commented Jan 20, 2016 at 9:31
  • 2
    Move your addEventListener into the event listener of btnSubmit Commented Jan 20, 2016 at 9:34
  • 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. Commented Mar 21, 2021 at 20:46
  • Closely related: Vanilla JavaScript Event Delegation. Commented Jan 15, 2023 at 4:07

14 Answers 14

366

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 occurred anywhere inside the target element or is the target element itself. This is useful if, for example, instead of your <input id="btnPrepend"/> you had a <button id="btnPrepend"><i class="icon">+</i> prepend</button> and you clicked the <i class="icon">+</i>.

jQuery makes it easier:

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

Here is an article about event delegation.

Sign up to request clarification or add additional context in comments.

6 Comments

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
if you want to add the event listener to the children of the element also : stackoverflow.com/questions/16863917/…
|
48

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

1 Comment

Or nearest container
18

The DOM:

const iAmAString = '<li>hi</li>';

const iAmAnElement = document.createElement('li');

Elements have events. Strings just say things. Even if they have HTML in them.

What I like to do is create elements like this:

const myString = '<h1>hi there</h1>';
document.body.insertAdjacentHTML('beforeend', myString);

I am not sure if you can add an event though...like where would I do that? I don't know.

You can definitely do this:

const button = document.getElementById('btnSubmit');
button.addEventListener('click', function () {
  // Create the element
  const li = document.createElement('li');
  
  // Add the event listener
  li.onclick = function () {
    console.log('Now that you have an element reference you can add a click event like this!')
  }

  // Add the element to the DOM
  document.querySelector('ul').appendChild(li);
});

So this thing well call the DOM is a big thing. It has all these interesting functions. I like to type window in the Chrome console and explore. There are so many cool things in there!

The point is that a "string" is different than an "object." Objects are fun. Strings are boring.

2 Comments

Had something similar. Interesting to see this work. However, uncertain if works accidentally. Any documentation link explaining why this works?
Your code example lacks the important part of adding a listener, hence giving the false impression that somehow magically this is achieved by using document.createElement.
15

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>

3 Comments

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

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

Comments

4

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

Comments

3

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.

Comments

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

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

Comments

1

I have found the solution posted by jillykate works, 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.

Comments

0

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>

3 Comments

Do not modify objects you don't own.
@connexo It was an old solution! The answer was in 2019
That rule in OOP is probably 30 years old.
0

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!

Comments

0

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: <button><u>label</u></button>, the e.target may return the <u> 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 snippet to make the function accept a selector for ancestor instead of an element only

Comments

0

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

and 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.
    });
}

Comments

-1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.