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?

11
  • 1
    You can have a look at the devil eval() Commented May 4, 2015 at 10:55
  • Is there a reason your avoiding dynamically binding? Would simplify the issue. Commented May 4, 2015 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 :) Commented May 4, 2015 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. Commented May 4, 2015 at 11:05
  • 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. Commented May 4, 2015 at 11:08

6 Answers 6

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

Sign up to request clarification or add additional context in comments.

5 Comments

I believe eval("doWork('Mike', 2)") launches the function by itself. No need to re-launch it by adding more ().
@JeremyThille that is the data-callback="doWork" case where a function reference is passed
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.
@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
@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
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');

1 Comment

wouldn't that have to be .filter('.marker1') with a dot? :)
8

Simply with :

 $("body").on("click","button.marker", function(e) {
     eval($(this).data("callback"));
 })

4 Comments

how to pass params?
The params are in the function, but everyting is hard-coded : data-callback="doWork('Mike', 2)"
yes, so that's why my question was, how to pass. of course i know we can hard code. eval("function")(args) wont work
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?
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>

1 Comment

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.
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);
})

1 Comment

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.
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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.