jQuery插件开发模式汇总

本文来自国外:jQuery Plugin Pattern,讲解了几种jQuery插件开发模式以及引导你怎样去创建一个简单的jQuery插件。

jQuery plugin is an extension to jQuery that encapsulates an ability or visual behaveiour so that it can be used later and in many different parts in a web application. This article will explain the basics of jQuery plugin and guide you how to create a simple jQuery plugin.

Two jQuery Plugin Types

I distinguish between two types of of jQuery plugins:

Plugin that works on element. For example, a plugin that converts <select> element to an autocomplete. Such plugin is working on the select element. This kind of plugin is actually extention to the jQuery prototype (or $.fn):

Extention to $.fn

$.fn.myPlugin = function() {
... //plugin content
}

Invocation of such plugins looks like:

Plugin invocation

$('#my-elem').myPlugin();

Plugin that doesn’t work on element. The utilities of jQuery are good examples for such plugin. They are actually functions that located in the jQuery object (or $):

Extention to $

$.myPlugin = function() {
... //plugin content
}

Invocation of such plugins looks like:

Plugin invocation

$.myPlugin();

Both types of plugins can get data as input, make DOM manipulations, make calculations, let the user interact them and much more.

Limit The Scope

Usually when writing jQuery plugin (or any JavaScript code), it is a good idea to limit it’s scope. This way you can prevent access to private variables and functions. In addition, using scopes may helping prevent naming conflicts. In order to limit the scope of jQuery plugin, wrap it with a function and invoke it. For example:

Plugin inside IIFE

(function() {
$.fn.myPlugin = function() {
... //plugin content
}
})();

This is called Immediately-Invoked Function Expression (IIFE).

The Dollar Sign

The dollar sign ($) is a synonym to the jQuery library. It is shorter and look better then the “jQuery” word. Because of that, there are many other libraries that make a use with the dollar sign as a synonym. So, we have to be sure our plugin doesn’t collide with other libraries. Therefore passing jQuery as a parameter to the IIFE is a best practice:

Passing jQuery as a parameter to the IIFE

(function($) {
$.fn.myPlugin = function() { // Here we sure $ is jQuery
... // Plugin content
}
})(jQuery);

Plugin Parameters and Defaults

We can pass parameters to our plugin when calling it, for example:

Pass parameters to our plugin

$('#elem').myPlugin(param1, param2, param3);

But, sometimes our plugin will have a lot of parameters (for instance, jqGrid plugin has more then 20 parameters) and some of them might be optionals. For this reason we wrap all the parameters in an object. For example, assume our plugin gets parameters “name”, “address” and “color”, we will define our plugin:

Define jQuery plugin with options

$.fn.myPlugin = function(options) { ... }

and for calling it:

Call jQuery plugin with options

$('#elem').myPlugin({
name: 'Naor',
address: 'Jerusalem',
color: 'Green'
});

This way the user can supply only the parameters he wants. But this leads to another problem. What if the plugin need the color parameter which wasn’t supplied? The solution is simple. All we have to do is to make a defaults to the parameters:

Options with defaults

(function($) {
$.fn.myPlugin = function(options) {
options = $.extend({
name: 'no-name',
address: 'none',
color: 'white'
}, options);
... // The rest of the plugin
}
})(jQuery);

This way we support many options with optional parameters.
In case we want to force the user pass some parameters, we can use the old way for the compulsory parameters and an “options” object for the optionals:

Compulsory and optionals parameters

// param1 is compulsory
$.fn.myPlugin = function(param1, options) { ... }

The this Expression

Inside a plugin definition the this expression has a different meaning between the two plugin types. I’ll explain the meaning of the this expression using examples:

The this expression for plugins that do not work on an element

The “this” expression download

<!doctype html>
<html>
<head>
<title>jQuery Plugins</title>
<script type="text/javascript" src="../assets/js/jquery-1.9.0.min.js"></script>
<script type="text/javascript">
(function($) {
$.myPlugin = function() {
// Here <i>this</i> represents the jQuery object
return $ === this;
};
})(jQuery);
</script>
</head>
<body>
Is <i>this</i> equals jQuery? <span id="isEqual" />
<script type="text/javascript">
$(function() {
$('#isEqual').text($.myPlugin());
});
</script>
</body>
</html>

