I am trying to write my own event delegation system, and it works great, except that I can't remove the event once I attach it to an element! I've been tearing my hair out trying to figure this out. Any help would be greatly appreciated.
Code is in a pen: http://codepen.io/anon/pen/BjyZyV?editors=101
And also below:
<ul id="parent">
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
<li class="item">Lorum</li>
</ul>
Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
function isDescendant(parents, child) {
for (var i = 0; i < parents.length; i++) {
var node = child.parentNode;
while (node !== null) {
if (node === parents[i]) {
return true;
}
node = node.parentNode;
}
}
return false;
}
function eventCallback(e) {
if (e.target && e.target.matches(this.options.selector)) {
this.options.callback.call(this, e);
} else if (isDescendant(this.parent.querySelectorAll(this.options.selector), e.target)) {
this.options.callback.call(this, e);
}
}
var MyEvent = {
register: function register(options) {
this.parent = document.querySelector(options.parentSelector);
this.options = options;
this.parent.addEventListener(options.event, eventCallback.bind(this), false);
return this;
},
unregister: function unregister(options) {
this.parent = document.querySelector(options.parentSelector);
this.parent.removeEventListener(options.event, eventCallback, false);
return this;
}
};
myEvent = Object.create(MyEvent);
myEvent.register({
event: 'click',
parentSelector: '#parent',
selector: '.item',
callback: function(e) {
alert('clicked!');
}
});
myEvent.unregister({
event: 'click',
parentSelector: '#parent'
});
The issue is with bind()
, it returns a new function.
From the documentation
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
So everytime you call bind
you get a brand new function, for instance here
this.parent.addEventListener(options.event, eventCallback.bind(this), false);
it's the same as
var brandNewFunction = eventCallback.bind(this); // creates new function
this.parent.addEventListener(options.event, brandNewFunction, false);
So you're not passing the function eventCallback
at all, you're passing in a new function, hence it can't be removed with
this.parent.removeEventListener(options.event, eventCallback, false);
as you never passed in eventCallback
, and the functions has to be the same for removeEventListener
to be able to remove the listener.
The solution is of course to call it like this
this.parent.addEventListener(options.event, eventCallback, false);
and find some other clever way to pass your options etc.