jQuery Plugin Development how to keep variables and methods private

1.6k views Asked by At

For exercise purposes I am creating a jQuery Plugin, a simple Image Slider. I use a Pattern from Boilerplate - jQuery Plugins.

During the initializing process everything works as expected, every Instance gets the right values (widths and heights, also event binding) needed for setup.

The trouble starts when I try to pass a calculated slides-width to a function doing the animation (clicked on next button). Every value which I tried to save was overwritten by the last instance - ok, that's what prototype inheritance does, as I learned.

I googled a lot and found (not only) this solution on stockoverflow: global or local variables in a jquery-plugin. The accepted answer suggests to store values which need to be private in the HTML Markup, the data-whatever attribute.

This works very good, but is not very nice.

The second answer seemed to be more elegant, keeping real private variables in the scope of every instance:

(function($) {

$.pluginName = function(element, options) {
....
var plugin = this;
....
        // a public method
    plugin.foo_public_method = function() {
          ....
    }

    // private methods
    var foo_private_method = function() {
          ....
    }
    plugin.init();

} ....

Looked pretty simple - but I am not able to implement this solution!

So my Question is: how do I implement a private variable in the following plugin pattern? (And how do I call it properly from inside Plugin.prototype ... )

I created a Fiddle with my plugin. An abstract of the code:

(function ($, window, document, undefined) {

var pluginName = "easyCarousel";

// default configuration object
defaults = {
       ... default values here, css etc
    }
};

// The actual plugin constructor
function Plugin(element, options) {

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

Plugin.prototype = {

    init: function () {

        var options = this.options;
        this.options.stage = $(this.element);
        .... calling/starting views and controllers

    },

    buildCollectionView: function (options, Helper) {
         .... setting markup, applying css, calulating dimensions 
    },

    buildStageView: function (options, Helper) {

        .... setting markup, applying css
        .... storing calculated slide width in data Attribute

        stage.attr({
            'data-slidewidth': width,
            'data-stagewidth': setWidth
        });
    },

    buildTheatreView: function (options, Helper) {

        .... setting markup, applying css

    },

    buildNavigationView: function (options) {

        .... setting markup

    },


    navigationController: 

    function (options, slideForward, slideBackward) {

        .... event binding

        pubSub.on("navigation:arrownext:click", function (e) {
            slideForward(options, e);
        });

        $('.carousel__arrownext', options.theatre)
        .bind('click', function () {
            pubSub.trigger("navigation:arrownext:click", this);
        });
    },

    slideForwardAnimation: function (options, e) {

       .... reading stored width value from data Attribute

        $element.animate({
            "left": "-=" + slideWidth
        })
    },


    setDimensionsHelper: function (options) {

        .... calculating an returning dimensions of the slider
    },

    eventBusHelper: function (options) {
        // integrating third party eventbus
    }
};

$.fn[pluginName] = function (options) {

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

I hope my code example is not to complicated/long to view.

As you may see I'm quite new to advanced Javascript patterns - and right now I really stuck. I already spend many many hours in this problem. So every attempt to help is very appreciated. Also of course every review on the code.

Thanks in advance :)

1

There are 1 answers

0
Isochronous On BEST ANSWER

All you need to do is define the variables within your prototype (directly or from within a method in the prototype) rather than doing it in your constructor. Variables defined in the constructor will be shared among all instances of your plugin, while variables defined in the prototype will be per-instance.

Also, you have no "var" keyword in front of your "defaults" definition, so you're actually creating a global variable called "defaults" rather than one that's scoped to just your closure.

This is what I'd recommend:

// default configuration object
var defaults = {
       ... default values here, css etc
    }
};

// The actual plugin constructor
function Plugin(element, options) {
    this._defaults = defaults;
    this._name = pluginName;
    this.init(element, options);
}

Plugin.prototype = {

    init: function (element, options) {
        this.element = element;
        this.options = $.extend({}, this._defaults, options);
        this.options.stage = $(this.element);
        .... calling/starting views and controllers

    },

or even better:

// The actual plugin constructor
function Plugin(element, options) {
    this.init(element, options);
}

Plugin.prototype = {

    _name: 'put your name here',

    _defaults: {
        // just define them here, no need for an outside variable
    },

    init: function (element, options) {
        this.element = element;
        this.options = $.extend({}, this._defaults, options);

        // why are you saving stage as an option? That doesn't make sense.
        this.options.stage = $(this.element);
        // Makes more sense to just do this ($el is a more standard name for this)
        this.$el = $(this.element);
        // .... calling/starting views and controllers

    },