0

I have the following code that defines a class for input events management: mouse, touch, pointer, ...

// base.js
export default () => {
  return {
    el: undefined,
    event: undefined,
    handler(ev) {
      console.log('default handler')
    },
    attach() {
      el.addEventListener(event, this.handler.bind(this), false)
    },
    detach() {
      el.removeEventListener(event, this.handler.bind(this), false)
    }
  }
}

// mouse.js
import baseInput from './base'

export default (el) => {
  return Object.assign(Object.create(baseInput()), {
    el: undefined,
    event: 'mousedown',
    handler(ev) {
      console.log('mouse handler)
    }
  }
}

There's some common business logic in 'base' object.

The problem comes from the calls to this.handler.bind(this) in attach and detach functions, because the returned bound function is not the same for each call, so removeEventListener can't remove the event listener added by addEventListener.

I know I have to keep one single function reference. My question is where that code should be.

One solution may be:

// base.js
export default () => {

  let boundFunction;

  return {
    el: undefined,
    event: undefined,
    getBoundFunction() {
      if (!boundFunction) {
        boundFunction = this.handler.bind(this)
      }
    },
    handler(ev) {
      console.log('default handler')
    },
    attach() {
      el.addEventListener(event, this.getBoundFunction(), false)
    },
    detach() {
      el.removeEventListener(event, this.getBoundFunction(), false)
    }
  }
}

This code works, but I don't want that extra getBoundFunction call for each event triggered and I think there should be a better method or best practice.

5
  • 1
    This is a very weird and somewhat wasteful way to do inheritance as you create a new base object, copy all its properties to a new object and then throw it way. Why are you doing inheritance this way? You can just extend the base object with your new properties/methods rather than copying and throwing it away. Commented Oct 7, 2017 at 15:47
  • There must be a typo in your first code block because in mouse.js, you have two consecutive return statements. Commented Oct 7, 2017 at 15:56
  • Yes. There's a typo. Thanks. Fixed! Commented Oct 7, 2017 at 16:08
  • About the inheritance, I wrote just the relevant lines to show the problem with bindings, but the base code is quite larger and is using some in-closure variables and methods. But I'll review it. Commented Oct 7, 2017 at 16:10
  • 1
    "I think there should be a better method or best practice." - the best practice for object creation with inheritance and initialisation code (which in your case would be this.handler = this.handler.bind(this)) is to use constructors and new. Of course you could do the same and just bind your handler method the factory function. Commented Oct 7, 2017 at 16:43

1 Answer 1

3

Implement the event listener interface by changing the name of handler to handleEvent. Then you just bind the object directly.

The value of this in the handleEvent function will be the bound object.

// base.js
export default () => {
  return {
    el: undefined,
    event: undefined,
    handleEvent(ev) {
      console.log('default handler')
    },
    attach() {
      el.addEventListener(event, this, false)
    },
    detach() {
      el.removeEventListener(event, this, false)
    }
  }
}

So then objects that inherit from the base object can define their own handleEvent function, which will get called when the bound events fire.


Also, you seem to be creating a new base object for each object that inherits from it. Why not share a base object? That's the point of prototypal inheritance.

Sign up to request clarification or add additional context in comments.

4 Comments

This is a seldom used trick. If you pass an object to addEventListener and removeEventListener, it looks for a special property name called handleEvent and calls it with the context of the original object. Odd, but works.
@jfriend00: You're right. I think people just don't know about it. It does a nice job of unifying business logic and objects with event handling. Makes for well-organized code, IMO.
Interesting tip! I didn't know about it. But the problem with the Event interface is that my code is part or a library and should support three ways of attaching events: el.addEventListener, el.attachEvent (IE10) and el["on{eventName}"]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.