Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the ID of a control that my jQuery plugin is attached to

I am writing my first jQuery plugin. Here's the skeleton of the plugin:

(function( $ ) {
    var methods = {
        init : function( options ) { 
            var settings = $.extend( {
                'id' : '#' + this[0].id,
                'foo' : 3,       
                'bar' : 4,    
            }, options ); 
        }
   }

   $.fn.MyPlugin = 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 MyPlugin' );
      }
  };
})( jQuery );

I attach the plugin like this:

$( '#some-id' ).MyPlugin({
   'something' : 'something
});

which works fine for one control on one page but when I start doing this:

$( '#some-id-2' ).MyPlugin({
   'something' : 'something
});

$( '#some-id-3' ).MyPlugin({
   'something' : 'something
});

I get the following error:

Uncaught TypeError: Cannot read property 'id' of undefined

because I get my ID from this which evaluates to [] for the any control that is not on the page I am currently on!

What's the correct way to get the ID of the controls that the plugin is attached to?

Many thanks :).

like image 448
ale Avatar asked Dec 16 '22 03:12

ale


2 Answers

There's a fundamental issue here: You're thinking about an individual element. jQuery is all about sets of elements. The this in your plug-in is a jQuery instance, which may have 0..n matched elements in it. Could be zero, one, 27, 342, etc. So doing this[0].id doesn't really make sense in a jQuery plug-in, because you'd just get the id of the first matching element (if it even has one; there's no requirement that it must), or an error (as you discovered) when there aren't any elements in the set at all.

So if you start thinking about your plug-in as dealing with a set of matched elements, which may be an empty set, a small set, or a large set, you'll be on the right path.

Here's a concrete example: Let's write a plug-in that does a silly thing: It turns the element(s) a color, then reverts them to their original color after a timeout. We'll use it like this:

$("selector").plugin("newcolor", timeout)

If we're thinking in terms of individual elements, our plug-in won't work correctly:

// The "foo" plug-in thinks in terms of just one element
$.fn.foo = function(color, time) {
    var self, oldColor;

    self = this;

    // Save the current color -- this is wrong
    oldColor = self.css("color");

    // Set new color
    self.css("color", color);

    // Restore old color after timeout
    setTimeout(function() {
      self.css("color", oldColor);
    }, time);
};

Now let's use it:

$("#theButton").click(function() {
    $(".foo").foo("blue", 1000);
});

...with this HTML:

<div class="foo">This is the first "foo" div</div>
<div class="foo" style="color: green">This is the second "foo" div</div>
<div class="foo" style="color: red">This is the third "foo" div</div>
<div><input type="button" id="theButton" value="Use foo"></div>

The problem here is that by thinking in terms of one element, the plug-in incorrectly saves the color of only the first element; when it goes to "restore" the original color, it applies the first element's color to all the others, which is wrong. We end up with three divs using black text, where the second two shouldn't be black. Live example | Source

Instead, if we think in terms of sets, we'd do it like this (for example; not saying this is the prettiest code on the planet):

// The "bar" plug-in understands sets
$.fn.bar = function(color, time) {
    var self;

    self = this;

    // Get the current color of each element
    self.each(function() {
      var entry = $(this);
      entry.data("oldColor", entry.css("color"));
    });

    // Set new color
    self.css("color", color);

    // Restore old color after timeout
    setTimeout(function() {
      self.each(function() {
        var entry = $(this);
        entry.css("color", entry.data("oldColor")).removeData("oldColor");
      });
    }, time);
};

We'll use it like this (basically the same):

$("#theButton").click(function() {
    $(".bar").bar("blue", 1000);
});

...with this HTML (basically the same):

<div class="bar">This is the first "bar" div</div>
<div class="bar" style="color: green">This is the second "bar" div</div>
<div class="bar" style="color: red">This is the third "bar" div</div>
<div><input type="button" id="theButton" value="Use foo"></div>

Note how it's saving each element's color, and on the element itself via data. When restoring, it restores each element's color. Live example | Source

like image 147
T.J. Crowder Avatar answered May 27 '23 10:05

T.J. Crowder


You should implement your plugin using each, then you won't have problem with empty jQuery objects:

$.fn.MyPlugin = function( method ) {
  this.each(function() {
    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 MyPlugin' );
    }
  });
};
like image 30
sinsedrix Avatar answered May 27 '23 11:05

sinsedrix