Búsqueda de sitios web

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=&quot;box&quot;><a class=&quot;edit&quot; href=&quot;#&quot;>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=&quot;box&quot;><a class=&quot;edit&quot; href=&quot;#&quot;>Edit</a></div>">Add box</span>