1

I set about asking this question wrongly, so I am starting again, beginning with the specifics of the issue I am wrestling with, rather than the general features of the issue.

I have an object in which the values are strings. They pretty much need to be strings because they are extracted from HTML5 custom data-* attributes and I really don't want to start writing javascript inside HTML attributes, like it's 1999.

Here's an example of one of the custom data-* attributes:

data-event="{«eventListener» : «transitionend», «eventAction» : «showText»}"

As you can see, it's basically a JSON string, slightly modified, so that it can take the form of an HTML attribute value.

I would like to be able to parse that custom data-* attribute and translate it into the following javascript:

parentElement.addEventListener('transitionend', showText, false);

To do this, I need to:

  1. Replace the « and » characters with the " character
  2. JSON.parse the resulting value into an object
  3. Create the useCapture parameter if it doesn't exist (as in this example)
  4. Then it's straightforward (almost) for me to create the addEventListener statement

In fact - of course - I end up with this statement:

parentElement.addEventListener('transitionend', 'showText', false);

which doesn't work because I can't invoke 'showText' - it's just a string, it doesn't point to a function.

The easiest way to fix this is to create this statement, instead:

parentElement.addEventListener('transitionend', eval('showText'), false);

which is equivalent to:

parentElement.addEventListener('transitionend', showText, false);

The only thing that's holding me back is my uncertainty over whether eval() should really never be used or whether it should simply mostly be avoided - and, despite normal warnings, this unusual situation is an acceptable situation in which to deploy eval().


Question: Is it misguided or inadvisable in Javascript to take a string which happens to be the name of a function and to access and execute that function by using eval() on the string?

If it is inadvisable, what is the alternative, when I know the name of a function and I wish to insert it into an event handler?

If I'm better off avoiding this:

parentElement.addEventListener('transitionend', eval('showText'), false);

what should I be using instead?

Or... is it entirely acceptable to use eval() in some situations - and this is one of them?

Rounin
  • 27,134
  • 9
  • 83
  • 108
  • Why do you need `eval` here. why not use `[]` dynamic property access if the string is not know before, and if it is know then you can invoke directly `obj.property()` – Code Maniac Oct 03 '19 at 16:21
  • 1
    See the linked question's answers for details, but basically: `const myObjectOfFunctions = { function1 : myFunction, function2 : yourFunction }; myObjectOfFunctions.function1();` or don't rename them: `const myObjectOfFunctions = { myFunction, yourFunction }; myObjectOfFunctions.myFunction();`. If you need a dynamic name: `const name = "myFunction"; myObjectOfFunctions[name]();` – T.J. Crowder Oct 03 '19 at 16:25
  • In the comment above, the name `name` would come from the attribute, for instance. – T.J. Crowder Oct 03 '19 at 16:26
  • 1
    @Rounin The answer is still the same. Put your event listeners in an object and refer to them by property name. Yes, it is inappropriate to use `eval` for this. – Bergi Oct 03 '19 at 18:15
  • Thank you, @Bergi. I'm starting to think I just don't understand what people are trying to tell me. Let me try to get my head around it again. I have an object with keys and string values: `const myObject = {eventListener: "transitionend", eventAction: "showText"}`. If I refer by property name eg. `myObject.eventAction` or `myObject['eventAction']` that gives me the string `showText`. Now what do I do with that string to turn it into the function `showText()`? – Rounin Oct 03 '19 at 18:21
  • 1
    No. You need an object where the actual `showText` function is stored, like `const myEventListeners = {showText: …};`. Then you can access *that* by the name from your JSON: `addEventListener(myObject.eventName, myEventListeners[myObject.eventAction]);` – Bergi Oct 03 '19 at 18:25
  • Ohhhh. I get it. Sorry everyone for being so slow. Now that I've grasped it this seems to me like it might be one of those approaches which is amazingly obvious in hindsight, but not always self-evident until it's been explained for the first time. I have declared functions in objects (in the _Revealing Module_ pattern, not least) but generally I declare them as `const` variables. I'll give this some thought. – Rounin Oct 03 '19 at 18:37

2 Answers2

1

If your function is in the global scope, this does it:

myElement.addEventListener('click', window[myObject.function1], false);

The square brackets are just an alternative syntax for the . - but they allow for variable property names.

Check this:

const obj = { a: 'Hello this is obj.a' };

const key = 'a';

console.log(obj.a);
console.log(obj['a']);
console.log(obj[key]);
connexo
  • 53,704
  • 14
  • 91
  • 128
  • This is an **often** repeated duplicate, it doesn't need Yet Another Answer, just a close vote and (if relevant) comment. – T.J. Crowder Oct 03 '19 at 16:21
  • Thanks. That's a good alternative. I'd not thought about the `window` object. I can't say 100% that the function will always be in the global scope, but if no-one comes up with any better suggestions, then your suggestion above will be the preferred solution. – Rounin Oct 03 '19 at 16:23
  • Ah, no. Sadly, `window[myObject.function1]` doesn't work. I think this is because `myObject` is defined using `let` and (as I've just learned) variables defined with `let` do not become a property of window. – Rounin Oct 03 '19 at 18:39
1

In response to the update, you will be better off placing all event handlers in an object instead of the global namespace, that is:

before:

function showText(event) {
   ...
}

function anotherHandler(event) {
   ...
}

after:

const myActions = {
   showText(event) {
      ...
   },
   anotherHandler(event) {
      ...
   }
}

And then, after you've parsed the data attribute and got something like this:

data = {"eventListener" : "transitionend", "eventAction" : "showText"}

bind the handler this way:

parentElement.addEventListener(
     data.eventListener,
     myActions[data.eventAction],
     false)

This not only gets rid of eval, but also helps organizing your code in a cleaner way.

georg
  • 211,518
  • 52
  • 313
  • 390
  • This is an **often** repeated duplicate, it doesn't need Yet Another Answer, just a close vote and (if relevant) comment. – T.J. Crowder Oct 03 '19 at 16:25
  • 1
    @Rounin: I don't understand what you're talking about. It would help if you edit the question and add an [example](https://stackoverflow.com/help/minimal-reproducible-example) of what you're trying to do. – georg Oct 03 '19 at 17:12
  • Yes, you're right, @georg. I've realised that I've gone about asking the question wrongly. I started at the most general, most simple level (which is generally good form for an SO question), but in this case, I should have started at the more specific, higher level. – Rounin Oct 03 '19 at 17:38
  • Thank you for your patience and perseverance, @georg - I got there in the end. – Rounin Oct 03 '19 at 18:42