7

Lets say for example:

$(".button").click(function() {

    $.post("commandrunner.php",
        {
            param1: 'value',
            param2: 'value2',
            param3: 'value3'
        },
        function(data, textStatus) {
            $(this).parent().after('<p>button clicked</p>');
        },
        "json"
    );

});

I ran this and it didn't work. I tried a couple of things before I theorized the callback wasn't being invoked in the context of this particular ".button" and so $(this) was useless. This worked instead:

$(".button").click(function() {
    var thisButton = $(this);

    $.post("commandrunner.php",
        {
            param1: 'value',
            param2: 'value2',
            param3: 'value3'
        },
        function(data, textStatus) {
            thisButton.parent().after('<p>button clicked</p>')
        },
        "json"
    );

});

This feels like a bit of a hack. Is this the right way to get a reference to the clicked on button? And what context is that (or any other callback) invoked in?

Thanks!

Ali

Ali
  • 502
  • 1
  • 4
  • 11

4 Answers4

13

What you've noticed here is the way JavaScript closures work. As in other OO languages, when a method is invoked, it has a "this", which is similar to Java's this or Ruby's self. However, the JavaScript this is remarkably malleable, and jQuery takes advantage of that property to set it to a useful value when it calls your callback.

In the case of events, jQuery sets the this pointer to point at the element that you bound the event handler to. Check this out:

var hello = function() {
  console.log("Hello " + this);
});

If you come from an OO background, you might look at the above snippet in bewilderment: what does this point to. By default, it will point at the global object (window in browsers). But you can easily override that:

hello.call("world") // will print "Hello world"

Call takes multiple arguments after the this which are passed in as arguments. A similar method, called apply, will also take a this as the first argument, but takes an Array of arguments as the second parameter.

Now, if we look at your example again:

$(".button").click(function() {

    $.post("commandrunner.php",
        {
            param1: 'value',
            param2: 'value2',
            param3: 'value3'
        },
        function(data, textStatus) {
            $(this).parent().after('<p>button clicked</p>')
        },
        "json"
    );

});

The issue here is that when jQuery called the function again, it did not call it with the same this. When you create an anonymous function, it will hold onto all local variables in the same scope, but not this, which is set by JavaScript itself (to the global object) or overridden when called.

As a result, the solution is to store off a pointer to this in a local variable, which will then be available to the closure.

The solution, as mentioned above, is:

$(".button").click(function() {
  var parent = $(this).parent();
  $.post("commandrunner.php",
    {
      param1: 'value',
      param2: 'value2',
      param3: 'value3'
    },
    function() {
      parent.after('<p>button clicked</p>')
    },
    "json"
  );
});

In general, when I store off a local, I store off the most specific set of elements I can, to make the callback code simpler. The common jQuery idiom is to do var self = this, which is perfectly fine as well.

Also note that JavaScript functions do not check the number of parameters, so it's perfectly legal to leave the parameters empty if you do not use them. They will still be passed in, but the empty parameter list will simply be ignored.

Yehuda Katz
  • 28,535
  • 12
  • 89
  • 91
  • +1 for setting off specific elements instead of the entire object. – Luca Matteis Jun 30 '09 at 17:01
  • Thanks, that answered the question. Great book by the way. I learned all the important stuff about javascript from the appendix! Also, coming from an OO/Java background, I've been struggling with closures for a while. Would I be right in assuming the above is an example of a closure? (i.e. that the anonymous function in the post callback can access variables in the scope of the enclosing anonymous function bound to the click event, and that this is therefore a 'closure') – Ali Jun 30 '09 at 21:34
  • All functions in JavaScript, regardless of whether they're attached to objects, close over the local variables in place when they were created. This applies to named functions (function foo() {}) and anonymous functions (function() {}). – Yehuda Katz Jun 30 '09 at 21:39
  • Oh and thanks for the note about unused parameters, will keep that in mind. – Ali Jun 30 '09 at 21:39
  • BTW: If you want to know how many explicit parameters a function has, you can call length on it. Example: (function(x, y) {}).length will return 2, (function() {}).length will return 0. – Yehuda Katz Jun 30 '09 at 21:43
5

According to jquery api (under "Callback Function Queues") the this object in any JQuery AJAX callback function will automatically point to the settings of the ajax object or - if specified - the context object.

Sadly you cannot specify a context in $.post().

Erik Dekker
  • 2,395
  • 5
  • 33
  • 55
p.sherif
  • 61
  • 1
  • 3
  • you can specify one in $.ajax, like : `$.ajax({ url: "test.html", context: document.body }).done(function() { $( this ).addClass( "done" ); });` – cnlevy Nov 24 '13 at 15:53
0

That is the right approach to the problem.

The easiest way to find out the context would be to use Firebug and console.log(this) — I suspect it would be the XHR object though.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
0

The keyword "this" is bound to the scope of a function. To reference other parent scopes you need to store the reference in appropriate scoping variables, such as var parentScope = this;

Luca Matteis
  • 29,161
  • 19
  • 114
  • 169