但必须知道,这些插件不是自己凭空产生的,它们是由开发人员编写、测试并完善的,这些人员为 jQuery 社区奉献了自己的业余时间。我们做这些都是免费的,是出于对自己代码的热爱。本文主要关注您如何回报这个伟大的社区,即如何编写自己的插件并上传到 jQuery 的插件页面。这可以让所有人使用您创建的插件,可以让整个 jQuery 开发社区变得更好。今年您也做出自己的贡献吧。
在编写本文中的插件时,我发现插件的创建过程以及用来创建它的框架非常简单明了。困难的地方在于想一些其他人还没有做过的事情,并编写一些能真正完成某些操作的 JavaScript 代码。由于插件结构简单明了,对于新手它简单易学,对于高手它很灵活,因此插件的数量急速上升。
当然,在研究本文所涉及的内容时,我还发现每个作者编写插件的风格都不同,jQuery 允许好几种不同的插件编写风格。在本文中,我集中介绍最简单的一种风格,以及 jQuery 本身推荐的一种风格,插件弹出后您就会看到差别或不同的选项。
描述插件
创建插件的第 1 步当然是想一个好点子。像大部分点子一样,其他人总会给您创造机会。以我在本文中开发的插件为例,它不是什么新颖的概念,但在我撰写文本的时候,jQuery 插件社区还找不到该插件。我知道我个人会从该插件中受益良多。
我的插件是一个 NumberFormatter 插件。处理服务器端代码(比如 Java™ 或 PHP)和国际化的用户应该很熟悉数字格式化。众所周知,并非每个人都用相同的方式格式化数字。例如,并非每个人都使用 “里” 来度量距离。在美国,数字的写法可能是 “1,250,500.75”(这个数字是从我的税收表上抄来的),但在其他国家的写法可能完全不同:德国是 “1.250.500,75”,法国是 “1 250 500,75”,瑞士是 “1'250'500.75”,日本是 “125,0500.75”。数字完全相同,但是在向 web 应用程序用户展示时使用不同的格式。
因此,问题归结到,当编写一个国际化应用程序时,如何向不同国家的人展示这些数字?当然,解决方案是使用服务器端格式化,这种解决办法非常常见。Java 有一个健壮的格式化库,使数字的格式化变得非常简单。当使用数字在服务器上设置页面时,服务器负责处理这些数字。但是,很多时候数字可能不在服务器上,因此您需要一种方法在客户机上格式化数字,而不需要与服务器会话。
我在这里描述的典型用例如下。您的 web 应用程序中有一个输入字段,要求用户输入他们的薪水。在美国,用户可能以各种格式输入 “$65000”、“65,000”、“65000” 和 “65,000.00”。所有这些数字都是相同的,但是您需要控制这些数字在屏幕上的显示方式,这样才能提供更好的用户体验。您可以在输入数字之后调用服务器,但是如果有许多使用不同格式的数字字段就太麻烦了。此外,如果您可以在客户端处理该问题,并向用户提供即时反馈,那么就不需要这样做了。
因此,我建立了一个空缺,之后我将尝试使用 JavaScript/jQuery 功能填补这一空缺。我的插件将在客户机上提供数字格式化,为其他人提供一种国际化 web 应用程序的方式,且无需与服务器会话。作为额外的功能,我的插件还可以提供反向操作;该插件使开发人员能够解析数字,从格式化的文本字符串中获取数字。这还可以应用于客户机上的数字操作。此外,我将模拟 Java<span style="FONT-FAMILY: NSimsun">DecimalFormatter</span>
类中的功能,以维护执行数字格式化的客户端代码和标准服务器端方法之间的通用性。
第 1 步结果:我发现了一个插件需求,然后定义了对于该需求我可以填补的空缺。
插件规则
jQuery 团队建立了许多希望插件作者都能遵守的通用规则,为插件用户创建一个通用而可信的环境。考虑到 jQuery 团队比我聪明多了,我没有理由违背这些规则,对不对?出于该原因,我在此列出这些规则,并且在插件的每一步都尽量遵守这些规则。
<span style="FONT-FAMILY: NSimsun">jquery.<your plug-in name>.js</span>
”检查完毕。我的插件将命名为 “<span style="FONT-FAMILY: NSimsun">jquery.numberformatter.js</span>
”。
检查完毕。我的方法/函数将仅附加到这两个对象。
<span style="FONT-FAMILY: NSimsun">this</span>
” 用于引用 jQuery 对象<span style="FONT-FAMILY: NSimsun">this</span>
” 时都知道将从 jQuery 收到哪个对象。
检查完毕。我将仅使用 “<span style="FONT-FAMILY: NSimsun">this</span>
” 引用 jQuery 对象。
检查完毕。所有的方法/函数都将以 “;” 结尾。
检查完毕。我的<span style="FONT-FAMILY: NSimsun">format()</span>
方法将返回 jQuery 对象,虽然我的<span style="FONT-FAMILY: NSimsun">parse()</span>
方法没有返回 jQuery 对象,但我在很多地方都注明该函数打破了链条。(毕竟,它不可能返回一个 Number 对象而不打破链条)。
<span style="FONT-FAMILY: NSimsun">this.each()</span>
迭代匹配的元素,这是一种可靠而有效地迭代对象的方式。检查完毕。我的方法都将只使用该方法迭代匹配的元素。
<span style="FONT-FAMILY: NSimsun">var JQ = jQuery.noConflict();</span>
” 函数更改他们的 jQuery 别名(pseudonym)。但是,在我查看许多插件时,我发现该规则常常得不到遵守,这太不幸了。如果开发人员需要更改 jQuery 别名,那么很可能意味着该插件要被弃用了。
检查完毕。在我的插件中,我将仅使用 jQuery 而不是它的别名 “$”。
好了,这些就是在插件代码中必须遵守的规则和建议。真正的问题在于,它们实际上是强制性的,因为如果您不遵守这些插件规则,那么您的插件就得不到广泛应用,而且还会得到不好的反馈。结果该插件很快就没有人使用了,您所花费的时间都将白费。因此,遵守这些规则非常重要。这不仅能帮助您鹤立鸡群,保证您代码的统一性,还能增加插件的成功几率。
第 2 步结果:我将遵守创建 jQuery 插件的所有规则
编写插件
现在可以开始编写代码了!开始编写插件的第一步是确定如何组织您的插件。开始有两种选择:您希望它是一个方法还是一个函数?“它们有区别吗?”您可能会这样问。
正如我上面提到的,方法需要附加到 jQuery.fn 对象,函数需要附加到 jQuery 对象。这样一切都清楚了,不是吗?如果您对 jQuery 相对不太了解,那么可能还不是很清楚。您可以这样考虑。方法使代码能够迭代所有传入插件的选定元素。因此,插件可以接收任何类型的 HTML 元素,由插件决定如何处理每个元素。因此,插件方法可以接收任何 jQuery 选择器,所有从 “p” 到 “#mySpecificPageElement” 的内容。如果您希望插件更加灵活,允许用户传入任何类型的页面元素,那么最好使用方法。插件开发人员应该负责正确地处理所有内容。相比之下,函数不使用任何选定元素作为参数。函数可以简单地应用于整个页面。这也由插件开发人员负责处理,开发人员必须定义他们希望与插件交互的页面元素,并忽略其他元素。让我们在代码中看看不同之处。
清单 1. jQuery 插件方法/函数
// This is a method because you can pass any type of selector to the method and it // will take some action on the results of the selector. In this case, it will // doSomething() on the page element with an ID of myExample $("#myExample").doSomething(); // This is also a method, even though you are passing the entire page body to // the method, because you are still passing a selector $("body").doSomethingElse();
// This is a function, because you are NOT passing any selector to the function // This hypothetical plug-in developer would document that his plug-in only works |
从这些描述中判断,插件使用的似乎是方法,因为您需要让用户告诉您他们希望格式化哪些页面元素。清单 2 展示了现在插件的代码。
清单 2. 方法定义
jQuery.fn.format = function();
// You would call your plug-in like this (at this point) |
当然,您的函数不可能是放之四海而皆准的插件,因为您处理的是国际化情况,无法自动指出希望格式化文本的国家或者需要的格式。因此,您必须稍微修改插件以接收某些选项。格式化方法中需要两个选项:数字应该使用的格式(例如,#,### 以及 #,###.00)和本地语言环境(本地语言环境是一个简单的 2 字符国家代码,用于确定要使用的国际数字格式)。
您还需要让插件尽可能的易于使用,因为您必须提高插件的成功几率。这意味着您应该继续定义一些默认的选项,使用户在不想传入选项时不需要这样做。我编写插件的所在地是美国,这里使用的是世界上最常见的数字格式,我的默认语言环境是 “us”,格式默认为 “#,###.00”,因此货币自然要使用该默认值。
清单 3. 允许在插件中使用选项
jQuery.fn.format = function(options) {
// the jQuery.extend function takes an unlimited number of arguments, and each format: "#,###.00", }, options); |
创建插件框架的最后一步是正确处理传入方法的选定元素。回想一下上例您会发现,选定元素可以是单页面元素,或者是多页面元素。您必须等效地处理它们。同样,回想一下 jQuery 插件规则,<span style="FONT-FAMILY: NSimsun">"this"</span>
对象只能引用 jQuery 对象。因此,您有一个对传入方法的 jQuery 选定元素的引用,现在需要迭代它们。同样,回顾规则让我们知道,每个插件方法都应该返回 jQuery 对象。当然,您知道 jQuery 对象就是 <span style="FONT-FAMILY: NSimsun">"this"</span>
,因此在方法中返回 this 完全没有问题。让我们看看如何在代码片段中实现迭代每个选定元素并返回 jQuery 对象。
清单 4. 处理 jQuery 对象
jQuery.fn.format = function(options) {
var options = jQuery.extend( { format: "#,###.00", }, options); // this code snippet will loop through the selected elements and return the jQuery object |
由于实际插件本身不是本文的重点,我不对此进行详细阐述,但是您可以在本文的插件代码附件中看到全部内容(请参见 下载)。如果您决定编写函数而不是方法,我还将向您展示一个样例,介绍如何设置插件架构。
清单 5. 使用函数的示例插件
jQuery.exampleFunction = function(options) {
var options = jQuery.extend( { // your defaults }, options); }); |
调优插件
网上关于初级插件的大部分文章都到此为止了,这时它们会让您采用基本的插件格式并运行。但是,这种基本架构也太 “基本” 了。在编写插件时还必须考虑另一件重要的事情,给您插件增色所需要的内容远不止一个初级插件那么简单。再多增加两个步骤,您就能将初级插件转换为中级插件。
调优 #1 - 让内部方法私有化
在任何面向对象的编程语言中,您会发现创建运行重复代码的外部函数非常方便。在我创建的 NumberFormatter 插件中,有一个这种代码的样例 —— 该代码决定向函数传递哪个地理位置,以及要使用哪些字符作为小数点和分组符。<span style="FONT-FAMILY: NSimsun">format()</span>
方法和 <span style="FONT-FAMILY: NSimsun">parse()</span>
方法中都需要该代码,任何一个初级程序员都会告诉您这属于它自己的方法。但是,这会出现一个问题,因为您处理的是 jQuery 插件:如果您使用 JavaScript 中的定义将它作为自己的函数,那么任何人都可以为任何目的使用脚本调用该方法。这不是该函数的目的,我更倾向于不调用它,因为它仅用于内部工作。那么,让我们看看如何将该函数私有化。
这种私有方法问题的解决方案称为 Closure,它可以有效地从外部调用关闭整个插件代码,附加到 jQuery 对象的除外(那些是公共方法)。通过这种设计,您可以将任何代码放入插件中,不用担心被外部脚本调用。通过将插件方法附加到 jQuery 对象,您可以有效地将它们变为公共方法,而让其他的函数/类私有化。清单 6 展示了实现该操作所需的代码。
清单 6. 私有化函数
// this function is "private"
function formatCodes(locale) {
// plug-in specific code here
}; // don't forget the semi-colon
// this method is "public" because it's attached to the jQuery object
jQuery.fn.format = function(options) {
var options = jQuery.extend( {
format: "#,###.00",
locale: "us"
},options);
return this.each(function(){
var text = new String(jQuery(this).text());
if (jQuery(this).is(":input"))
text = new String(jQuery(this).val());
// you can call the private function like any other function
var formatData = formatCodes(options.locale.toLowerCase());
// plug-in-specific code here
});
}; // don't forget the semi-colon to close the method
// this code ends the Closure structure
})(jQuery);
调优 #2 - 让插件的默认值可覆盖
调优插件的最后一步是让它可以覆盖默认值。毕竟,如果德国的开发人员下载了该插件,而且了解他的所有 web 应用程序用户希望使用德文版本,那么您应该让他能够使用一行代码修改默认语言环境,而不是要他在每个方法调用中都修改一遍。这样您的插件才会非常方便,因为一个 web 应用程序不太可能使用不同的国际化格式向用户展示数字。您在网页上看一下就知道,所有数字都是使用同一个语言环境的格式。
该步骤要求您修改某处代码,因此您将看到让插件最为耀眼的一步。
jQuery.fn.format = function(options) { return this.each(function(){ // define the defaults here as an object in the plug-in |
这是创建插件的最后一个步骤!这样您就有了一个不错的插件,可以进行最后的测试了。清单 8 展示了您可以放入本文的完整插件,以便您查看这些部分是如何变为一个整体的。此外还包含了 <span style="FONT-FAMILY: NSimsun">parse()</span>
函数,到目前为止我还没有讨论过该函数,但是它包含在插件中(我没有详细介绍插件处理格式化的部分,因为它们不在本文讨论之列。样例中包含了该部分,插件本身当然也有)。
(function(jQuery) {
function FormatData(valid, dec, group, neg) { function formatCodes(locale) { jQuery.fn.parse = function(options) { var options = jQuery.extend({},jQuery.fn.parse.defaults, options); var formatData = formatCodes(options.locale.toLowerCase()); var valid = formatData.valid; var array = []; var text = new String(jQuery(this).text());
return array; jQuery.fn.format = function(options) { var options = jQuery.extend({},jQuery.fn.format.defaults, options); var formatData = formatCodes(options.locale.toLowerCase()); var valid = formatData.valid; return this.each(function(){ // formatting logic goes here if (jQuery(this).is(":input")) jQuery.fn.format.defaults = { |
测试插件
创建插件的最后一步是全面测试它。用户在插件中发现 bug 会让他们很恼火。用户不会去修复它,他们会快速放弃使用它。如果有几个这类用户再加上一些糟糕的评论,您的插件很快就会石沉大海。此外,这是一个很好的互惠行为 —— 您希望自己使用的插件都经过了很好的测试,那么您也应该提供经过良好测试的插件。
我创建了一个快速测试结构来测试我的插件(不需要单元测试库),该结构创建了许多跨区,跨区中是一些数字,紧接数字后面的是该数字的正确格式。JavaScript 测试对数字调用格式,然后比较格式化的数字与想要的结果,如果失败则显示为红色。通过该测试,我可以设置不同的测试用例,测试所有可能的格式(我已经这样做了)。我将测试页面附加到样例 下载 中,以便您为测试自己插件找到一个可能的解决方案,利用 jQuery 进行测试。
查看完成的插件
让我们看看运行中的新 NumberFormatter。我已经创建了一个简单的 web 应用程序,您可以查看 NumberFormatter 插件如何满足您的应用程序。
图 1. 运行中的 NumberFormatter
这个 Web 应用程序很简单,也很直接。当用户离开文本字段时(输入了薪水、住宅、子女信息之后),NumberFormatter 插件将相应地格式化其输入的信息。该插件使 Web 应用程序能够向用户展示统一格式的数字。还要注意,该 web 应用程序是为德国用户格式化的,因此小数点和分组符号与美国用户不一样(关于这一点,请让我展示如何更改默认值)。
$(document).ready(function() {
// use the AlphaNumeric plug-in to limit the input // you want to change the defaults to use the German locale // when the salary field loses focus, format it properly // when the house field loses focus, format it properly }); |
在结束之前,关于 NumberFormatter 插件还有几件事情需要指出。首先,该插件是第一个 1.0.0 发行版,因此我希望将来进行扩展,包含更多 Java DecimalFormatter 中的格式化功能。包括支持货币、科学计数法和百分比。它还对负数和正数包含不同的格式化规则,负数不是简单的 “-”(例如,对负数使用 (5,000),在会计中是这样做的)。最后,一个好的格式器应该支持格式中的任何字符,而仅它忽略不属于保留字符的部分。这都是我近期想添加的功能,希望该插件变得更加健壮。
获取用户的语言环境
最后一个问题与 jQuery 无关,但是使用该插件时可能会出现 —— 如何获取用户的语言环境?这个问题提得很好,因为目前使用 JavaScript 没有办法获取该信息。您需要创建一个 JavaScript Bridge 来实现该目的。什么是 JavaScript Bridge?我的意思是您可以建立一个简单的设计模式将值从服务器端代码传入 JavaScript 代码。清单 10 展示了您可以使用 Java 在 JSP 页面做到这一点。
清单 10. 获取用户的语言环境
<%
// the request object is built into JSPs %> $(document).ready(function() { // take advantage of the ability to override defaults by using the JavaScript |
Share plug-ins
Finally, writing and testing the plug-in is done. The final step is to share the plugin with others and upload it to the jQuery website's plugin repository.
Conclusion
In this article, I mainly introduce how to create a plug-in for the jQuery JavaScript framework. I started from scratch, conceptualized an idea and brought it to life, covering the few steps I would use to create the plugin. Then, I read about jQuery “commandments,” which are plugin rules that are set to ensure the plugin’s consistency. I also remind you of these rules in the article because I see many rules that are not followed in plugins, especially only using "jQuery" and not "$" in plugins (of course, I follow that rule).
After introducing the background of plug-ins and the rules for writing plug-ins, you understand the basic plug-in framework and the difference between writing methods in plug-ins and writing functions. Methods should be used when taking a selected element as a parameter and performing some operation on it. The task of providing page elements is the responsibility of the person calling the method. Functions, on the other hand, should be used when you are not interested in the selected element, since you already know the page element on which you want to perform the operation. The task of providing page elements is the responsibility of the developer who wrote the function. Both forms of plug-ins are valid and differ only in the plug-in requirements. Finally, look at the basic ways to set default options and how to let users provide their own options.
The next step in this article is to provide some highlights for the plugin to increase its sophistication. This step concludes the entire plugin, effectively creating private and public functions. I created some functions that are called inside the plugin so that people outside the plugin cannot call them. You can also see how to expose default values to plugin users, allowing users to define their own default values for easy coding.
Finally, use the plug-in in a sample web application to demonstrate its behavior. The final part of this article is the culmination of all this hard work - you need to upload your plugin to the jQuery Plugin Community site to make it part of the JavaScript library.