简体   繁体   中英

Is there a better way/pattern to write this jQuery plugin?

I have a search plugin that is decently complex: it has different versions of UI and functionality as well as a bunch in interdependent domElements. Multiple instances of the plugin will exist on a page at once.

I am using the basic jQuery authoring pattern: http://docs.jquery.com/Plugins/Authoring

In order to save the options, interdependent events and and all sorts of dom lookups across multiple objects, I've come to passing the element in question to every function, and storing state/options/interdependencies in a data attribute which I retrieve each time. It works, and keeps events from colliding, but it seems like a messy way to write code.

What is the best way to store state across multiple instances? Is the way I am doing it a huge overkill and I am missing something? It probably stems from my misunderstanding of creating class like objects in a jQuery plugin pattern.

(function($) {
var _options = {};

var methods = {
    init: function(options) {
        return this.each(function() {
            if (options) {
                _options = $.extend($.fn.examplePlugin.defaults, options);
            } else {
                _options = $.fn.examplePlugin.defaults;
            }
            $this = $(this);
            var data = $this.data('examplePlugin');
            if (!data) {

                $this.data('examplePlugin', {
                    target: $this
                });
                $.each(_options, function(key, value){
                    $this.data('examplePlugin')[key] = value;
                });
                data = $this.data('examplePlugin');
            }
            //Cache dom fragment plugin is in (if passed)
            if (data.domContextSelector == null || data.domContextSelector == "") {
                data.domContext = $(body);
            } else {
                data.domContext = $(data.domContextSelector);
            }
            init($this);
        });
    }
};
var init = function(element) {
    data = getData(element);
    //Storing dom elements to avoid lookups
    data.relatedElement = $(data.relatedElementSelector, data.domContext);
    element.click(function(event){
        doSomethingCool($(event.currentTarget));
    });
};
var doSomethingCool = function(element) {
    data = getData(element);
    element.slideUp();
    data.relatedElement.slideDown();
};
var adjustHeight = function(element) {
    data = getData(element);
    element.height(data.relatedElement.height());
};
var getData = function(element) {
    return $(element).data('examplePlugin');
};

$.fn.examplePlugin = 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.examplePlugin');
    }
    return false;
};
$.fn.examplePlugin.defaults = {
    defaultA: 'something',
    relatedElementSelector: '#related',
    domContextSelector: 'header.header'
};})(jQuery);

Yup, if you follow the jQuery guide, you are building it according to how it's supposed to be built and taking advantage of what it was designed to do (especially chaining).

However, I don't necessarily follow that path. There are a lot of ways you can do these plugins, take for example this guy who made a boilerplate for jQuery plugins which are NOT based on jQuery's design but rather in the OOP perspective (which I prefer). I see it as cleaner, but has the sacrifice of not following the usual syntax (the element.myPlugin({options}) and not being able to chain (until you modify a bit)

The same guy has an older post which is a boilerplate for the usual jQuery plugin design.

Personally, I suggest sticking to what the jQuery team recommends , in terms of plugin design patterns. It helps keeps consistency, and makes your plugin more community friendly.

Having said that...

I've run into the problem of trying to keep the state of multiple elements as well. One solution I've found is to use the jQuery Data API (which looks like this: $( selector ).data( key, value ) ) to keep meta information like an element's state or the application state.

The nice thing about using data() is that it's not updating/acessing the DOM, rather it's using jQuery's internal meta stuff, so it's faster to access than trying to store info hidden input fields, changing class names, or doing other funky tricks that developers have tried to use to store data on the clientside. ( Keep in mind too that you don't need to use the HTML5 doctype to use the data API, but if you do data-*key attributes are extremely helpful ! )

It gets tricky when all the elements have their own states but the current element is the one that is controlling the overall plugin state. For this scenario I use the body tag to store data bout the current element , something like this:

    $('body').data('myPluginNameSpace.current', selectorRef );

That way, when I need to check the state of my plugin/page/application, or listen for my plugin-specific event that's bubbled up to the document object, I can do a quick lookup for the current/selected element, and apply any UI changes or behaviors to it:

    var currentElementRef = $('body').data('myPluginNameSpace.current');
    doFunStuff( currElementRef );

There are a number of other ways you can do this too, like creating a custom Event object and attaching custom parameters to it :

    var myPluginEvent = jQuery.Event( 'customEvent.myPluginNameSpace', { myProp : myValue });
    $( document ).trigger( myPluginEvent );

When your custom Event gets triggered and later handled via a callback function, your custom parameters are attached to the Event Object passed to the handler:

   $( document ).on( 'customEvent.myPluginNameSpace', function( e ){
      doStuff( e.myProp ); //you can access your custom properties attach to the event
    });

You can get to the same destination via many, different roads; that's the beauty and horror of JavaScript.

In your particular case keep in mind that you don't have to have everything running inside return this.each({ }) portion of the methods.init function for your plugin:

For example, unless you are setting specific options for each element, I would take out the part where you're extending the options object for every element!

var methods = {
    init: function(options) {
        //DO OPTIONS/EVENTLISTENER/etc STUFF OUT HERE
        return this.each(function() {
            //DONT DO THIS
            if (options) {
                _options = $.extend($.fn.examplePlugin.defaults, options);
            } else {
                _options = $.fn.examplePlugin.defaults;
            }

Try this instead:

...
    var methods = {

        init : function( options ){

          //do setup type stuff for the entire Plugin out here
          var _options = $.MyPlugin.options = $.extend( defaults, options );

          //add some listeners to $(document) that will later be handled               
          //but put them in an external function to keep things organized:
          //methods.addListeners() 

          //this refers to the array of elements returned by $(selector).myPlugin();
          //this.each() iterates over, EACH element, and does everything inside (similar to Array.map())
          //if the selector has 100 elements youre gonna do whats in here 100 times
          return this.each(function(){
            //do function calls for individual elements here        
          });

        },

Also, taking advantage of custom events will help you! Add some event listeners to the document object, and let the event handlers figure out which element to interact with using the data API or custom event parameters.

I've found your tweet when checking how my plugin saves a state, while learning plugin developing along this tutorial: http://tutsplus.com/lesson/head-first-into-plugin-development/

In this massive lesson, we'll dive into jQuery plugin development. Along the way, we'll review various best practices and techniques for providing the highest level of flexibility for the users of your plugins.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM