HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Private methods and properties in jQuery "lightweight start" pattern

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
propertieslightweightprivatemethodsjquerystartandpattern

Problem

For non-plugin code, I generally use the module pattern, but I'm working on a jQuery plugin and decided to try out the "lightweight start" pattern. The problem is, I find myself needing $.proxy when calling private methods to ensure the this context.

Is there a better way to structure the code in order to avoid $.proxy, while ensuring the private methods have access to the init() method's this context? Where exactly should I define private properties?

Jsfiddle example without $.proxy ... also have a jsfiddle example using $.proxy

(function($, window, document, undefined) {
var pluginName = "myPlugin",
    defaults = {
        opt1: "#opt1",
    };

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

Plugin.prototype.init = function() {
    this.counter = 1;
    $("div").each(privateMethod1);
    $("body").on("click", this.options.opt1, event1);
};

function privateMethod2(i, div) {
    // "this" context is Window, not Plugin
    $(div).append(i);
    this.element.append(this.counter);        
};

function privateMethod1(i, el) {
    // "this" context is Window, not Plugin
    this.counter += 1; // undefined, but defined in init()
    privateMethod2(i, el);
};

function event1(ev, el) {
    this.counter += 1; // undefined, but defined in init()
    privateMethod1(this.counter, el);
};

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


Edit:

What I learned. This pattern appears to actually make all methods private. This is because the if (!$.data(this, "plugin_" + pluginName)) { has no else block to handle elements that already have a reference to the plugin. To allow a method to be called, check out https://stacko

Solution

You could use a closure to create your own proxy.

Plugin.prototype.init = function() {
    var self = this;
    rows.each(function (groupIndex, group) {
        self.initGroup(groupIndex, group);
    });
    this.container.on("click", this.options.addBtnId, function (ev) {
        self.addGroup(ev);
    });
};


Why are initGroup and addGroup using $.proxy? They are already being called with the correct this context, and rebinding to the same context changes nothing.

Edit: Instead of using plain functions to implement "private methods", promote them to proper object methods and mark them private using JSDoc. This will save you those extra calls to $.proxy.

/** @private */
Plugin.prototype.initGroup = function(groupIndex, group) {
    this.addFieldIds(groupIndex, group);
};

/** @private */
Plugin.prototype.addGroup = function(ev) {
    this.initGroup(this.maxGroupIndex, newGroup);
};


In my opinion, forcing developers to jump through hoops just to hide private methods is a net loss.

Code Snippets

Plugin.prototype.init = function() {
    var self = this;
    rows.each(function (groupIndex, group) {
        self.initGroup(groupIndex, group);
    });
    this.container.on("click", this.options.addBtnId, function (ev) {
        self.addGroup(ev);
    });
};
/** @private */
Plugin.prototype.initGroup = function(groupIndex, group) {
    this.addFieldIds(groupIndex, group);
};

/** @private */
Plugin.prototype.addGroup = function(ev) {
    this.initGroup(this.maxGroupIndex, newGroup);
};

Context

StackExchange Code Review Q#45369, answer score: 4

Revisions (0)

No revisions yet.