2017-07-31 341 views
6

如果我点击第一个“编辑”我得到一个console.log('click happend')但如果我通过JavaScript添加这些框中的一个(点击“添加框”),然后编辑click从这新箱子不起作用。我知道这是因为javascript在元素不存在的情况下运行,这就是为什么没有click事件监听器的原因。我也知道用jQuery我能做到像这样:添加点击事件到元素插入javascript

$('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的元素调用eventListener(s),如new XXX.ClickMe(element);

+0

的可能的复制[什么是DOM事件代表团?](https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation ) –

回答

2

这里有一个方法,模仿$('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()的每个实例的弱引用,这允许事件delega通过仅为每个.edit元素初始化一个实例,然后通过静态方法ClickMe.get(element)在将来的委托点击上引用已创建的实例来有效进行委派。

弱引用允许ClickMe()的实例被垃圾收集,如果它的元素键从DOM中删除并且超出范围。

+0

谢谢Patrick,这对我有很大帮助! +1 – caramba

+1

@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>

+0

假设有一个缺失的空间'me.split(“”)[0] [HERE] =='edit''我不喜欢[这个小提琴](https://jsfiddle.net/noeuesps/1/ )。有时候我没有点击,有时候是两次。只是感觉不好,干净 – caramba

+0

是的,因为你需要调整你的代码。如果你使用这个代码与另一个,那么将有多个绑定点击 – Tushar

+0

@caramba我已经更新你的小提琴。 https://jsfiddle.net/bwveoyrz/。现在作品 – Tushar

2

做这样的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

+0

我也想知道这一点。它不适合你的'新的XXX.ClickMe(元素)'设计,但它应该适用于一些改变。 – PeterMader

+0

感谢您的帮助,现在我的问题是如果对象'XXX.ClickMe()'具有不同的属性,他们都丢失。所以我想调用或实例化'new XXX.ClickMe(element);' – caramba

+0

您不能同时使用事件委托和'XXX.ClickMe'。你当然可以有一个'XXX.ClickMe'数组,并且每当一个不在数组中的元素被点击时添加一个新数组。但我建议你在'addBox'中创建一个新的'XXX.ClickMe',而无需事件委托。 – PeterMader

0

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

将所需的所有功能包装在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>

+0

好吧,这不使用你请求的事件代表团。每个'.edit'都包含它自己的'this.element.addEventListener('click',onClick.bind(this))',所以你可以即时生成几个有作用域的函数。相比之下,我的答案使用事件委托在必要时创建'ClickMe'的实例,但不为每个事件监听器绑定一个事件监听器,而是从单个委托事件监听器调用其成员方法。 –

+0

@PatrickRoberts感谢您检查和评论!非常感激! – caramba

相关问题