3
\$\begingroup\$

I need to write a jQuery plugin that doesn't take an element to work.

Example call:

$.funkyTown();

... not called like this:

$('#foo').funkyTown();

...in other words, I need this plugin to act more like a "utility" plugin (vs. apply itself directly to a matched element(s)).

Here's what I have written so far:

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

    var console = this.console || { log : $.noop, warn: $.noop }, // http://api.jquery.com/jQuery.noop/

    defaults = {

        foo : 'bar',

        // Callbacks:

        onInit : $.noop, // After plugin data initialized.
        onAfterInit : $.noop // After plugin initialization.

        // Using $.noop shorter than function() {} and slightly better for memory.

    },

    settings = {},

    methods = {

        // Initialize!
        // Example call:
        // $.funkyTown({ foo : 'baz' });
        // @constructor
        init : function(options) {

            settings = $.extend({}, defaults, options);

            settings.onInit.call(this, 'that');

            console.log('1. init:', settings.foo, _foo_private_method(), methods.foo_public_method());

            console.warn('2. I\'m a warning!');

            settings.onAfterInit.call(this);

            return this; // Is this needed for chaining?

        },

        // Example call:
        // console.log($.funkyTown('foo_public_method', 'Wha?'));
        foo_public_method : function(arg1) {

            arg1 = (typeof arg1 !== 'undefined') ? arg1 : 'Boo!';

            return 'foo_public_method(), arg1: ' + arg1;

        },

        // Might need to give users the option to destroy what this plugin created:
        destroy : function() {

            // Undo things here.

        }

    },

    // The _ (underscore) is a naming convention for private members.
    _foo_private_method = function() {

        return '_foo_private_method(), settings.foo: ' + settings.foo;

    };

    // Method calling logic/boilerplate:
    $.funkyTown = function(method) {

        if (methods[method]) {

            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));

        } else if ((typeof method === 'object') || ( ! method)) {

            return methods.init.apply(this, arguments);

        } else {

            $.error('Method ' + method + ' does not exist on jQuery.funkyTown.');

        }

    };

}(jQuery, window, document));

The above is called like so:

$(document).ready(function() {

    // Calls init and modifies default options:
    $.funkyTown({ foo : 'baz' });

    // Access to public method:
    console.log($.funkyTown('foo_public_method', 'Wha?'));

});

My questions:

  1. Do you see anything out of the ordinary with my above plugin template? If so, how could it be improved?
  2. Related to #1 above: As you can probably see, I'm trying to account for the various needs like passing options, private and public methods and console handling... What am I missing in terms of useful features?
  3. This line $.funkyTown = function(method) { makes the javascript linter throw a warning: "warning: anonymous function does not always return a value"; is there anyway for me to fix this? Could I just add return false to the end (right before the closing };? Update: Looks like I just needed to use a different tool.
  4. Because I'm writing a utility plugin (one that will never be used directly on an element) do I still need to return this in my public methods in order to make things chainable (see the return this; // Is this needed for chaining? line of code above)? Should I even worry about chaining for this type of plugin?
  5. Could you provide any other feedback to help me improve my code?
  6. What's the easiest/best way to pass settings from init to other private and public functions? Normally, I'd use .data() on $(this) to store settings and other stateful vars... Because there's not element, should I just pass settings as an argument to the other methods? Update: Doi! This was an easy one! I simply needed to initialize my settings outside of my public methods object.

UPDATE 1:

I've updated my code (above) to reflect the things I've learned (i.e. the strike-through lines in numeric list above) since posting this question.

I've also added a new feature:

;(function($, window, document, undefined) {
// ...
}(jQuery, window, document));

I found that I needed access to window a few times already in my real script... After some Googling, I found this awesome resource:

JavaScript Patterns Collection

... which led me to here:

Lightweight - perfect as a generic template for beginners and above

... specifically:

// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {

    // undefined is used here as the undefined global
    // variable in ECMAScript 3 and is mutable (i.e. it can
    // be changed by someone else). undefined isn't really
    // being passed in so we can ensure that its value is
    // truly undefined. In ES5, undefined can no longer be
    // modified.

    // window and document are passed through as local
    // variables rather than as globals, because this (slightly)
    // quickens the resolution process and can be more
    // efficiently minified (especially when both are
    // regularly referenced in your plugin).

})( jQuery, window, document );

Update 2:

I've added callbacks. I've also decided to use $.noop in place of function() {}:

... typing $.noop is 6 chars shorter than function(){}. Also if you use this everywhere instead of creating new, anonymous, empty functions, you'll slightly cut down on memory. – Marco

Now I'm wondering what my callbacks should return in a utility plugin? Sending this doesn't seem that useful.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ To answer your last update's question, you should probably still return this to continue chaining. Yeah it might not seem useful, but it's what someone would expect if they just walked in on the plugin. \$\endgroup\$ Commented Feb 28, 2013 at 15:42
  • \$\begingroup\$ @JonnySooter If you look at my init method (in my plugin example above), I'm returning this at the end of init; is this a good spot to do that? In my last update, I was asking specifically about my callback methods; I just wanted to clarify about my init method as well. Thank you! \$\endgroup\$
    – mhulse
    Commented Mar 20, 2013 at 20:11
  • \$\begingroup\$ Answer to your comment is a bit long to fit here so I placed in answer. \$\endgroup\$ Commented Mar 21, 2013 at 14:16

2 Answers 2

3
\$\begingroup\$

What I suggest is read the jQuery source. Check out how they do it. In their init method they return this for certain cases, but not for others.

Like when there is no selector:

if ( !selector ) {
    return this;
}

But if you check out other "utility" plugins in their source like $.map

map: function( elems, callback, arg ) {
var value,
    i = 0,
    length = elems.length,
    isArray = isArraylike( elems ),
    ret = [];

    // Go through the array, translating each of the items to their
    if ( isArray ) {
        for ( ; i < length; i++ ) {
            value = callback( elems[ i ], i, arg );

            if ( value != null ) {
                ret[ ret.length ] = value;
            }
        }

    // Go through every key on the object,
    } else {
        for ( i in elems ) {
            value = callback( elems[ i ], i, arg );

            if ( value != null ) {
                ret[ ret.length ] = value;
            }
        }
    }

    // Flatten any nested arrays
    return core_concat.apply( [], ret );
},

Here they don't return the jQuery object, since this utility is for arrays. My point here is that there's no one shoe fits all. It depends on what you're trying to get from your plugin. Also chaining is expected but not on something like $.map().

Also keep in mind, in your callbacks this should refer to the element in question (ie. in a click callback this refers to the clicked element). If you're not playing with an element, this should refer to the global(window) object.

\$\endgroup\$
0
2
\$\begingroup\$

Late to the party I know but after looking into this for my own uses I found the jquery documentation for $.extend (http://api.jquery.com/jquery.extend/) seems to be the answer:

If only one argument is supplied to $.extend(), this means the target argument was omitted. In this case, the jQuery object itself is assumed to be the target. By doing this, you can add new functions to the jQuery namespace. This can be useful for plugin authors wishing to add new methods to JQuery.

I create a simple proof of concept:

http://jsbin.com/cixik/1/edit?js

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.