views:

1235

answers:

3

Hi, I'm trying to write a jQuery plugin that will provide additional functions/methods to the object that calls it. All the tutorials I read online (have been browsing for the past 2 hours) include, at the most, how to add options, but not additional functions.

Here's what I am looking to do:

//format div to be a message container by calling the plugin for that div

$("#mydiv").messagePlugin();
$("#mydiv").messagePlugin().saySomething("hello");

or something along those lines. Here's what it boils down to: I call the plugin, then I call a function associated with that plugin. I can't seem to find a way to do this, and I've seen many plugins do it before.

Here's what I have so far for the plugin:

jQuery.fn.messagePlugin = function() {
  return this.each(function(){
    alert(this);
  });

  //i tried to do this, but it does not seem to work
  jQuery.fn.messagePlugin.saySomething = function(message){
    $(this).html(message);
  }
};

How can I achieve something like that?

Thank you!

+5  A: 

Here's the pattern I have used for creating plugins with additional methods. You would use it like:

$('selector').myplugin( { key: 'value' } );

or, to invoke a method directly,

$('selector').myplugin( 'mymethod1', 'argument' );

Example:

;(function($) {

    $.fn.extend({
        myplugin: function(options,arg) {
            if (options && typeof(options) == 'object') {
                options = $.extend( {}, $.myplugin.defaults, options );
            }

            // this creates a plugin for each element in
            // the selector or runs the function once per
            // selector.  To have it do so for just the
            // first element (once), return false after
            // creating the plugin to stop the each iteration 
            this.each(function() {
                new $.myplugin(this, options, arg );
            });
            return;
        }
    });

    $.myplugin = function( elem, options, arg ) {

        if (options && typeof(options) == 'string') {
           if (options == 'mymethod1') {
               myplugin_method1( arg );
           }
           else if (options == 'mymethod2') {
               myplugin_method2( arg );
           }
           return;
        }

        ...normal plugin actions...

        function myplugin_method1(arg)
        {
            ...do method1 with this and arg
        }

        function myplugin_method2(arg)
        {
            ...do method2 with this and arg
        }

    };

    $.myplugin.defaults = {
       ...
    };

})(jQuery);
tvanfosson
same pattern as jquery-ui, i dont like all the magic strings but is there any other way!
redsquare
this seems like a non-standard way of doing things - is there anything simpler than this, like chaining functions?thank you!
yuval
also, how would i pass more than one argument under the arg - as an object, perhaps? (i.e. $('selector').myplugin('mymethod1', {arg1 : 'arg1value', arg2 : 'arg2value}) ). thank you!
yuval
one potential problem(?) that i noticed: when creating the new pluginnew $.myplugin(this, options, arg), you pass 3 arguments, while the plugin itself seems to only expect 2 (i.e $.myplugin = function( options, arg)) - is this done on purpose? if not, how do i fix it?thanks!
yuval
@yuval -- typically jQuery plugins return jQuery or a value, not the plugin itself. That's why the name of the method is passed as an argument to the plugin when you want to invoke the plugin. You could pass any number of arguments, but you'll have to adjust the functions and the argument parsing. Probably best to set them in an anonymous object as you showed.
tvanfosson
@yuval -- a transliteration mistake when extracting the code from an actual plugin I have. I've corrected it.
tvanfosson
+3  A: 

What about this approach:

jQuery.fn.messagePlugin = function(){
    var selectedObjects = this;
    return {
             saySomething : function(message){
                              $(selectedObjects).each(function(){
                                $(this).html(message);
                              });
                              return selectedObjects; // Preserve the jQuery chainability 
                            },
             anotherAction : function(){
                               //...
                               return selectedObjects;
                             }
           };
}
// Usage:
$('p').messagePlugin().saySomething('I am a Paragraph').css('color', 'red');

The selected objects are stored in the messagePlugin closure, and that function returns an object that contains the functions associated with the plugin, the in each function you can perform the desired actions to the currently selected objects.

You can test and play with the code here.

Edit: Updated code to preserve the power of the jQuery chainability.

CMS
i am having a bit of a hard time understanding what this would look like. Assuming i have code that needs to be executed the first time this is run, i'll have to first initialize it in my code - something like this: $('p').messagePlugin(); then later in the code i'd like to call the function saySomething like this $('p').messagePlugin().saySomething('something'); will this not re-initialize the plugin and then call the function? what would this look like with the enclosure and options? thank you very much. -yuval
yuval
Sort of breaks the chainability paradigm of jQuery, though.
tvanfosson
@tvanfosson: completely agree, fixed code...
CMS
A: 

A simpler approach is to use nested functions. Then you can chain them in an object-oriented fashion. Example:

jQuery.fn.MyPlugin = function()
{
  var _this = this;
  var a = 1;

  jQuery.fn.MyPlugin.DoSomething = function()
  {
    var b = a;
    var c = 2;

    jQuery.fn.MyPlugin.DoSomething.DoEvenMore = function()
    {
      var d = a;
      var e = c;
      var f = 3;
      return _this;
    };

    return _this;
  };

  return this;
};

And here's how to call it:

var pluginContainer = $("#divSomeContainer");
pluginContainer.MyPlugin();
pluginContainer.MyPlugin.DoSomething();
pluginContainer.MyPlugin.DoSomething.DoEvenMore();

Be careful though. You cannot call a nested function until it has been created. So you cannot do this:

var pluginContainer = $("#divSomeContainer");
pluginContainer.MyPlugin();
pluginContainer.MyPlugin.DoSomething.DoEvenMore();
pluginContainer.MyPlugin.DoSomething();

The DoEvenMore function doesn't even exist because the DoSomething function hasn't been run yet which is required to create the DoEvenMore function. For most jQuery plugins, you really are only going to have one level of nested functions and not two as I've shown here. Just make sure that when you create nested functions that you define these functions at the beginning of their parent function before any other code in the parent function gets executed.

Finally, note that the "this" member is stored in a variable called "_this". For nested functions, you should return "_this" if you need a reference to the instance in the calling client. You cannot just return "this" in the nested function because that will return a reference to the function and not the jQuery instance. Returning a jQuery reference allows you to chain intrinsic jQuery methods on return.

Polaris431