emmzee
emmzee

Reputation: 640

jQuery multiple instance plugin using "this" in setTimeout

I'm trying to write a new plugin that can be initialized on multiple elements within the same page, each time with different options, ex:

$('#id').plugin({ option:true });
$('#id2').plugin({ option:false });

I'm using the boilerplate from jqueryboilerplate.com (https://github.com/jquery-boilerplate/jquery-boilerplate). I understand (at least I think I do!) that the problem is that within the scope of the anonymous function (here, within setTimeout) 'this' refers to the window. So in the following, the output is logged the first time, but not the second:

// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {
    init: function () {
        console.log(this.settings.propertyName);
        setTimeout(function(){
            console.log(this.settings.propertyName);
        }, 1000);
    }
});

Elsewhere this.settings.propertyName is set to 'value'. Console.log result is:

value
Uncaught TypeError: Cannot read property 'propertyName' of undefined

If for example I set window.prop = this.settings.propertyName and console.log window.prop instead, that works, but the problem is that there may be many instances running at the same time.

I've read through lots of questions related to this subject, but none seem to actually address this particular situation. I would be grateful if someone could give me a clear example of how to do this within the context of a jQuery plugin using the boilerplate, or something like it. Please excuse my noobness, thank you!!

Upvotes: 0

Views: 180

Answers (3)

Tushar
Tushar

Reputation: 87203

setTimeout is executed as window.setTimeout, so the context of the setTimeout handler is set to the window object. If you want to change the context of it, you can use bind().

setTimeout(function () {
    // this.xyz
    // use this here
}.bind(this), 1000);

The bind will bind the context outside of setTimeout to the one inside it.

Reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

You can also cache the context and use it inside setTimeout.

// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {
    init: function () {
        var self = this; // Cache context

        setTimeout(function () {
            console.log(self.settings.propertyName); // Use cached context instead of `this`
        }, 1000);
    }
});

Upvotes: 1

Karl-André Gagnon
Karl-André Gagnon

Reputation: 33880

It happen because the this in the timeout refer to the window object. It can be easy fixed by keeping a reference outside the function like that :

// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {
    init: function () {
        console.log(this.settings.propertyName);
        var self = this;
        setTimeout(function(){
            console.log(self.settings.propertyName); //Use your reference here.
        }, 1000);
    }
});

If for some reason you prefer not having a variable with the reference, you can always use Function.prototype.bind. Be sure to check the compatibility before using it. What .bind does is simply returning a new function while altering the this value and the arguments. You can use it like that :

// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {
    init: function () {
        console.log(this.settings.propertyName);
        setTimeout(function(){
            console.log(this.settings.propertyName);
        }.bind(this), 1000);
    }
});

Upvotes: 1

Malk
Malk

Reputation: 11983

Capture a closure on this:

$.extend(Plugin.prototype, {
    init: function () {
        var _this = this;
        console.log(this.settings.propertyName);
        setTimeout(function(){
            console.log(_this.settings.propertyName);
        }, 1000);
    }
});

Upvotes: 1

Related Questions