2017-07-31 14 views
6

最初の「編集」をクリックするとconsole.log('click happend')が表示されますが、これらのボックスの1つをjavascript(「追加ボックス」をクリック)に追加してからclick新しいボックスは機能しません。要素がなかったときにjavascriptが実行され、クリックイベントリスナーが存在しないからです。私はまた、jQueryを使って私がそうすることができることを知っています。javacriptによって挿入された要素にクリックイベントを追加する

$('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を呼び出すことができますか?

+0

可能性のある重複した[DOMイベントの代表団は何ですか?](https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation:魔法のように動作します) –

答えて

2

ここで模倣$('body').on('click', '.edit', function() { ... })方法です:これはへWeakMap()を使用しています

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>

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

あなたの例にその作業は、(私は少しを変更します) ClickMe()の各インスタンスへの弱い参照を保持すると、イベントdelega .edit要素ごとに1つのインスタンスを初期化し、静的メソッドClickMe.get(element)を使用して、今後委任されたクリックで既に作成されたインスタンスを参照するだけで、効率的に委任できます。

弱い参照では、要素キーがDOMから削除されて範囲外になった場合、ClickMe()のインスタンスをガーベジコレクションすることができます。

+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/ )。時には私はクリックがなく、時には2つもありません。あなたのコードを微調整する必要があるので、ちょうど良いと清潔感がない – caramba

+0

はい。このコードを他のものと一緒に使用すると、クリック数が複数になることがあります。 – Tushar

+0

@caramba私はあなたのフィドルを更新しました。 https://jsfiddle.net/bwveoyrz/。 Works now – 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

関連する問題