'How to bind an event callback passed as a string parameter?

I need to dynamically pass in a function name that will be added as an onclick event handler as part of a dynamic UI creator. Most of the function is easy but I can't work out how to turn the string function name into the function that is bound to the event handler.

I've tried things like:

// Add event handlers
Object.keys(compToAdd.events).forEach( (type) => {
    newEl.addEventListener( type, Function.prototype.bind( compToAdd.events[type] ) )
})

But that doesn't work.

Also tried:

window.mycb = function() {
    console.log('>>>> hello >>>>')
}
// ...
Object.keys(compToAdd.events).forEach( (type) => {
    newEl.addEventListener( type, window['mycb'] )
})

Using window['mycb']() immediately executes the fn when applied to the event listener which is obviously not correct. Without (), nothing happens when the click event fires.



Solution 1:[1]

A simplest and arguably best approach would be loading all your callback functions into a single object, versus create them in global scope:

const compToAdd =
{
  events:
  {
    click: "onClick",
    mousedown: "onMouseDown",
    mouseup: "onMouseUp",
  }
}


const myCallbacks = 
{
  onClick: function(e)
  {
    console.log("onClick type:", e.type)
  },

  onMouseDown: function(e)
  {
    console.log("onMouseDown type:", e.type)
  },

  onMouseUp: "this is not a function"
}
// ...
Object.keys(compToAdd.events).forEach( (type) => {
    const callback = myCallbacks[compToAdd.events[type]];

    if (callback instanceof Function) //make sure it's a function
      newEl.addEventListener( type, callback )
})
<div id="newEl" style="height: 100vh">click here</div>

P.S. your second example works fine though. If it executed without () it means something triggered the event.

Solution 2:[2]

I have managed to find a couple of possible answers. However, I don't know that I like either of them and I am sure there are better ones.

  1. Using eval
        // Add event handlers
        Object.keys(compToAdd.events).forEach( (type) => {
            // Add the event listener - hate eval but it is the only way I can get it to work
            try {
                newEl.addEventListener( type, (evt) => {
                    eval(`${compToAdd.events[type]}(evt)`)
                } )
            } catch (err) {
                console.error(`Add event '${type}' for element '${compToAdd.type}': Cannot add event handler. ${err.message}`)
            }
        })
  1. Using setAttribute instead of addEventListener
        // Add event handlers
        Object.keys(compToAdd.events).forEach( (type) => {
            if (type.toLowerCase === 'onclick' || type.toLowerCase === 'click') type = 'click'
            // Add the event listener
            try {
                newEl.setAttribute( type, `${compToAdd.events[type]}()` )
            } catch (err) {
                console.error(`Add event '${type}' for element '${compToAdd.type}': Cannot add event handler. ${err.message}`)
            }
        })

I'm preferring (1) since it seems a bit more flexible and allows the use of pre-defined functions in any context reachable from the context when setting the event listener. But it is far from ideal.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 vanowm
Solution 2 Julian Knight