[whatwg/dom] Adding a `context` property to the listener options. (#472)

Coming from [this thread](https://github.com/whatwg/dom/issues/208) without the intent to go off topic in there, but since it's being considered to add properties to the listener options I'd like to propose the following one:
```js
el.addEventListener(type, anyCallback, {context: anyObject});
```
The reason is: it should always be possible, holding a reference to the element, the callback, and the context, to also **remove** the listener at any time:
```js
el.removeEventListener(type, anyCallback, {context: anyObject});
```

### Rationale / Goal
While memory consumption wise there won't probably be any huge improvement, it is still a very common mistake to assume that if you add a callback listener as such:
```js
el.addEventListener(type, anyCallback.bind(anyObject));
```
it is later on possible to remove it in some way:
```js
// fail
el.removeEventListener(type, anyCallback.bind(anyObject));

// also fail
el.addEventListener(type, anyCallback);
```

### Behavior

  * if the callback with the specified context has not been set already, add the listener and once invoked do: `callback.call(context, event)`
  * if the callback is known, and the context was previously used, do nothing
  * if the callback is known, and the context is different, add the listener and once invoked do `callback.call(otherContext, event)`

Same logic would work to `removeEventListener` too.

### Implementation
The following code is just a Proof Of Concept. It's not a polyfill or an implementation, it just shows the logic behind this feature.
```js
(() => {

  const elwm = new WeakMap;

  Element.prototype.addEventListener =
    function (type, eventListener, options) {
      if (!elwm.has(this)) {
        elwm.set(this, Object.create(null));
      }
      const ref = elwm.get(this);
      if (!(type in ref)) {
        ref[type] = {
          listeners: [],
          contextes: []
        };
      }
      const handler = ref[type];
      let i = handler.listeners.indexOf(eventListener);
      if (i < 0) {
        i = handler.listeners.push(eventListener) - 1;
        handler.contextes[i] = [];
      }
      // if context is not provided (or no options at all)
      // the list of context will have one `null` value
      // this preservers the order in which listeners are added
      const context = options.context || null;
      if (!handler.contextes[i].includes(context)) {
        handler.contextes[i].push(context);
      }
    };

  Element.prototype.dispatchEvent =
    function (event) {
      if (elwm.has(this)) {
        const ref = elwm.get(this);
        if (event.type in ref) {
          const handler = ref[type];
          handler.listeners.forEach((listener, i) => {
            // invoke the listener callback
            // and use the context only if not null
            // if null, invokes it with the default context
            if (typeof listener === 'function') {
              handler.contextes[i].forEach(context =>
                // which is the element node for callbacks
                listener.call(context || this, event)
              );
            } else if ('handleEvent' in listener) {
              handler.contextes[i].forEach(context =>
                // and the object with the method otherwise
                listener.handleEvent.call(context || listener, event)
              );
            }
          });
        }
      }
    };

})();
```

Thanks in advance for eventually considering this proposal/idea.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/472

Received on Tuesday, 27 June 2017 23:11:50 UTC