Henrique Miranda
Henrique Miranda

Reputation: 1108

How to use the same instance of a jQuery plugin when calling it from different elements?

I've used this plugin boilerplate as a starting point to create a plugin that allows me to make direct calls to it's methods:

;(function ($, undefined ) {
    var pluginName = 'myPlugin',
        defaults = {
            option1: 'none',
            option2: 'none',
        };

    function Plugin( element, options ) {
        this.element = element;
        this.options = $.extend({}, defaults, options) ;
        this.someBool = undefined;

        this.init();
    }

     Plugin.prototype = {

        init: function () {
            this.someBool = false;
        },
        someMethod: function (selector) {
            var self = this;
            $(selector).each(function () {
                // do something to the element using self.options
                self.someBool = true;
            };
        }
        someOtherMethod: function (selector, myBool) {
            var self = this;
            $(selector).each(function () {
                // do some other thing to the element using self.options
                self.someBool = myBool;
            };
        }
    }
    $.fn[pluginName] = function (options) {
        var args = arguments;
        if (options === undefined || typeof options === 'object') {
            return this.each(function () {
                if (!$.data(this, 'plugin_' + pluginName)) {
                    $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
                }
            });
        } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
            var returns;
            this.each(function () {
                var instance = $.data(this, 'plugin_' + pluginName);
                if (instance instanceof Plugin && typeof instance[options] === 'function') {
                    returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
                }

                if (options === 'destroy') {
                  $.data(this, 'plugin_' + pluginName, null);
                }
            });
            return returns !== undefined ? returns : this;
        }
    };

}(jQuery));

So, if I call the plugin with no arguments or with an object as argument, it instantiates and initializes it. If I pass a string, it looks for a method with that name and pass the other arguments to it. Right now I'm using it like this (note that #someElement is only used to store the instance, nothing else):

$('#someElement').myPlugin({
    option1: 'value1',
    option2: 'value2',
});
$('#someElement').myPlugin('someMethod', '#someOtherElement');
$('#someElement').myPlugin('someOtherMethod', '#yetAnotherElement', false);

It kinda works, but I would like to be able to call these methods using different selectors while working with the same instance, a singleton, if you will. Something like:

$.myPlugin({
    option1: 'value1',
    option2: 'value2',
});
$('#someOtherElement').myPlugin('someMethod');
$('#yetAnotherElement').myPlugin('someOtherMethod', false);

I'm not much of a javascript programmer, but as far as I understand, I must store it's instance in a place other than the elements I'm using to call it, maybe in the window object or jQuery namespace? What about being able to call it both with and without a selector, is it doable?

Upvotes: 1

Views: 1848

Answers (2)

Henrique Miranda
Henrique Miranda

Reputation: 1108

I was able to set options globally instead of per instance by looking at this link. This does not provides a singleton plugin as I initially requested, but it is, in fact, even better this way, because I can have both global and per element options.

$[pluginName] = $.fn[pluginName] = function (options) {
    var args = arguments;

    if (options === undefined || typeof options === 'object') {
        if (!(this instanceof $)) {
            $.extend(defaults, options);
        }
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
            }
        });
    } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
        var returns;

        this.each(function () {
            var instance = $.data(this, 'plugin_' + pluginName);
            if (!instance) {
                instance = $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
            }

            if (instance instanceof Plugin && typeof instance[options] === 'function') {
                returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
            }

            if (options === 'destroy') {
                $.data(this, 'plugin_' + pluginName, null);
            }
        });

        return returns !== undefined ? returns : this;
    }
};

And now I can call the script just like I've described in my question.

As a side note, if you look at my previous questions, you'll see that I have answered most of them. Mostly because very few people bothered to even try to help. I don't think that these questions were neither too basic or too difficult to bother to answer... too specific, perhaps? I would love to get some feedback on that.

Upvotes: 2

janfoeh
janfoeh

Reputation: 10328

There are jQuery plugins that act as singletons. jQuery cookie is a example that is short and easy to read. It should be no problem to adapt its pattern to your requirements.

In essence, singleton plugins simply define themselves directly on $, as in

$.myplugin = function(...)

and skip the whole

return this.each(function () { }

part, since they are not being initialized on jQuery elements. So it's mostly a matter of leaving things out ;)

Upvotes: 2

Related Questions