1

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.

Wordpressor
  • 7,173
  • 23
  • 69
  • 108

2 Answers2

2

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/

filur
  • 2,116
  • 6
  • 24
  • 47
0

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.

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44