4

I have a select menu like this:

HTML

<select name="moduleGroup">
    <option data-function="getListing('pages')">Pages</option>
</select>

Javascript

function getListing(s) {
    console.log(s);
}

$('select').on('change', function() {
    var func = $(this).find('option:selected').attr('data-function');
    [func]();
});

I've also tried, $[func]();, $('body')[func]();

I have read multiple posts about the same question, but none help me: here, and here

I'm sure I'm doing something wrong, but can someone point me in the right direction please?

Community
  • 1
  • 1
Shannon Hochkins
  • 11,763
  • 15
  • 62
  • 95

1 Answers1

2

The very easiest way is to call eval() like this:

$('select').change(function() {
    var func = $(this).find('option:selected').attr('data-function');
    eval(func);
});

Fiddle: http://jsfiddle.net/gywzb/4/

But the entire world agrees that this is a bad practice. As my favorite linter says, "eval() is evil." It opens you up to lots of nasty security holes (especially as you maintain the app and things change from how you initially wrote it), and the performance is abysmal anyway. Fortunately, there is a better way.

First, I'm going to break up your data attributes a little bit, so that instead of having one attribute that contains the function name and parameter, you'll have two attributes: one for the function name, and another for the parameter.

<select name="moduleGroup">
    <option data-function="getListing" data-parameter="pages">Pages</option>
</select>

Second, we'll create a "class" that wraps your functions, like this:

var MyClass = function() {
}

MyClass.prototype = {
    getListing: function(s) {
        console.log(s);
    }
};

Finally, here's how you use the above changes to call your function:

$('select').on('change', function() {
    var func = $(this).find('option:selected').attr('data-function');
    var param = $(this).find('option:selected').attr('data-parameter');

    var myInstance = new MyClass();
    var funcRef = myInstance[func];
    funcRef.call(myInstance, param);
});

Here's the whole thing in a fiddle: http://jsfiddle.net/7CwH4/2/

The magic happens when you use the .call method to call your function. The .call() method's first parameter is the object the function will see as its this object. The next parameter to .call is the first parameter to the function. (And if your function took more parameters, the third parameter to .call would be the second parameter to the function, etc. See the MDN documentation for .call if this is confusing.)

If you need to pass more complicated data than a string as the parameter, you can put it in the data-parameter attribute as a JSON string, then use $.parseJSON to convert it into an object before passing the parameter to the function:

// If your data-parameter attribute contained a JSON string.
$('select').on('change', function() {
    var func = $(this).find('option:selected').attr('data-function');
    var param = $(this).find('option:selected').attr('data-parameter');

    var paramObj = $.parseJSON(param);

    var myInstance = new MyClass();
    var funcRef = myInstance[func];
    funcRef.call(myInstance, paramObj);
});
Katie Kilian
  • 6,815
  • 5
  • 41
  • 64
  • Wow thankyou for this very detailed response! As this doesn't really pose any threats as it's just for myself, this will work just fine with `eval` but thankyou again! – Shannon Hochkins Mar 13 '14 at 04:26
  • 1
    Truthfully, the code to do it the "right" way isn't much more complicated than the code to do it with `eval`. You might check out the fiddles -- you'd hardly notice the difference if I hadn't gone on at length explaining the changes. (Though I totally get not wanting to jump into the details of this problem when you have a simple solution while other problems are calling, too.) – Katie Kilian Mar 13 '14 at 04:32