Pass javascript function as data-* attribute and execute
Asked Answered
M

6

36

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?

Macnair answered 4/5, 2015 at 10:54 Comment(11)
You can have a look at the devil eval()Tardigrade
Is there a reason your avoiding dynamically binding? Would simplify the issue.Genovese
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 :)Zinnia
@Genovese Yes. The sample is simplified. In production code the callback is meant to be called by other components and at latter time.Macnair
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.Genovese
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..)Zinnia
@Mvision Regards the inline code totally agree. I'm more interested on calling by function name and function name with argsMacnair
@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.Oscilloscope
@Christian: That still looks like a bad idea to meKiele
$(".marker2").myCallback = doWork; -- without $(".marker1").myCallback = function(){doWork(arg1,arg2);}; -with, solves that ...Zinnia
@Mvision I don't quite get your point. Could you please add your thoughts as an answer. Thanks!Macnair
T
16

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

Tardigrade answered 4/5, 2015 at 10:59 Comment(5)
I believe eval("doWork('Mike', 2)") launches the function by itself. No need to re-launch it by adding more ().Oscilloscope
@JeremyThille that is the data-callback="doWork" case where a function reference is passedTardigrade
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.Oscilloscope
@JeremyThille I did that because of Or by function 'name' - I assumed OP meant is a function reference(name) is passed that must be executedTardigrade
@ArunPJohny Even if JeremyThille was faster with the answer. I'll mark yours, as an accepted, hence is 'more complete' for future SO users. ThanksMacnair
G
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');

Genovese answered 4/5, 2015 at 11:13 Comment(1)
wouldn't that have to be .filter('.marker1') with a dot? :)Zinnia
O
8

Simply with :

 $("body").on("click","button.marker", function(e) {
     eval($(this).data("callback"));
 })
Oscilloscope answered 4/5, 2015 at 10:59 Comment(4)
how to pass params?Safeguard
The params are in the function, but everyting is hard-coded : data-callback="doWork('Mike', 2)"Oscilloscope
yes, so that's why my question was, how to pass. of course i know we can hard code. eval("function")(args) wont workSafeguard
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?Oscilloscope
Z
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>
Zinnia answered 4/5, 2015 at 11:22 Comment(1)
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.Macnair
G
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);
})
Grangerize answered 2/4, 2018 at 21:14 Comment(1)
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.Gallice
G
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>
Gorget answered 17/4, 2019 at 22:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.