Search code examples
javascriptjqueryjquery-pluginsprototypal-inheritance

How to extend a jquery plugin's public methods through its prototype?


How can I extend a plugin's public methods its prototype?

For instance, I have method1 in my plugin, and I want to add another and more through its .prototype. Is it possible?

var extensionMethods = {
   method2: function(){
        return this;
    }
};

$.fn.MyPlugin.prototype = extensionMethods;

console.log($(".element").MyPlugin());

result,

 Object { Element={...}, Options={...}, method1=function()}

Ideally,

 Object { Element={...}, Options={...}, method1=function(), method2=function(), method2function()}

my plugin boilerplate,

(function ($) {

    // Create the plugin name and defaults once
    var pluginName = 'MyPlugin';

    // Attach the plugin to jQuery namespace.
    $.fn[pluginName] = function(PublicOptions) {

        // Set private defaults.
        var Defaults = {
            param1:       'param1',
            param2:       'param2',
            onSuccess:    function(){}
        };

        // Do a deep copy of the options.
        var Options = $.extend(true, {}, Defaults, PublicOptions);

        // Define a functional object to hold the api.
        var PluginApi = function(Element, Options) {
            this.Element   = Element;
            this.Options   = Options;
        };

        // Define the public api and its public methods.
        PluginApi.prototype = {

            method1: function(PublicOptions) {

                // Process the options.
                var Options = $.extend(true, {}, this.Options, PublicOptions);
                return this.Options;
            }
        };

        //Create a new object of api.
        return new PluginApi(this, Options);
    };

})(jQuery);

Any ideas?


Solution

  • I think the best structure you can do in this case would not involve prototypes at all. Check this plugin base:

    (function($) {
    
       // Set private defaults.
        var Defaults = {
            param1: 'param1',
            param2: 'param2',
            onSuccess: function() {}
        };
    
        // Define the public api and its public methods.
        var PluginApi = {
    
            extend: function(name, method) {
                PluginApi[name] = method;
                return this;
            },
    
            init: function(PublicOptions) {
    
                // Do a deep copy of the options.
                var Options = $.extend(true, {}, Defaults, PublicOptions);
    
                return this.each(function() {
                    console.log('set up plugin logic', this.tagName);
                });
            },
    
            method1: function() {
                console.log('called: method1');
                return this;
            }
        };
    
        // Create the plugin name and defaults once
        var pluginName = 'MyPlugin';
    
        // Attach the plugin to jQuery namespace.
        $.fn[pluginName] = function(method) {
    
            if (PluginApi[method]) {
                return PluginApi[method].apply(this, Array.prototype.slice.call(arguments, 1));
            } 
            else if (typeof method === 'object' || !method) {
                return PluginApi.init.apply(this, arguments);
            } 
            else {
                $.error('Method ' + method + 'does not exist');
            }
        };
    
    })(jQuery);
    

    This plugin structure allows you to chain methods as expected:

    $('h1').MyPlugin('method1').css('color', 'red');
    

    In case of the need to use non-existent method you could do this:

    // Extend plugin "prototype" with method2 and use it
    $('h1, h2').MyPlugin('extend', 'method2', function(prop, value) {
        return this.css(prop, value);
    }).MyPlugin('method2', 'color', 'green');
    

    Check usage example in the demo below.

    (function($) {
    
       // Set private defaults.
        var Defaults = {
            param1: 'param1',
            param2: 'param2',
            onSuccess: function() {}
        };
    
        // Define the public api and its public methods.
        var PluginApi = {
    
            extend: function(name, method) {
                PluginApi[name] = method;
                return this;
            },
    
            init: function(PublicOptions) {
                
                // Do a deep copy of the options.
                var Options = $.extend(true, {}, Defaults, PublicOptions);
                
                return this.each(function() {
                    console.log('set up plugin logic', this.tagName);
                });
            },
    
            method1: function() {
                console.log('called: method1');
                return this;
            }
        };
    
        // Create the plugin name and defaults once
        var pluginName = 'MyPlugin';
    
        // Attach the plugin to jQuery namespace.
        $.fn[pluginName] = function(method) {
    
            if (PluginApi[method]) {
                return PluginApi[method].apply(this, Array.prototype.slice.call(arguments, 1));
            } 
            else if (typeof method === 'object' || !method) {
                return PluginApi.init.apply(this, arguments);
            } 
            else {
                $.error('Method ' + method + 'does not exist');
            }
        };
    
    })(jQuery);
    
    
    // Call existen method1: should make h1 and h2 red
    $('h1, h2').MyPlugin('method1').css('color', 'red');
    
    // Call non-existent method2: should throw error in console
    try {
        $('h1, h2').MyPlugin('method2').css('color', 'green');
    }
    catch (e) {
      // Extend "plugin" prototype with method2
      $('h1, h2').MyPlugin('extend', 'method2', function(prop, value) {
          return this.css(prop, value);
      }).MyPlugin('method2', 'color', 'green');
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <h1>H1</h1>
    <h2>H2</h2>

    Or it may be more optimal to define a static method extend within $[pluginName] namespace:

    // Attach the plugin to jQuery namespace.
    $.fn[pluginName] = function(method) {
    
        if (PluginApi[method]) {
            return PluginApi[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } 
        else if (typeof method === 'object' || !method) {
            return PluginApi.init.apply(this, arguments);
        } 
        else {
            $.error('Method ' + method + 'does not exist');
        }
    };
    
    $[pluginName] = {};
    $[pluginName].extend = function(name, method) {
        PluginApi[name] = method;
    };
    

    and then use it like this when necessary to add additional methods:

    $.MyPlugin.extend('method2', function(prop, value) {
        return this.css(prop, value);
    });
    
    $('h1, h2').MyPlugin('method2', 'color', 'green');
    

    Final demo: http://plnkr.co/edit/qqlfRqAM84goscU5BFNU?p=preview