Agregar evento de clic al elemento insertado por JavaScript
Si hago clic en el primer "Editar" obtengo un console.log('click sucedió'). Pero si agrego una de estas cajas vía JavaScript (haciendo clic en "Agregar caja") entonces el click de Editar de esta nueva caja no funciona. Sé que es porque el JavaScript se ejecutó cuando el elemento no estaba allí y por eso no hay un listener de evento click. También sé que con jQuery podría hacer algo así:
$('body').on('click', '.edit', function(){ // do whatever };
y eso funcionaría.
¿Pero cómo puedo hacer esto con Javascript puro? No pude encontrar ningún recurso útil. Creé un ejemplo simple que me gustaría que funcionara. ¿Cuál es la mejor manera de resolver esto?
Entonces el problema es: si agregas una caja y luego haces clic en "Editar", no pasa nada.
var XXX = {};
XXX.ClickMe = function(element){
this.element = element;
onClick = function() {
console.log('click happend');
};
this.element.addEventListener('click', onClick.bind(this));
};
[...document.querySelectorAll('.edit')].forEach(
function (element, index) {
new XXX.ClickMe(element);
}
);
XXX.PrototypeTemplate = function(element) {
this.element = element;
var tmpl = this.element.getAttribute('data-prototype');
addBox = function() {
this.element.insertAdjacentHTML('beforebegin', tmpl);
};
this.element.addEventListener('click', addBox.bind(this));
};
[...document.querySelectorAll('[data-prototype]')].forEach(
function (element, index) {
new XXX.PrototypeTemplate(element);
}
);[data-prototype] {
cursor: pointer;
}<div class="box"><a class="edit" href="#">Edit</a></div>
<span data-prototype="<div class="box"><a class="edit" href="#">Edit</a></div>">Add box</span>JSFiddle aquí
Esta sesión de preguntas y respuestas es información útil pero no responde a mi pregunta sobre cómo resolver el problema. Por ejemplo, ¿cómo puedo invocar el(los) eventListener(s) como new XXX.ClickMe(element); para esos elementos insertados dinámicamente en el DOM?
Respuesta #1
Aquí hay un método que imita $ ('body').on('click', '.edit', function() { ... }):
document.body.addEventListener('click', function (event) {
if (event.target.classList.contains('edit')) {
...
}
})
Incorporando eso en tu ejemplo (que modificaré un poco):
var XXX = {
refs: new WeakMap(),
ClickMe: class {
static get (element) {
// if no instance created
if (!XXX.refs.has(element)) {
console.log('created instance')
// create instance
XXX.refs.set(element, new XXX.ClickMe(element))
} else {
console.log('using cached instance')
}
// return weakly referenced instance
return XXX.refs.get(element)
}
constructor (element) {
this.element = element
}
onClick (event) {
console.log('click happened')
}
},
PrototypeTemplate: class {
constructor (element) {
this.element = element
var templateSelector = this.element.getAttribute('data-template')
var templateElement = document.querySelector(templateSelector)
// use .content.clone() to access copy fragment inside of <template>
// using template API properly, but .innerHTML would be more compatible
this.template = templateElement.innerHTML
this.element.addEventListener('click', this.addBox.bind(this))
}
addBox () {
this.element.insertAdjacentHTML('beforeBegin', this.template, this.element)
}
}
}
Array.from(document.querySelectorAll('[data-template]')).forEach(function (element) {
// just insert the first one here
new XXX.PrototypeTemplate(element).addBox()
})
// event delegation instead of individual ClickMe() event listeners
document.body.addEventListener('click', function (event) {
if (event.target.classList.contains('edit')) {
console.log('delegated click')
// get ClickMe() instance for element, and create one if necessary
// then call its onClick() method using delegation
XXX.ClickMe.get(event.target).onClick(event)
}
})[data-template] {
cursor: pointer;
}
/* compatibility */
template {
display: none;
}<span data-template="#box-template">Add box</span>
<template id="box-template">
<div class="box">
<a class="edit" href="#">Edit</a>
</div>
</template>Esto usa WeakMap() para mantener referencias débiles a cada instancia de ClickMe(), lo que permite que la delegación de eventos se haga de manera eficiente inicializando solo una instancia para cada elemento .edit, y luego haciendo referencia a la instancia ya creada en futuros clics delegados a través del método estático ClickMe.get(element).
Las referencias débiles permiten que las instancias de ClickMe() sean recolectadas por el recolector de basura si su clave de elemento se elimina del DOM y queda fuera de alcance.
Respuesta #2
Puedes hacer algo así...
document.addEventListener('click',function(e){
if(e.target && e.target.className.split(" ")[0]== 'edit'){
new XXX.ClickMe(e.target);}
})
var XXX = {};
XXX.ClickMe = function(element) {
this.element = element;
this.element.addEventListener('click', onClick.bind(this));
};
XXX.PrototypeTemplate = function(element) {
this.element = element;
var tmpl = this.element.getAttribute('data-prototype');
addBox = function() {
this.element.insertAdjacentHTML('beforebegin', tmpl);
};
this.element.addEventListener('click', addBox.bind(this));
};
[...document.querySelectorAll('[data-prototype]')].forEach(
function(element, index) {
new XXX.PrototypeTemplate(element);
}
);
document.addEventListener('click', function(e) {
if (e.target && e.target.className.split(" ")[0] == 'edit') {
console.log('click happend');
}
})[data-prototype] {
cursor: pointer;
}<div class="box"><a class="edit" href="#">Edit</a></div>
<span data-prototype="<div class="box"><a class="edit" href="#">Edit</a></div>">Add box</span>