1

I've had a look at this thread: How to create a jQuery plugin with methods?, and while there are plenty of solutions regarding multi-function plugins, I'm interested if it's possible to use an object so that you don't have ugly constructions like $("selector").plugin().dostuff(), and instead can use it more like this: $("selector").plugin.dostuff(), ie 'plugin' is an object not a function.

I can write the plugin like this:

$.fn.plugin = {
    dostuff: function() {},
    domorestuff: function(){}
};

But then the inner functions won't have access to the JQuery object that's calling it in the first place, as this returns the plugin object. Is there any way to structure a plugin based around an object that would work with JQuery?

Community
  • 1
  • 1
Migwell
  • 18,631
  • 21
  • 91
  • 160

1 Answers1

1

No, and for the reason you gave: You lose this, and this is hugely important when writing a plugin. To get this, your plugin must be a function.

You basically have three ways of implementing methods:

  1. Using string arguments, e.g.:

    $("selector").plugin("dostuff");
    

    ...where you dispatch to your handler methods within the plugin function. This is by far the most popular way. It's the way used by jQuery UI, Bootstrap, and others. Full example of this below.

  2. Using the "ugly construction" as you put it, where your plugin is a function and it returns an object (a new object each time, to preserve this) with functions you can call.

  3. Adding several plugin methods to jQuery rather than just one, using a prefix of some kind, so it ends up being $("selector").pluginDoThis(); and $("selector").pluginDoThat(); and $("selector").pluginDoTheOther(); The key is to use a prefix to avoid name conflicts. I've only seen this done once, a long time ago, and I haven't seen that plugin in a while. Not really a popular choice.

I just recently posted this other answer showing the full pattern for #1 (in their case, the methods were callThis and destroy), e.g.:

// Create the plugin
(function ($) {
    var methods = {
        init: function(options) {
            // Determine options
            var opts = $.extend({
                opacity: 0.5             
            }, options);

            // Remember them
            this.data("pluginname", opts);

            // Initial stuff
            this.css("opacity", opts.opacity);
        },

        callThis: function(opts) {
            // Use 'opts' if relevant
            this.css("display","none");
        },

        destroy: function(opts) {
            this.removeData("pluginame");
            this.css("display", "").css("opacity", "");
        }
    };

    jQuery.fn.pluginname = function (options) {
        var method, args;

        // Method?
        if (typeof options === "string") {
            // Yes, grab the name
            method = options;

            // And arguments (we copy the arguments, then
            // replace the first with our options)
            args = Array.prototype.slice.call(arguments, 0);

            // Get our options from setup call
            args[0] = this.data("pluginname");
            if (!args[0]) {
                // There was no setup call, do setup with defaults
                methods.init.call(this);
                args[0] = this.data("pluginname");
            }
        }
        else {
            // Not a method call, use init
            method = "init";
            args = [options];
        }

        // Do the call
        methods[method].apply(this, args);
    };
})(jQuery);

// Example usage
function doInit() {
  $("#target").pluginname();
  setTimeout(doCallThis, 700);
}
function doCallThis() {
  $("#target").pluginname("callThis");
  setTimeout(doDestroy, 700);
}
function doDestroy() {
  $("#target").pluginname("destroy");
  setTimeout(doInit, 700);
}

setTimeout(doInit, 700);
<div id="target">This is the target</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Okay you've convinced me. But can you explain why `this` works in your example and not in mine? – Migwell Feb 07 '14 at 12:44
  • @Miguel: Because `this` in JavaScript is set primarily by how a function is called: If you call a function via an object property, within the call, `this` refers to that object (normally). So `foo.bar();` calls `bar` with `this` equal to `foo`. `$("selector").plugin();` calls `plugin` with `this` equal to the jQuery object returned by `$("selector")`. But `$("selector").plugin.method();` calls `method` with `this` equal to `plugin`, not the jQuery object; the reference to the jQuery object has been lost. – T.J. Crowder Feb 07 '14 at 12:51
  • But in your case doesn't `methods[method].apply(this, args);` involve calling a method of the object "methods"? So why is `this` not set to "methods"? – Migwell Feb 07 '14 at 12:58
  • 1
    @Miguel: Because the `apply` (and `call`) functions attached to functions are special: Their raison d'être is to call a function with the specified `this` value (which you give in the first argument). – T.J. Crowder Feb 07 '14 at 13:07