jQuery plugin development - return this.each issue

2020-03-31 05:32发布

I'm trying to develop my first jQuery plugin. Basically it appends classes to various elements on site an then changes it when user scrolls (I'm calculating offsets and whatnot). And I think I've hit a wall with this one.

Here's how I start the plugin:

$("div").myPlugin();

And the source:

$.fn.myPlugin = function() {
  return this.each(function() {
   $(this).addClass("something-" + i);
   i++;
  });
};

Could someone explain please how to use $(window).scroll in my plugin?

Because once it into "return this.each" it gets attached as many times as many elements there are in this loop...

$.fn.myPlugin = function() {
  return this.each(function() {
   $(this).addClass("something-" + i);
   i++;

   $(window).scroll( function() { <!-- here it not only attaches itself x-times but i is always the same -->
    ...
    $(".something-" + i).addClass("active");
    ...
  });
  });
};

Putting it after return doesn't do nothing:

$.fn.myPlugin = function() {
  return this.each(function() {
   $(this).addClass("something-" + i);
   i++;
  });

  $(window).scroll( function() { <!-- doesn't seem to work -->
    ...
    $(".something-" + i).addClass("active");
    ...
  });
};

And before there are no "i"s, also I don't know if I can put anything outside of the "return" scope inside a function (doesn't seem valid to me?):

$.fn.myPlugin = function() {
  $(window).scroll( function() { <!-- there is no "i" yet -->
    ...
    $(".something-" + i).addClass("active");
    ...
  });

  return this.each(function() {
   $(this).addClass("something-" + i);
   i++;
  });
};

I'm new to jQuery and I'm not sure if I understand the documentation correctly, I was wondering wether it might be better to do not use return here at all? Note this plugin can work with 1 element but usually there will be more divs than 1.

Thanks a lot.

2条回答
做自己的国王
2楼-- · 2020-03-31 06:21

There are probably several ways to write the plugin. Here are the characteristics of what seems most appropriate :

  • Maintain an internal jQuery collection containing those elements currently associated with the plugin.
  • Allow the internal collection to be added to with an 'add' method and depleted by a 'remove' method (both public).
  • Don't loop when the plugin is invoked. Simply call the 'add' method and return this - no need for .each() (at least, not overtly).
  • Attach the scroll handler only while the jQuery collection is not empty - monitor the internal collection's length and attach/detach accordingly.

Please note that these characteristics, when coded, will be in a very atypical plugin pattern. Therefore, this is probably not the best problem to choose for your first plugin.

However, here goes ...

(function($, window, undefined) {
    // Private vars
    var pluginName = 'myPlugin',
        jqCollection = $(),
        events = {
            scroll: 'scroll.' + pluginName
        },
        className = pluginName + 'active',
        timeout;
    // Private functions
    function scroll() {
        if(jqCollection.closest('body').length === 0) { 
            //This is advisable in case some other javascript/DOM activity has removed all elements in jqCollection.
            // Thanks to selected answer at :
            // http://stackoverflow.com/questions/4040715/check-if-cached-jquery-object-is-still-in-dom
            jqCollection = $();
            $(window).off(events.scroll);
        } else {
            jqCollection.addClass(className);
            clearTimeout(timeout);
            timeout = setTimeout(function() {
                jqCollection.removeClass(className);
            }, 100);
        }
    }
    // Public methods
    methods = {
        add: function(jq) {
            jqCollection = jqCollection.add(jq);
            if(jqCollection.length > 0) {
                $(window).off(events.scroll).on(events.scroll, scroll)
            }
        },
        remove: function(jq) {
            jqCollection = jqCollection.not(jq);
            if(jqCollection.length === 0) {
                $(window).off(events.scroll);
            }
        }
    };
    // The actual plugin
    $.fn[pluginName] = function(method) {
        method = method || 'add';
        if(methods[method]) {
            methods[method](this);
        } else {
            console.log('jQuery plugin ' + pluginName + 'has no method: ' + method);
        };
        return this;
    };
})(jQuery, window);

Associate selected elements as follows :

$(selector).myPlugin();
//or
$(selector).myPlugin('add');

Dissociate selected elements as follows :

$(selector).myPlugin('remove');

DEMO

Note that I changed the class name to pluginName + 'active' to make it far less likely to be used by some other piece of code.

As I said, that's one way to do it. Maybe you can think of improvements or some other way entirely.

查看更多
家丑人穷心不美
3楼-- · 2020-03-31 06:32

How about this:

(function($, window, undefined){

    $.fn.myPlugin = function() {

        // bind the scroll event listener once
        $(window).scroll(function(){ 
            console.log('scroll');
        });

        // iterate your plugin elements
        // use index passed by the .each callback:
        this.each(function(i, el){   
            $(el).addClass('something-' + i);
        });

        // return your jQuery object to allow chaining on your plugin
        // note that (this instanceof jQuery) === true, so there is no need
        // to pass it to the jQuery function i.e. $(this)
        return this; 

    };

})(jQuery, window);

$('div').myPlugin();

console.log($('div').map(function(){ return this.className; }).get());

http://jsfiddle.net/yw0q5hn7/2/

查看更多
登录 后发表回答