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 :).
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
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' );
}
});
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With