Notice that inside such plugin, this is equal to jQuery. You can watch this example here.

The this expression for plugins that do work on an element

The “this” expression download

<!doctype html>
<html>
<head>
<title>jQuery Plugins</title>
<script type="text/javascript" src="../assets/js/jquery-1.9.0.min.js"></script>
<script type="text/javascript">
(function($) {
$.fn.myPlugin = function(text) {
// Here <i>this</i> is a reference to the actaul jQuery
// element the plugin works on.
this.each(function() {
var current = $(this);
var elemType = current.prop('tagName').toLowerCase();
switch(elemType) {
case 'input':
current.val(text);
break;
case 'select':
current.empty().append('<option>' + text + '</option>');
break;
case 'span':
current.text(text);
break;
}

});
};
})(jQuery);
</script>
</head>
<body>
<p>
Choose text: <input type="text" id="my-text" />
<button id="my-button">Press here</button>
</p>
<p>
Span: <span class="target"></span> <br/>
Text input: <input type="text" class="target" /> <br/>
Select: <select class="target"></select>
</p>

<script type="text/javascript">
$(function() {
$('#my-button').click(function() {
var text = $('#my-text').val();
$('.target').myPlugin(text);
});
});
</script>
</body>
</html>

Notice that this is a reference to the main element that the plugin works on. Sometimes, like in this example, the jQuery element represents more then one DOM element and we have to iterate each one of them in order to effect all of the DOM elements. In this example each DOM element is different element and so different treatment. You can watch this example here.

jQuery Chaining Principal

jQuery Chaining Principal is relevant only to plugins that do work on an element.

Take this code for example:

jQuery without chaining

$('#elem').addClass('active');
$('#elem').val('some value');
$('#elem').prop('disabled', true);

This code adds css class to an element, sets its value and disables it. Instead of three different lines of code we can write: I believe you’ve seen this syntax before:

jQuery chaining

