Search code examples
javascriptevent-handlingdom-eventsevent-delegation

Cannot removing an event from my own event delegation system


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:

Markup

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

Javascript

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'
});

Solution

  • 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.