So you've become comfortable with jQuery and would like to learn how to write your own plugins. Great! You're in the right spot. Extending jQuery with plugins and methods is very powerful and can save you and your peers a lot of development time by abstracting your most clever functions into plugins. This post will outline the basics, best practices, and common pitfalls to watch out for as you begin writing your plugin.
Getting Started
To write a jQuery plugin, start by adding a new function property to the jQuery.fn object where the name of the property is the name of your plugin:
jQuery.fn.myPlugin = function() {
// Do your awesome plugin stuff here
};
But wait! Where's my awesome dollar sign that I know and love? It's still there, however to make sure that your plugin doesn't collide with other libraries that might use the dollar sign, it's a best practice to pass jQuery to a self executing function (closure) that maps it to the dollar sign so it can't be overwritten by another library in the scope of its execution.
(function( $ ){
$.fn.myPlugin = function() {
// Do your awesome plugin stuff here
};
})( jQuery );
Ah, that's better. Now within that closure, we can use the dollar sign in place of jQuery as much as we like.
Context
Now that we have our shell we can start writing our actual plugin code. But before we do that, I'd like to say a word about context. In the immediate scope of the plugin function, the this
keyword refers to the jQuery object the plugin was invoked on. This is a common slip up due to the fact that in other instances where jQuery accepts a callback, the this
keyword refers to the native DOM element. This often leads to developers unnecessarily wrapping the this
keyword (again) in the jQuery function.
(function( $ ){
$.fn.myPlugin = function() {
// there's no need to do $(this) because
// "this" is already a jquery object
// $(this) would be the same as $($('#element'));
this.fadeIn('normal', function(){
// the this keyword is a DOM element
});
};
})( jQuery );
$('#element').myPlugin();
The Basics
Now that we understand the context of jQuery plugins, let's write a plugin that actually does something.
(function( $ ){
$.fn.maxHeight = function() {
var max = 0;
this.each(function() {
max = Math.max( max, $(this).height() );
});
return max;
};
})( jQuery );
var tallest = $('div').maxHeight(); // Returns the height of the tallest div
This is a simple plugin that leverages .height()
to return the height of the tallest div in the page.
Maintaining Chainability
The previous example returns an integer value of the tallest div on the page, but often times the intent of a plugin is simply modify the collection of elements in some way, and pass them along to the next method in the chain. This is the beauty of jQuery's design and is one of the reasons jQuery is so popular. So to maintain chainability in a plugin, you must make sure your plugin returns thethis
keyword.
(function( $ ){
$.fn.lockDimensions = function( type ) {
return this.each(function() {
var $this = $(this);
if ( !type || type == 'width' ) {
$this.width( $this.width() );
}
if ( !type || type == 'height' ) {
$this.height( $this.height() );
}
});
};
})( jQuery );
$('div').lockDimensions('width').css('color', 'red');
Because the plugin returns the this
keyword in its immediate scope, it maintains chainability and the jQuery collection can continue to be manipulated by jQuery methods, such as .css
. So if your plugin doesn't return an intrinsic value, you should always return the this
keyword in the immediate scope of the plugin function. Also, as you might assume, arguments you pass in your plugin invocation get passed to the immediate scope of the plugin function. So in the previous example, the string 'width' becomes the type argument for the plugin function.
Defaults and Options
For more complex and customizable plugins that provide many options, it's a best practice to have default settings that can get extended (using $.extend
) when the plugin is invoked. So instead of calling a plugin with a large number of arguments, you can call it with one argument which is an object literal of the settings you would like to override. Here's how you do it.
(function( $ ){
$.fn.tooltip = function( options ) {
var settings = {
'location' : 'top',
'background-color' : 'blue'
};
return this.each(function() {
// If options exist, lets merge them
// with our default settings
if ( options ) {
$.extend( settings, options );
}
// Tooltip plugin code here
});
};
})( jQuery );
$('div').tooltip({
'location' : 'left'
});
In this example, after calling the tooltip plugin with the given options, the default location setting gets overridden to become 'left'
, while the background-color setting remains the default 'blue'
. So the final settings object ends up looking like this:
{
'location' : 'left',
'background-color' : 'blue'
}
This is a great way to offer a highly configurable plugin without requiring the developer to define all available options.
Namespacing
Properly namespacing your plugin is a very important part of plugin development. Namespacing correctly assures that your plugin will have a very low chance of being overwritten by other plugins or code living on the same page. Namespacing also makes your life easier as a plugin developer because it helps you keep better track of your methods, events and data.
Plugin Methods
Under no circumstance should a single plugin ever claim more than one namespace in the jQuery.fn
object.
(function( $ ){
$.fn.tooltip = function( options ) { // THIS };
$.fn.tooltipShow = function( ) { // IS };
$.fn.tooltipHide = function( ) { // BAD };
$.fn.tooltipUpdate = function( content ) { // !!! };
})( jQuery );
This is a discouraged because it clutters up the $.fn
namespace. To remedy this, you should collect all of your plugin's methods in an object literal and call them by passing the string name of the method to the plugin.
(function( $ ){
var methods = {
init : function( options ) { // THIS },
show : function( ) { // IS },
hide : function( ) { // GOOD },
update : function( content ) { // !!! }
};
$.fn.tooltip = function( method ) {
// Method calling logic
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.tooltip' );
}
};
})( jQuery );
$('div').tooltip(); // calls the init method
$('div').tooltip({ // calls the init method
foo : 'bar'
});
$('div').tooltip('hide'); // calls the hide method
$('div').tooltip('update', 'This is the new tooltip content!'); // calls the update method
This type of plugin architecture allows you to encapsulate all of your methods in the plugin's parent closure, and call them by first passing the string name of the method, and then passing any additional parameters you might need for that method. This type of method encapsulation and architecture is a standard in the jQuery plugin community and it used by countless plugins, including the plugins and widgets in jQueryUI.
Events
A lesser known feature of the bind method is that is allows for namespacing of bound events. If your plugin binds an event, its a good practice to namespace it. This way, if you need to unbind it later, you can do so without interfering with other events that might have been bound to the same type of event. You can namespace your events by appending “.<namespace>” to the type of event you're binding.
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);
});
},
destroy : function( ) {
return this.each(function(){
$(window).unbind('.tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = 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.tooltip' );
}
};
})( jQuery );
$('#fun').tooltip();
// Some time later...
$('#fun').tooltip('destroy');
In this example, when the tooltip is initialized with the init method, it binds the reposition method to the resize event of the window under the namespace 'tooltip'. Later, if the developer needs to destroy the tooltip, we can unbind the events bound by the plugin by passing its namespace, in this case 'tooltip', to the unbind method. This allows us to safely unbind plugin events without accidentally unbinding events that may have been bound outside of the plugin.
Data
Often times in plugin development, you may need to maintain state or check if your plugin has already been initialized on a given element. Using jQuery's data method is a great way to keep track of variables on a per element basis. However, rather than keeping track of a bunch of separate data calls with different names, it's best to use a single object literal to house all of your variables, and access that object by a single data namespace.
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
/*
Do more setup stuff here
*/
$(this).data('tooltip', {
target : $this,
tooltip : tooltip
});
}
});
},
destroy : function( ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip');
// Namespacing FTW
$(window).unbind('.tooltip');
data.tooltip.remove();
$this.removeData('tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = 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.tooltip' );
}
};
})( jQuery );
Using data helps you keep track of variables and state across method calls from your plugin. Namespacing your data into one object literal makes it easy to access all of your plugin's properties from one central location, as well as reducing the data namespace which allows for easy removal if need be.
Summary and Best Practices
Writing jQuery plugins allows you to make the most out of the library and abstract your most clever and useful functions out into reusable code that can save you time and make your development even more efficient. Here's a brief summary of the post and what to keep in mind when developing your next jQuery plugin:
- Always wrap your plugin in
(function( $ ){ // plugin goes here })( jQuery );
- Don't redundantly wrap the
this
keyword in the immediate scope of your plugin's function
- Unless you're returning an intrinsic value from your plugin, always have your plugin's function return the
this
keyword to maintain chainability.
- Rather than requiring a lengthy amount of arguments, pass your plugin settings in an object literal that can be extended over the plugin's defaults.
- Don't clutter the
jQuery.fn
object with more than one namespace per plugin.
- Always namespace your methods, events and data.
-
jQuery.fn
is pronounced jQuery effin'
- my ps:看完了jquery官方的插件编写文档,先摘起来,有空翻译一下....
相关推荐
查了一下jq的官方插件编写文档(http://docs.jquery.com/Plugins/Authoring)以及文档中推荐的Mike Alsup写的一篇A Plugin Development Pattern。英语不是很好,但还是努力看下来(既学习到知识又能练习英语,...
jQuery1.2 API 中文版折叠展开折叠全部展开全部 英文说明 核心jQuery 核心函数 jQuery(expression,[context]) jQuery(expression,[context]) 这个函数接收一个包含 CSS 选择器的字符串,然后用这个字符串去匹配一组...
Author James Cryer takes you from initial installation all the way through to authoring successful plugins. Using hands-on examples you will learn about CSS linting, combination, compilation and ...
Get familiar with using plugins, and extend PostCSS with the API Build a fully working custom preprocessor and test it on different sites such as WordPress Write a custom syntax in PostCSS while still...
Ajax 属性(Attributes) 核心(Core) CSS 数据(Data) 延迟对象(Deferred object) ...插件编写(Plugin Authoring) Plugins 属性(Properties) 选择器(Selectors) 遍历(Traversing) 工具(Utilities)
详细介绍了Tiny项目的内部结构,包括场景和子场景(Scenes and Subscenes)、游戏对象和行为(GameObject and Behavior Authoring)、"RootAssembly",以及***子集等。 8. 构建配置: 讲解了如何设置不同类型的构建...
v3.17 * updated libFLAC to version 1.2.1 * added a flush after every log line to help GUIs * "eac3to some.mpls" now also works if the stream files aren't there, anymore * fixed: number of subtitles ...
它可能提供了一个图形用户界面,让用户可以加载SWF文件,查看其内部结构,并可能支持导出为FLA(Flash Authoring File)或其他可编辑格式。 3. **SwfModify.exe.manifest**:这是一个应用程序清单文件,用于指定...
- **Authoring Libraries**:编写库时,需要考虑如何让使用者能够方便地使用这些库。 - **改善构建性能**:Webpack 提供了一系列策略来加快构建速度,包括使用更高效的 loader、并行构建等。 #### 处理兼容性问题 ...