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 e
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)
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.
There is a workaround by capturing clicks on document.body
and then checking event target.
document.body.addEventListener( 'click', function ( event ) {
if( event.srcElement.id == 'btnSubmit' ) {
someFunc();
};
} );
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
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!
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.