1

function ABC(obj) {
  $("#ID").append('<div onclick="obj.func();">TEST</div>');
}

ABC({func: function() { alert("abc"); } })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="ID"></div>

If you run this function, you will not be able to run obj.func

Why is it not called defined?

How can I solve it?

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
Yongbeen Im
  • 97
  • 1
  • 5

3 Answers3

0

HTML attributes aren’t a good way to attach event listeners, because they run in a global-ish scope. obj is present in your function’s scope, and so it won’t be visible to the code in the onclick attribute. You should create the element and attach the listener separately:

function ABC(obj) {
    // Create a DIV element
    var div = $("<div>");

    // Add an event listener that calls the function
    div.on("click", function () {
        obj.func();
    });

    // Append the created element to #ID
    $("#ID").append(div);
}

The function expression does have access to its parent scope.

If obj.func doesn’t use this and doesn’t return a value, you can also use that function as the listener directly:

div.on("click", obj.func);
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • If you have a lot of items to be appended to, how should you handle them all the way? – Yongbeen Im Dec 29 '17 at 04:58
  • @YongBinLim: What do you mean by "all the way"? – Ry- Dec 29 '17 at 04:59
  • There are a **[whole lot of reasons](https://stackoverflow.com/questions/43459890/javascript-function-doesnt-work-when-link-is-clicked/43459991#43459991)** not to use HTML event attributes. – Scott Marcus Dec 29 '17 at 05:00
  • I use a translator. So my sentence is not natural. I have a lot of tags to " append " to do. Would you like to add the tags in the above way? – Yongbeen Im Dec 29 '17 at 05:04
  • @YongBinLim: You can use this for any number of tags, in a loop, if that’s what you’re asking. – Ry- Dec 29 '17 at 05:08
  • @YongBinLim You'd have better to use a class, delegate event and pass `obj` as data to event handler. That way, you'd just bind one event handler for all these elements. EDIT: it looks like you even don't need to pass any data because the `obj.func` is the event handler in your case... – A. Wolff Dec 29 '17 at 05:11
  • This is a good answer and does work, but you really didn't address why the original code doesn't work. – Scott Marcus Dec 29 '17 at 05:29
  • @ScottMarcus: Expanded on "scope" – Ry- Dec 29 '17 at 05:34
0
function ABC(obj) {

    $("#question-header").append('<div id="this-div">TEST</div>');
   $("body").off("click","#this-div").on("click","#this-div",function(){ obj.func(); });
}

ABC({func: function() { alert("abc"); } }); 

This is 100% working .. check this , you can even put events other than click ...

-1

The actual problem is that by the time the element gets clicked, the ABC function will have completed and its obj argument will be out of scope. Since your code hard-codes obj into the click event code, obj has no meaning at that point and you get your obj is not defined error.

Now, HTML event attributes don't take functions as their values, they take executable JavaScript instructions. So, you don't want the onclick to be set to a function, you want it set to a string of JavaScript to execute and that can be returned from your function that is passed in:

function ABC(obj) {
  // By invoking the function stored in the passed in object, we can 
  // inject the appropriate JavaScript to run when the element gets clicked.
  $("#ID").append('<div onclick="' + obj.func() + '">TEST</div>');
}

ABC({ 
   func: function() { return "alert('abc');"; }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="ID"></div>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • Why a down vote for the correct explanation of the problem with a working solution? – Scott Marcus Dec 29 '17 at 05:30
  • Embedding JavaScript in strings isn’t a good idea. See e.g. `setTimeout`. – Ry- Dec 29 '17 at 05:33
  • @Ryan That's besides the point. My answer doesn't recommend this approach. It correctly answers the question and shows what to do to make it work. – Scott Marcus Dec 29 '17 at 05:35
  • I’m pretty sure the idea was to call `obj.func()` on click anyway, and good practices are never beside* the point. – Ry- Dec 29 '17 at 05:36
  • @Ryan Answering the question is always the #1 job. That's what my answer does. – Scott Marcus Dec 29 '17 at 05:37
  • Well, like I said, it doesn’t do that either – the intent was obviously to call `obj.func()`, not have it return a string of JS. – Ry- Dec 29 '17 at 05:38
  • @Ryan That's your opinion. I think the intent was for it to work. The OP says *Why is it not called defined?* (I've answered that in full) and then the OP says *How can I solve it?* (I've shown and explained this as well). Your answer is good and it is the better approach, but you side-step the opportunity to show the OP how his code could work in the name of "I've got a better way." In cases like that, you should show the solution using the OPs method and then provide your better way as well. You don't do that. – Scott Marcus Dec 29 '17 at 05:39
  • There isn’t a solution using the OP’s method. You can’t expose a function’s scope to an HTML attribute. Globals, your answer, etc. are just sidestepping the problem in a different (less helpful) way. – Ry- Dec 29 '17 at 05:48
  • @Ryan I think that your interpretation of what the OP wants is incorrect. I believe that my answer shows the OP how to get the job they want done, done using a slight modification of his original code. Your solution takes a completely different approach, which (as I've said) is a good approach, but doesn't address the original attempt. – Scott Marcus Dec 29 '17 at 15:40