【问题标题】:Add click event to element inserted by javascript将点击事件添加到由 javascript 插入的元素
【发布时间】:2017-07-31 14:01:05
【问题描述】:

如果我点击第一个“编辑”,我会得到一个 console.log('click happend') 但如果我通过 javascript 添加其中一个框(点击“添加框”),然后从这个新框中编辑 click 不会工作。我知道这是因为 javascript 在元素不存在时运行,这就是为什么没有单击事件侦听器的原因。我也知道使用 jQuery 我可以这样做:

$('body').on('click', '.edit', function(){ // do whatever };

这样就行了。

但是如何使用纯 Javascript 来做到这一点?我找不到任何有用的资源。创建了一个我想工作的简单示例。解决这个问题的最佳方法是什么?

所以问题是:如果您添加一个框,然后单击“编辑”,则没有任何反应。

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 here

This Q/A is useful information 但它没有回答我关于如何解决问题的问题。就像我如何为那些动态插入 DOM 的元素调用像 new XXX.ClickMe(element); 这样的 eventListener?

【问题讨论】:

标签: javascript event-delegation


【解决方案1】:

这是一个模仿$('body').on('click', '.edit', function () { ... })的方法:

document.body.addEventListener('click', function (event) {
  if (event.target.classList.contains('edit')) {
    ...
  }
})

将其应用于您的示例(我将对其进行一些修改):

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>

这使用WeakMap() 来保存对ClickMe() 的每个实例的弱引用,这允许事件委托通过仅为每个.edit 元素初始化一个实例来有效地委托,然后在未来引用已经创建的实例通过静态方法ClickMe.get(element)委托点击。

如果 ClickMe() 的元素键曾经从 DOM 中删除并超出范围,弱引用允许对实例进行垃圾回收。

【讨论】:

  • @caramba 实际上我刚刚意识到我没有使用事件委托,只是在其中工作,查看编辑
【解决方案2】:

你可以做这样的事情......

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>

【讨论】:

  • 假设缺少空格me.split(" ")[0][HERE]== 'edit' 我不喜欢this fiddle。有时我没有点击,有时两个。只是感觉不太干净
  • 是的,因为您需要调整代码。如果您将此代码与另一个代码一起使用,那么将会有多次点击绑定
  • @caramba 我还通过运行代码更新了我的答案。
  • 谢谢,但我仍然在纠结如何调用new XXX.ClickMe(element);
【解决方案3】:

像 jQuery 那样做:有一个控制事件委托的父元素。在下文中,我使用document.body 作为父级:

document.body.addEventListener('click', e => {
  if (e.target.matches('.edit')) {
    // do whatever 
  }
});

工作示例:

var XXX = {};
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));
};


new XXX.PrototypeTemplate(document.querySelector('[data-prototype]'));

document.body.addEventListener('click', e => {
  if (e.target.matches('.edit')) {
    // do whatever
    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>

看看 MDN 对Element.prototype.matches 的评价。

【讨论】:

  • 我也想知道。它不适用于您的 new XXX.ClickMe(element); 设计,但应该进行一些更改。
  • 感谢您的帮助,现在我的问题是如果对象 XXX.ClickMe() 具有不同的属性,它们都会丢失。所以我想调用或实例化new XXX.ClickMe(element);
  • 您不能同时使用事件委托和XXX.ClickMe。您当然可以拥有一个 XXX.ClickMes 数组,并在单击不在数组中的元素时添加一个新元素。但我建议你在addBox 中创建一个新的XXX.ClickMe,而不使用事件委托。
  • 这听起来像我想要的。只是不知道该怎么做。此外,一个盒子内还有很多链接,因此在插入新盒子后有一种方法可以再次“运行”javascript或在一组给定的函数上“运行” javascript...
【解决方案4】:

感谢所有对问题的回答,这都是有用的信息。以下内容呢:

将所有需要的函数包装在 XXX.InitializeAllFunctions = function(wrap) {} 中,并将 document 作为第一页加载时的包装传递。所以它的行为就像以前一样。当插入新的 DOM 元素时,只需在插入 DOM 之前将它们也传递给此函数。像魅力一样工作:

var XXX = {};

XXX.ClickMe = function(element){
    this.element = element;
    onClick = function() {
        console.log('click happend');
    };
    this.element.addEventListener('click', onClick.bind(this));
};

XXX.PrototypeTemplate = function(element) {
    this.element = element;

    addBox = function() {
        var tmpl = this.element.getAttribute('data-prototype');
        var html = new DOMParser().parseFromString(tmpl, 'text/html');

        XXX.InitializeAllFunctions(html);  // Initialize here on all new HTML
                                           // before inserting into DOM

        this.element.parentNode.insertBefore(
            html.body.childNodes[0],
            this.element
        );
    };

    this.element.addEventListener('click', addBox.bind(this));
};

XXX.InitializeAllFunctions = function(wrap) {

    var wrap = wrap == null ? document : wrap;

    [...wrap.querySelectorAll('[data-prototype]')].forEach(
        function (element, index) {
            new XXX.PrototypeTemplate(element);
        }
    );

    [...wrap.querySelectorAll('.edit')].forEach(
        function (element, index) {
            new XXX.ClickMe(element);
        }
    );
};

XXX.InitializeAllFunctions(document);
[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>

【讨论】:

  • 好吧,这并没有像您要求的那样使用事件委托。每个.edit 都包含自己的this.element.addEventListener('click', onClick.bind(this)),因此您正在动态生成多个作用域函数。相比之下,我的答案在必要时使用事件委托创建 ClickMe 的实例,但不会为每个实例绑定事件侦听器,而是从单个委托事件侦听器调用其成员方法。
  • @PatrickRoberts 感谢您回来查看和评论!非常感谢!
猜你喜欢
  • 2013-06-07
  • 2016-10-31
  • 1970-01-01
  • 2012-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-18
  • 1970-01-01
相关资源
最近更新 更多