34

We know such syntaxes as below, when defining a value for onClick attribute:

<button type="submit" onclick="alert('hi');"></button>
<button type="submit" onclick="doWork"></button> <!-- This one doesn't work -->
<button type="submit" onclick="doWork()"></button>
<button type="submit" onclick="doWork('Mike', 2)"></button>

What I'm interested in is to define a custom data-attribute and execute the value as follows:

<button type="submit" data-callback="alert('hi');"      class="marker"></button>
<button type="submit" data-callback="doWork"            class="marker"></button>
<button type="submit" data-callback="doWork()"          class="marker"></button>
<button type="submit" data-callback="doWork('Mike', 2)" class="marker"></button>

<script type="text/javascript">
    jQuery("body").on("click","button.marker", function(e) {
        var callback = jQuery(e.currentTarget).data("callback");

        // Now I need to execute the callback no matter of the format
        // 1. Execute as function's body
        // 2. Or by function 'name'
        // 3. Or by function 'name' with 0 parameters
        // 4. Or by function 'name' with n parameters
    })

    function doWork(name, nr){
        var personName = name || "Unnamed";
        var personNr = nr || 0;
        alert("Name is: " + personName + " Nr: " + personNr);
    }
</script>

I've pasted the sample to jsBin

How to accomplish same behaviour using custom data-attributes?

