2017-07-31 329 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

相關問題