Attach event to dynamic elements in javascript

Javascript

Javascript Problem Overview


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>

Javascript Solutions


Solution 1 - Javascript

This is due to the fact that your element is dynamically created. You should use event delegation to handle the event.

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

jquery makes it easier:

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

Here is an article about event delegation event delegation article

Solution 2 - Javascript

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

Solution 3 - Javascript

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)

Solution 4 - Javascript

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>

Solution 5 - Javascript

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

Solution 6 - Javascript

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

Solution 7 - Javascript

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

Solution 8 - Javascript

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.

Solution 9 - Javascript

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

Solution 10 - Javascript

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>

Solution 11 - Javascript

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!

Solution 12 - Javascript

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.

Solution 13 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManjuView Question on Stackoverflow
Solution 1 - JavascriptjilykateView Answer on Stackoverflow
Solution 2 - JavascriptManasov DanielView Answer on Stackoverflow
Solution 3 - JavascriptRichardView Answer on Stackoverflow
Solution 4 - JavascriptR3tepView Answer on Stackoverflow
Solution 5 - JavascriptPikamander2View Answer on Stackoverflow
Solution 6 - JavascriptPRDevingView Answer on Stackoverflow
Solution 7 - JavascriptparanjothiView Answer on Stackoverflow
Solution 8 - JavascriptXCSView Answer on Stackoverflow
Solution 9 - Javascriptcmd3BOTView Answer on Stackoverflow
Solution 10 - JavascriptAdnane ArView Answer on Stackoverflow
Solution 11 - JavascriptsstenView Answer on Stackoverflow
Solution 12 - Javascriptc zView Answer on Stackoverflow
Solution 13 - JavascriptSajid KhakiView Answer on Stackoverflow