0

When I try to bind a class method to an event, I get a error "Cannot read property 'bind' of undefined." This tells me that the object that I'm binding to is undefined, yet it does exist and can be called. Consider the following code:

someClass = ({

    event: function(workObject, event, action) {
        workObject.addEventListener(event, window[action].bind(null, arguments[3]));
    },

    methodEventHandler: function(param) {
        // Do Something
    },
});


function someEventHandler(param) {
    // Do Something
}

Now when I try to bind the event listener like this:

someClass.event(workObject, "click", "someEventHandler", someParameter);

It works. However, when I try to bind it this way:

someClass.event(workObject, "click", "someClass.methodEventHandler", someParameter);

It fails with the afore mentioned error. My question is why? I think it has something to do with scope since the regular function is in the global context and works but the method, which is not in the global scope, fails. I fixed this using a wrapper function, but I would really like to know why it is exhibiting this behavior. I'm doing it this way so I can bind event handlers using the function name as a string. Makes it easier to deal with when generating web pages dynamically. The only thing that I can think of is that the name of the event handler needs to be in a certain format, but I'm not sure what that looks like. Any insights?

I have dome some research on this and I found lots of questions and answers about how to bind handlers to events and such using various frameworks like jQuery, AngularJS, and Node.js. Some of those are here:

How to access the correct `this` inside a callback?

Bind click event to open a link in new tab

Bind a click event to a method inside a class

Daniel Rudy
  • 1,411
  • 12
  • 23
  • 2
    I don't think your problem is to do with `this` binding - it's much simpler. Your failing second example ends up trying to read a `bind` property of `window["someClass.methodEventHandler"]` which is not the same thing at all as `window.someClass.methodEventHandler`. You'll either need to write a function to dynamically access nested properties of an object from a dot-separated string and use that - or almost certainly there's a better way to achieve what you actually want. (In other words, this looks like an [XY problem](https://www.perlmonks.org/?node=XY+Problem).) – Robin Zigmond May 06 '21 at 22:26
  • @RobinZigmond I wouldn't call it an XY problem since it does work for the most part. As I said, I figured it was a scoping issue. I'm doing this way so event bindings can be handled dynamically as the JSON is parsed and the page is rendered. As I mentioned, I used a wrapper function to solve the problem. But, as you pointed out, the two are not the same. I don't know why I didn't see that earlier. – Daniel Rudy May 07 '21 at 02:21

2 Answers2

1

someEventHandler is a Global function and so it can be called as window["someEventHandler"], as someEventHandler becomes a property of window.

But someClass.methodEventHandler isn't Global and so window["someClass.methodEventHandler"] doesn't return anything because there isn't a window.someClass.methodEventHandler property and therefore .bind() can't be called on it.

You could call: window["someClass"].methodEventHandler.

someClass = ({

    event: function(workObject, event, action) {
        workObject.addEventListener(event, action.bind(null, arguments[3]));
    },

    methodEventHandler: function(param) {
        console.log(param);
    },
});

function someEventHandler(param) {
    // Do Something
}

const workObject = document.querySelector("div");
someClass.event(workObject, "click", window["someClass"].methodEventHandler, "x");
<div>Click Me</div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • 1
    Well, while `someClass` might not exist on the global object, the glaring issue at hand is that `window["someClass.methodEventHandler"]` and `window.someClass.methodEventHandler` are not the same. – MinusFour May 06 '21 at 22:33
  • @MinusFour Actually, `someClass` does exist as a Global, but as you say (and as I pointed out), `"someClass.methodEventHanlder"` is being passed as a single property and not a property of a property. That's why I said that you could call: `window["someClass"].methodEventHandler`. – Scott Marcus May 06 '21 at 22:36
1

String can be problematic and are harder to debug but parsing it is one option to continue working with strings. In addition to the string issues, you need to use var since let and const not becoming part of the window object.

const someParameter = "placeholder";

var someClass = ({ // note the use of `var` :/

  event: function(workObject, event, action) {
    const func = action.split(".")
      .reduce((obj, name) => obj[name], window); // walk down the window object

    workObject.addEventListener(event, func.bind(null, arguments[3]));
  },

  methodEventHandler: function(param) {
    console.log("I am methodEventHandler");
  },
});


function someEventHandler(param) {
  console.log("I am someEventHandler");
}

const workObject = document.querySelector("button"); // just the button

someClass.event(workObject, "click", "someEventHandler", someParameter);

someClass.event(workObject, "click", "someClass.methodEventHandler", someParameter);
<button>Click me for stuff</button>
Philip Rollins
  • 1,271
  • 8
  • 19
  • I like your solution. Does it work with more than two levels of properties? Something like someClass.someSubClass.someMethod? – Daniel Rudy May 07 '21 at 02:25
  • Yes it's recursive so any number of sublevels are possible. – Philip Rollins May 07 '21 at 02:26
  • It's going to take me a few days to get back around to that code as I'm working on other things. But if it works in my use case, I will mark your solution as the answer. – Daniel Rudy May 11 '21 at 05:54
  • I finally got around to testing this. It will call the function in question, but the context is in the node being clicked. When I do console.log(this) inside the handler method, I get the button control instead of the object that the handler lives in. – Daniel Rudy Jul 24 '21 at 19:31