12

Is it possible to write an html attribute that will store the name of a javascript function and then extract that val() and execute that function? Example:

<button id="example" data-function-name="showAllElements()">

and then in js/jq

fn = $('#example').data('function-name');

fn;
Jarryd Goodman
  • 477
  • 3
  • 9
  • 19

3 Answers3

25

You can, yes. Whether you should is another question entirely, to which the answer is almost certainly "no" (in terms of executing the string; in terms of the alternative shown below, there are times it's useful).

The way you'd evaluate that snippet of code (what you have isn't just a function name, because of the ()) would be to use the dreaded eval:

eval(fn);

There's almost always a better option than using eval. (See below.)

eval example:

$("#example").on("click", function() {
  var fn = $("#example").attr("data-function-name");
  eval(fn);
});

function showAllElements() {
  alert("showAllElements was called");
}
<button type="button" id="example" data-function-name="showAllElements()">Click Me</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

One of the better options is to store your function references as properties on an object, and then use brackets notation to get the function reference based on the function name:

Example:

var functions = {
  showAllElements: function() {
    alert("showAllElements was called");
  }
};

$("#example").on("click", function() {
  var fn = $("#example").attr("data-function-name");
  functions[fn]();
});
<button type="button" id="example" data-function-name="showAllElements">Click Me</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Note that there, I'm just storing the function name, not arbitrary code.

Update: See canon's answer for a clever way to handle it if you have your functions nested inside an object, e.g. mumble.foo.doSomething, using reduce. (reduce is an ES5 feature, but it's polyfillable.)


Side note: Unless you're doing something more than just retrieving the value of a data-* attribute, don't use data, use attr. data initializes a data cache for the element, reads in all of the data-* attributes for that element, and copies them to cache. If you're not using it, there's no reason to do that. The idea that you use data to access data-* attributes is a common misconception.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Thanks I really like your suggestions! Also, could you explain to me a situation where you would use .data()? – Jarryd Goodman Dec 12 '14 at 16:39
  • I was, sadly, misusing jQuery's [`data()`](http://api.jquery.com/jquery.data/) for a while... thinking it was a wrapper/polyfill for [`dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.dataset). – canon Dec 12 '14 at 18:11
  • 1
    @canon: You and a **lot** of other people. :-) I keep thinking how, in some alternate universe, the API could have made that more clear, because people get misled by it. But I haven't come up with anything, and the cat's out of the bag now anyway... – T.J. Crowder Dec 12 '14 at 18:21
12

Sure... assuming that showAllElements is global...

function showAllElements() {
  console.log("test!");
}
document.querySelector("button").addEventListener("click", function(e) {
  var functionName = this.getAttribute("data-function-name");
  window[functionName]();
});
<button id="example" data-function-name="showAllElements">x</button>

Now, let's say your attribute is actually something harrier, like: foo.bar.showAllElements...

var foo = {
  bar: {
    showAllElements: function() {
      console.log("test!");
    }
  }
};

document.querySelector("button").addEventListener("click", function(e){
    var functionName = this.getAttribute("data-function-name");
    // Split by "." and resolve each segment starting at the window. Invoke with ()
    functionName.split(".").reduce((o,n) => o[n], window)();
});
<button id="example" data-function-name="foo.bar.showAllElements">x</button>
canon
  • 40,609
  • 10
  • 73
  • 97
  • 1
    Nice, didn't think of that! – Bill Dec 12 '14 at 16:23
  • Only works for global functions, of course, and global functions, like all globals, are a Bad Idea. – T.J. Crowder Dec 12 '14 at 16:28
  • 1
    @T.J.Crowder - No, it would work for any object as long as you have a reference to it. It's just looking up a function by it's name and then calling it. Nothing magic about that. Could just as easily be: `({ foo: function(){ console.log('bar'); } })['foo']();`. No global functions there. – JDB Dec 12 '14 at 16:32
  • @JDB: canon's answer only works for global functions, because it relies on using `window` (unless we're going to multiply uncertainties and assume `window` has been shadowed). My answer addresses what you're talking about (the very handy practice of having a map of functions you retrieve using a string). – T.J. Crowder Dec 12 '14 at 16:34
  • Right -- as I demonstrate in my answer. I'm just saying that if you use `window`... – T.J. Crowder Dec 12 '14 at 17:44
  • 1
    I realized I'd left it out as a hit Post. Didn't see any other answers. Yours may well have been there, but that's not why I added it. I just realized I'd committed the sin of telling the OP how to do it with `eval`, telling them they probably shouldn't, and *not giving them the alternative*! :-) – T.J. Crowder Dec 12 '14 at 18:05
  • 1
    Ooooh, +1 for the clever use of `reduce`! – T.J. Crowder Dec 12 '14 at 18:07
0

While I don't think you can do it this way with an HTML attribute directly (except with global functions as mentiopned above), if you can add the data element with javascript at page load you can do it directly. As said here, jquery's $.data() is not the same as the data-* attributes. According to the jQuery docs, the value stored using .data() "The new data value; this can be any Javascript type except undefined."

So you can store a function directly using

function my_func(){
   // Do stuff
}
$(function(){
   $('#foo').data('func', my_func);
});

And then later

var func = $('#foo').data('func');
if( $.isFunction( func ) ) {
   func.call( $('#foo'), somearg );
}

I have an example as a fiddle.

This has the advantage that you can have the functions in enclosures, etc. as long as you add them to the elements within the same enclosure scope.

Adam
  • 6,539
  • 3
  • 39
  • 65