0

I'm trying to write my first plugin using jQuery Boilerplate. The issue I'm having is that I lose access to the this scope as soon as I try to handle an event, due, I assume, to variable shadowing. It seems like would be a common use case for jQuery Boilerplate, so I'm guessing that I'm going about it incorrectly.

I found two similar questions here on SO:

  1. jQuery plugin object: attached an event handler via .on() and now have a scope issue of this. (the main plugin object) - Has no answer
  2. Javascript scoping Issue using JQuery Boilerplate - Doesn't answer my question

I've created a minimal sample to demonstrate the issue.

HTML

<ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
</ul>

PLUGIN

(function($, window, document, undefined) {
    'use strict';

    var pluginName = 'elementValueLog',
            defaults = {};

    function Plugin(element, options) {
        this.element = element;
        this.$element = $(element);
        this.settings = $.extend({}, defaults, options);
        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    }

    $.extend(Plugin.prototype, {
        init: function() {
            this.$element.on('click', this.doLog);
        },
        doLog: function() {
            // HERE: I can't figure out how to access the Plugin "this" scope
            // I want to be able to use "element", "$element", "settings", etc.
            console.log(this.$element.val().trim());
        }
    });

    $.fn[pluginName] = function(options) {
        this.each(function() {
            if (!$.data(this, "plugin_" + pluginName)) {
                $.data(this, "plugin_" + pluginName, new Plugin(this, options));
            }
        });
        return this;
    };
}(jQuery, window, document, undefined));

USE PLUGIN

$(document).ready(function() {
    $('li').elementValueLog();
});

SOLUTION

I would have preferred to add this as an answer, but being marked as "duplicate" is preventing that. After trying several methods shown in the answers on the other post, I found a solution. I personally think my question is specific enough to stand on its own, because the "canonical" answer on the other post is quite broad.

For browsers that support bind, the init function can be changed like this:

init: function() {
    this.$element.on('click', this.doLog.bind(this));
},

Because I need to support IE 8, I will be using jQuery.proxy:

init: function() {
    this.$element.on('click', $.proxy(this.doLog, this));
},

The doLog function can then reference this.element, this.$element, this.settings, etc.

Based on the answer from Jeff Watkins:

init: function() {
    var plugin = this;
    var doLog = function() {
        console.log(plugin.$element.val().trim());
    };
    this.$element.on('click', doLog);
},

This solution has the benefit of preserving the this context while giving access to the Plugin's this context.

Community
  • 1
  • 1
Sonny
  • 8,204
  • 7
  • 63
  • 134
  • 1
    So this has been marked as a duplicate of another post, but I don't understand how to apply the solution that's presented in that post. – Sonny Dec 01 '14 at 14:32

1 Answers1

1

"this" is contextual to where you are in the stack, so "this" in an event is actually the event, if that makes sense. It's a common programming concept.

I often use a cached version of "this" (i.e. the caller) to use if you need to call something on it.

i.e.

$.fn[pluginName] = function(options) {
    var caller = this;
    this.each(function() {
        if (!$.data(caller, "plugin_" + pluginName)) {
Jeff Watkins
  • 6,343
  • 16
  • 19
  • How would you change the `doLog` function? That's where I am unable to access the `Plugin`'s `element`, `$element`, and `settings` properties. – Sonny Dec 01 '14 at 14:46
  • 1
    The same way as above... although your proxy answer works as well IIRC. – Jeff Watkins Dec 02 '14 at 14:58
  • Because you gave an answer that works, I'm going to accept it. I've also added your answer as one of the possible solutions. – Sonny Dec 02 '14 at 15:06