Cristian E.
  • 3,116
  • 7
  • 31
  • 61
  • 1
    You can have a look at the [devil](http://stackoverflow.com/questions/86513/why-is-using-the-javascript-eval-function-a-bad-idea) [eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) – Arun P Johny May 04 '15 at 10:55
  • Is there a reason your avoiding dynamically binding? Would simplify the issue. – Taplar May 04 '15 at 11:01
  • Unless you have a really good reason / are just experimenting, writing code inside a data attribute just seems like a bad idea! You can just set the callback on the buttons $(".marker").myCallback = doWork; Feel free to broaden my mind If you don't agree :) – Michiel Cornille May 04 '15 at 11:05
  • @Taplar Yes. The sample is simplified. In production code the callback is meant to be called by other components and at latter time. – Cristian E. May 04 '15 at 11:05
  • Still confused. Dynamic binding would allow that same interaction by the other components by invoking the bound action on the elements (ex. click, focus, etc..). Edit: You could also bind them as custom events so default event handlers would not invoke the code. Only your other components would know about them. – Taplar May 04 '15 at 11:06
  • Then you can still just set them as a js property instead of a data-attribute, (unless you plan to save these data-attributes in local storage, which I would also advise against..) – Michiel Cornille May 04 '15 at 11:06
  • @Mvision Regards the inline code totally agree. I'm more interested on calling by `function name` and `function name with args` – Cristian E. May 04 '15 at 11:07
  • 1
    @Mvision : AngularJS doesn't think that writing code in attributes is a bad idea :) It's its core, and IMHO even what makes it _way_ better to use than jQuery. – Jeremy Thille May 04 '15 at 11:08
  • @Christian: That still looks like a bad idea to me – Adassko May 04 '15 at 11:09
  • $(".marker2").myCallback = doWork; -- without $(".marker1").myCallback = function(){doWork(arg1,arg2);}; -with, solves that ... – Michiel Cornille May 04 '15 at 11:09
  • @Mvision I don't quite get your point. Could you please add your thoughts as an answer. Thanks! – Cristian E. May 04 '15 at 11:12

6 Answers6

15

One way is to use eval()

jQuery(".container").on("click", "button.marker", function (e) {
    var callback = jQuery(e.currentTarget).data("callback");

    var x = eval(callback)
    if (typeof x == 'function') {
        x()
    }
});

Demo: Fiddle

Note: Make sure it is safe in your environment, ie there is no possibility of script injection because of bad input from users

Community
  • 1
  • 1
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
  • I believe `eval("doWork('Mike', 2)")` launches the function by itself. No need to re-launch it by adding more `()`. – Jeremy Thille May 04 '15 at 11:03
  • @JeremyThille that is the `data-callback="doWork"` case where a function reference is passed – Arun P Johny May 04 '15 at 11:03
  • I agree, but I guess OP is looking for one solution or another, and they will choose either the `"dowork"` or `"dowork()"` syntax, making the `if (typeof x == 'function')` test useless. – Jeremy Thille May 04 '15 at 11:05
  • @JeremyThille I did that because of `Or by function 'name'` - I assumed OP meant is a function reference(name) is passed that must be executed – Arun P Johny May 04 '15 at 11:07
  • @ArunPJohny Even if JeremyThille was faster with the answer. I'll mark yours, as an accepted, hence is 'more complete' for future SO users. Thanks – Cristian E. May 04 '15 at 11:55
10

I think a better idea would be to dynamically bind the events and trigger them. If you wanted them to only be known by other code, you could use custom events.

<button type="submit" class="marker marker1"></button>
<button type="submit" class="marker marker2"></button>
<button type="submit" class="marker marker3"></button>
<button type="submit" class="marker marker4"></button>

<script>
    var $markers = $('.marker');
    $markers.filter('.marker1').bind('customCallback', function(){ alert("hi"); });
    $markers.filter('.marker2').bind('customCallback', function(){ doWork(); });
</script>

Then your other components could invoke them with $(selector).trigger('customCallback');

Taplar
  • 24,788
  • 4
  • 22
  • 35
8

Simply with :

 $("body").on("click","button.marker", function(e) {
     eval($(this).data("callback"));
 })
cweston
  • 11,297
  • 19
  • 82
  • 107
Jeremy Thille
  • 26,047
  • 12
  • 43
  • 63
  • how to pass params? – Tim Kretschmer Mar 08 '17 at 05:52
  • The params are in the function, but everyting is hard-coded : `data-callback="doWork('Mike', 2)"` – Jeremy Thille Mar 08 '17 at 09:39
  • yes, so that's why my question was, how to pass. of course i know we can hard code. `eval("function")(args)` wont work – Tim Kretschmer Mar 09 '17 at 04:09
  • Well I guess, if the `doWork` function is global, you can do something like `window[ $(this).data("callback") ](arg1, arg2)` but all this is sooooo dirty :/ Passing global functions via HTML attributes... yurgh... Why not go the clean way and just write JS functions in an isolated scope? – Jeremy Thille Mar 09 '17 at 08:08
5

If you really wanted to pass functions (with or without parameters) from one thing to another without binding them as events you could do it this way (I started from @Taplar's answer)

<button type="submit" class="marker marker1"></button>
<button type="submit" class="marker marker2"></button>
<button type="submit" class="marker marker3"></button>
<button type="submit" class="marker marker4"></button>

<script>
  var $markers = $('.marker');
  $markers.filter('.marker1').get(0).customCallback =  doWork;
  $markers.filter('.marker2').get(0).customCallback = function(){ 
    doWork(arg0,arg1); 
  };
</script>

Then you could access them in your other component as:

<script>
  $('.marker').each(function(){
    var  thisFunction = $(this).get(0).customCallback;
    //do something useful with thisFunction
  });
</script>
Shiladitya
  • 12,003
  • 15
  • 25
  • 38
Michiel Cornille
  • 2,067
  • 1
  • 19
  • 42
  • This is an interesing approach as an alternative to `eval()`. The only shortage is the lack of readability comparing to `data-attributes` especially when using a couple of these. eg: `data-title="Welcome" data-width="300" data-url="/.../..." data-callback="closeOtherDialogs"`. Anyway thank you for sharing experience. – Cristian E. May 04 '15 at 11:46
2

You can just bind a function as a data attribute

const ele = $('button');

ele.data('onClick', evt => {
    alert('bye');
})

ele.click(evt => {
    const ele = $(evt.target);
    ele.data('onClick')(evt);
})
Mojimi
  • 2,561
  • 9
  • 52
  • 116
  • It's worth mentioning this is not storing it as a data attribute within the DOM. JQuery is abstracting that away and maintaining that data within it self. Only string's can be stored in attributes. So that means it cannot be read in native JS. Still a much better solution than using eval, assuming jQuery is available that is. – ste2425 Sep 17 '18 at 10:57
1

Another way is using window[func](args).

Similar to the eval(), but you will have to store the function name and the argument separately in the HTML attribute.

Here is a quick example... CodePen

<button type="submit" data-func="Angel" data-args='use me instead of evil()' class="marker">TEST</button>

<script type="text/javascript">

    //=== The Listener =====
    $(".marker").on("click",function(){

        // Get the function name and arguments
        let func = $(this).attr("data-func");
        let args = $(this).attr("data-args");

        // Call the function
        window[func](args);
    })


    //=== The Function =====
    function Angel(msg){
        alert(arguments.callee.name + " said : " + msg);
    }
</script>
Asuka165
  • 312
  • 2
  • 6