$(‘#elem’).addClass(‘active’).val(‘some value’).prop(‘disabled’, true);
This looks better, easier to understand and more effective (no need to search for ‘#elem’ a few times). This is made possible due to the jQuery chaining principal. Each jQuery method or plugin returns the element or elements that it works on:

jQuery chaining principal

(function($) {
$.fn.myPlugin = function(options) {
...
...
return this; // This line responsible for chaining
}
})(jQuery);

Remember that inside the plugin scope, the this expression referenced to the element itself.

User Interface

Up to now we saw a plugin structure wrapped in IIFE, with $ as jQuery and with compulsory/optional parameters. We undertsood the this expression inside a plugin and saw the chaining principal in action. Now we need to see how to create an interface so the user can interact with the plugin. I’ll do it separately for each plugin type.

Plugin that doesn’t work on element

The first plugin doesn’t work on element, it gets positions and a text as parameters and displays the text on the specified position:

float plugin

(function($) {
$.float = function(posX, posY, text) {
$('<div>'+text+'</div>').appendTo('body').css({
left: posX,
top: posY,
position: 'absolute'
});
}
})(jQuery);

Now we want to allow the user to move the text to a new position and to remove it. Let’s write methods:

changePosition() and remove() methods

(function($) {
function changePosition(elem, posX, posY) {
elem.css({
left: posX,
top: posY
});
}

function remove(elem) {
elem.remove();
}

$.float = function(posX, posY, text) {
$('<div>'+text+'</div>').appendTo('body').css({
left: posX,
top: posY,
position: 'absolute'
});
}
})(jQuery);

Notice that the user doesn’t have an access to “changePosition” nor “remove” and he never holds the <div> element. So now we need to connect the user to the methods. In order to do it we make the “float” plugin return a “remote control” object:

jQuery plugin returns “remote control” object

(function($) {
function changePosition(elem, posX, posY) {
elem.css({
left: posX,
top: posY
});
}

function remove(elem) {
elem.remove();
}

$.float = function(posX, posY, text) {
var elem = $('<div>'+text+'</div>').appendTo('body').css({
left: posX,
top: posY,
position: 'absolute'
});

return {
changePosition: function(posX, posY) {
changePosition(elem, posX, posY);
},
remove: function() { remove(elem); }
};
}
})(jQuery);

Now, whenever the user will invoke $.float(..) he will get a “remote control” object with the interface we want to provide, and in order to use it:

Interact with a plugin

var control = $.float('100px', '100px', 'Hello!');
control.changePosition('200px', '200px');

Live example for the float plugin you can find here.

Plugin that does work on element

The second plugin does work on element. It works on an <input> element and gets two parameters: <ul> selector and a number “N”. Whenever the input’s value changes, the <ul> gets filled with “N” items containing the value:

Compose plugin

(function($) {
$.fn.compose = function(options) {
options = $.extend({
number: 2,
ul: null
}, options);

this.change(function() {
if (options.ul !== null) {
var value = $(this).val();
var ul = $(options.ul).empty();
for(var i=0;i<options.number;i++) {
ul.append('<li>' + value + '</li>')
}
}
});

return this;
}
})(jQuery);

Now we want to allow the user to change the number parameter “N”. Again, let’s write a method for changing the parameter:

setNumber() method

(function($) {
$.fn.compose = function(options) {
options = $.extend({
number: 2,
ul: null
}, options);

function setNumber(number) {
options.number = number;
}

this.change(function() {
if (options.ul !== null) {
var value = $(this).val();
var ul = $(options.ul).empty();
for(var i=0;i<options.number;i++) {
ul.append('<li>' + value + '</li>')
}
}
});

return this;
}
})(jQuery);

Like the former plugin example, the user doesn’t have an access to “setNumber” method. Unlike the former plugin example, here we cannot return a “remote control” object. Due to jQuery chaining principal we have to return this. For solving this we use jQuery.data() method. This method allows us attach key-value data to an element. For example, $(‘#elem’).data(‘my-color’, ‘Green’); attaches the “my-color = Green” key-value to the element. In order to get the value of “my-color” all we have to do is: $(‘#elem’).data(‘my-color’) and we get “Green”. So we use the jQuery.data() method to attach the “remote control” object to the element, and as a key we use the name of the plugin “compose”:

jQuery plugin returns “remote control” object

(function($) {
$.fn.compose = function(options) {
options = $.extend({
number: 2,
ul: null
}, options);

function setNumber(number) {
options.number = number;
}

this.change(function() {
if (options.ul !== null) {
var value = $(this).val();
var ul = $(options.ul).empty();
for(var i=0;i<options.number;i++) {
ul.append('<li>' + value + '</li>')
}
}
});

this.data('compose', {
setNumber: setNumber
});

return this;
}
})(jQuery);

Now, in order to change the number:

Interact with a plugin

$('#elem').compose({
number: 3,
ul: '#ul'
});

$('#elem').data('compose').setNumber(8);

And then the input’s value will appear 8 times. Live example for the compose plugin you can find here.

Summary

In this article I presented two jQuery plugin types and their structure (IIFE wrap and jQuery injection as $), I explained how to add compulsory and optional parameters, demonstrated the meaning of the this expression and described the jQuery chaining principal. At the end I also presented a way of letting the user interact the plugins.

Template of plugin that does not work on an element

Template of plugin that does not work on an element download

// jQuery plugin template for plugin that does not work on element
(function($) {
$.extend($, {
pluginName: function(param, options) {
options = $.extend({
// Options Defaults
}, options);

// Plugin content

return {
// Plugin interface object
};
}
});
})(jQuery);

Template of plugin that does work on an element

Template of plugin that does not work on an element download

// jQuery plugin template for plugin that does work on element
(function($) {
$.extend($.fn, {
pluginName: function(param, options) {
options = $.extend({
// Options Defaults
}, options);

this.each(function() {
// Operations for each DOM element
}).data('pluginName', {
// Plugin interface object
});

return this;
}
});
})(jQuery);

Demo & Download

  • Live example for the float plugin you can find here.
  • Live example for the compose plugin you can find here.
  • Here you can find all the examples of this post and the plugins templates.

I hope you find this post useful, and if you have any question, don’t hesitate to ask!


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流