0

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.

Julian Knight
  • 4,716
  • 2
  • 29
  • 42
  • You'll need create your functions either in a global scope, or in a local object, then you can do either `window[compToAdd.events[type]]` for global scope or `myFuncs[compToAdd.events[type]]` for local object. – vanowm Apr 16 '22 at 12:10
  • Thanks @vanowm but that doesn't work either. It finds the function but doesn't execute it on click. – Julian Knight Apr 16 '22 at 12:29
  • https://stackoverflow.com/questions/65621481/javascript-addeventlistener-from-a-given-function-string-name-as-an-argument – Robin Mackenzie Sep 26 '22 at 09:35
  • @RobinMackenzie, that is not the same issue as the one I was facing. The CODE itself here is passed as a string from a back-end tool to the front end. This is done over websockets as text. So it isn't just the function _name_ that is a text variable but the code itself. In the end, I think I went a different route altogether. – Julian Knight Sep 26 '22 at 18:51

2 Answers2

0

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.

vanowm
  • 9,466
  • 2
  • 21
  • 37
  • Thanks again. Unfortunately, I cannot pre-define anything since the actual definitions will be sent dynamically from a server post page-load. I have two other - fairly nasty solutions that I've managed to work through. They are not ideal though. – Julian Knight Apr 16 '22 at 14:02
0

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.

Julian Knight
  • 4,716
  • 2
  • 29
  • 42