jquery

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > jquery > jQuery 自定义插件

jQuery创建自己的插件(自定义插件)的方法

作者:

在该系列之前的文章使用 jQuery:UI 项目中,我介绍了使用 jQuery 代码中的插件来提高 web 应用程序的效率。
但必须知道,这些插件不是自己凭空产生的,它们是由开发人员编写、测试并完善的,这些人员为 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 应用程序的方式,且无需与服务器会话。作为额外的功能,我的插件还可以提供反向操作;该插件使开发人员能够解析数字,从格式化的文本字符串中获取数字。这还可以应用于客户机上的数字操作。此外,我将模拟 JavaDecimalFormatter类中的功能,以维护执行数字格式化的客户端代码和标准服务器端方法之间的通用性。

第 1 步结果:我发现了一个插件需求,然后定义了对于该需求我可以填补的空缺。

插件规则

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
// The plug-in developer must determine what page elements they want to take action on.
// This is usually accomplished by the plug-in developer requiring the page elements
// to contain a certain class name.

<div class="anotherThing">

// This hypothetical plug-in developer would document that his plug-in only works
// on elements with the class "anotherThing"
$.anotherThing();

 

从这些描述中判断,插件使用的似乎是方法,因为您需要让用户告诉您他们希望格式化哪些页面元素。清单 2 展示了现在插件的代码。


清单 2. 方法定义

jQuery.fn.format = function();

 

// You would call your plug-in like this (at this point)
$("#myText").format();

 

当然,您的函数不可能是放之四海而皆准的插件,因为您处理的是国际化情况,无法自动指出希望格式化文本的国家或者需要的格式。因此,您必须稍微修改插件以接收某些选项。格式化方法中需要两个选项:数字应该使用的格式(例如,#,### 以及 #,###.00)和本地语言环境(本地语言环境是一个简单的 2 字符国家代码,用于确定要使用的国际数字格式)。

您还需要让插件尽可能的易于使用,因为您必须提高插件的成功几率。这意味着您应该继续定义一些默认的选项,使用户在不想传入选项时不需要这样做。我编写插件的所在地是美国,这里使用的是世界上最常见的数字格式,我的默认语言环境是 “us”,格式默认为 “#,###.00”,因此货币自然要使用该默认值。

 

 

清单 3. 允许在插件中使用选项


jQuery.fn.format = function(options) {

 

// the jQuery.extend function takes an unlimited number of arguments, and each
// successive argument can overwrite the values of the previous ones.
// This setup is beneficial for defining default values, because you define
// them first, and then use the options passed into the method as the
// second argument. This allows the user to override any default values with their
// own in an easy-to-use setup.
var options = jQuery.extend( {

format: "#,###.00",
locale: "us"

}, options);

 

创建插件框架的最后一步是正确处理传入方法的选定元素。回想一下上例您会发现,选定元素可以是单页面元素,或者是多页面元素。您必须等效地处理它们。同样,回想一下 jQuery 插件规则,"this" 对象只能引用 jQuery 对象。因此,您有一个对传入方法的 jQuery 选定元素的引用,现在需要迭代它们。同样,回顾规则让我们知道,每个插件方法都应该返回 jQuery 对象。当然,您知道 jQuery 对象就是 "this",因此在方法中返回 this 完全没有问题。让我们看看如何在代码片段中实现迭代每个选定元素并返回 jQuery 对象。


清单 4. 处理 jQuery 对象


jQuery.fn.format = function(options) {

 

var options = jQuery.extend( {

format: "#,###.00",
locale: "us"

}, options);

// this code snippet will loop through the selected elements and return the jQuery object
// when complete
return this.each(function(){
// inside each iteration, you can reference the current element by using the standard
// jQuery(this) notation
// the rest of the plug-in code goes here
});

 

由于实际插件本身不是本文的重点,我不对此进行详细阐述,但是您可以在本文的插件代码附件中看到全部内容(请参见 下载)。如果您决定编写函数而不是方法,我还将向您展示一个样例,介绍如何设置插件架构。


清单 5. 使用函数的示例插件


jQuery.exampleFunction = function(options) {

 

var options = jQuery.extend( {

// your defaults

}, options);

jQuery(".exampleSelector").each(function(){

});

});

 

调优插件

网上关于初级插件的大部分文章都到此为止了,这时它们会让您采用基本的插件格式并运行。但是,这种基本架构也太 “基本” 了。在编写插件时还必须考虑另一件重要的事情,给您插件增色所需要的内容远不止一个初级插件那么简单。再多增加两个步骤,您就能将初级插件转换为中级插件。

调优 #1 - 让内部方法私有化

在任何面向对象的编程语言中,您会发现创建运行重复代码的外部函数非常方便。在我创建的 NumberFormatter 插件中,有一个这种代码的样例 —— 该代码决定向函数传递哪个地理位置,以及要使用哪些字符作为小数点和分组符。format() 方法和 parse() 方法中都需要该代码,任何一个初级程序员都会告诉您这属于它自己的方法。但是,这会出现一个问题,因为您处理的是 jQuery 插件:如果您使用 JavaScript 中的定义将它作为自己的函数,那么任何人都可以为任何目的使用脚本调用该方法。这不是该函数的目的,我更倾向于不调用它,因为它仅用于内部工作。那么,让我们看看如何将该函数私有化。

这种私有方法问题的解决方案称为 Closure,它可以有效地从外部调用关闭整个插件代码,附加到 jQuery 对象的除外(那些是公共方法)。通过这种设计,您可以将任何代码放入插件中,不用担心被外部脚本调用。通过将插件方法附加到 jQuery 对象,您可以有效地将它们变为公共方法,而让其他的函数/类私有化。清单 6 展示了实现该操作所需的代码。


清单 6. 私有化函数


// this code creates the Closure structure
(function(jQuery) {

 

// 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 应用程序不太可能使用不同的国际化格式向用户展示数字。您在网页上看一下就知道,所有数字都是使用同一个语言环境的格式。

该步骤要求您修改某处代码,因此您将看到让插件最为耀眼的一步。


清单 7. 覆盖默认值

 

jQuery.fn.format = function(options) {
// Change how you load your options in to take advantage of your overridable defaults
// You change how your extend() function works, because the defaults
// are globally defined, rather than within the method. If you didn't use the
// {} as the first argument, you'd copy the options passed in over the defaults, which is
// undesirable. This {} creates a new temporary object to store the options
// You can simply call the defaults as an object within your plug-in
var options = jQuery.extend({},jQuery.fn.format.defaults, options);

return this.each(function(){

// rest of the plug-in code here

// define the defaults here as an object in the plug-in
jQuery.fn.format.defaults = {
format: "#,###.00",
locale: "us"
}; // don't forget the semi-colon


这是创建插件的最后一个步骤!这样您就有了一个不错的插件,可以进行最后的测试了。清单 8 展示了您可以放入本文的完整插件,以便您查看这些部分是如何变为一个整体的。此外还包含了 parse() 函数,到目前为止我还没有讨论过该函数,但是它包含在插件中(我没有详细介绍插件处理格式化的部分,因为它们不在本文讨论之列。样例中包含了该部分,插件本身当然也有)。


清单 8. NumberFormatter 插件

(function(jQuery) {

 

function FormatData(valid, dec, group, neg) {
this.valid = valid;
this.dec = dec;
this.group = group;
this.neg = neg;
};

function formatCodes(locale) {
// format logic goes here
return new FormatData(valid, dec, group, neg);
};

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 dec = formatData.dec;
var group = formatData.group;
var neg = formatData.neg;

var array = [];
this.each(function(){

var text = new String(jQuery(this).text());
if (jQuery(this).is(":input"))
text = new String(jQuery(this).val());


// now we need to convert it into a number
var number = new Number(text.replace(group,'').replace(dec,".").replace(neg,"-"));
array.push(number);
});

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;
var dec = formatData.dec;
var group = formatData.group;
var neg = formatData.neg;

return this.each(function(){
var text = new String(jQuery(this).text());
if (jQuery(this).is(":input"))
text = new String(jQuery(this).val());

// formatting logic goes here

if (jQuery(this).is(":input"))
jQuery(this).val(returnString);
else
jQuery(this).text(returnString);
});
};

jQuery.fn.parse.defaults = {
locale: "us"
};

jQuery.fn.format.defaults = {
format: "#,###.00",
locale: "us"
};

})(jQuery);


测试插件

创建插件的最后一步是全面测试它。用户在插件中发现 bug 会让他们很恼火。用户不会去修复它,他们会快速放弃使用它。如果有几个这类用户再加上一些糟糕的评论,您的插件很快就会石沉大海。此外,这是一个很好的互惠行为 —— 您希望自己使用的插件都经过了很好的测试,那么您也应该提供经过良好测试的插件。

我创建了一个快速测试结构来测试我的插件(不需要单元测试库),该结构创建了许多跨区,跨区中是一些数字,紧接数字后面的是该数字的正确格式。JavaScript 测试对数字调用格式,然后比较格式化的数字与想要的结果,如果失败则显示为红色。通过该测试,我可以设置不同的测试用例,测试所有可能的格式(我已经这样做了)。我将测试页面附加到样例 下载 中,以便您为测试自己插件找到一个可能的解决方案,利用 jQuery 进行测试。

查看完成的插件

让我们看看运行中的新 NumberFormatter。我已经创建了一个简单的 web 应用程序,您可以查看 NumberFormatter 插件如何满足您的应用程序。

 

图 1. 运行中的 NumberFormatter
 

这个 Web 应用程序很简单,也很直接。当用户离开文本字段时(输入了薪水、住宅、子女信息之后),NumberFormatter 插件将相应地格式化其输入的信息。该插件使 Web 应用程序能够向用户展示统一格式的数字。还要注意,该 web 应用程序是为德国用户格式化的,因此小数点和分组符号与美国用户不一样(关于这一点,请让我展示如何更改默认值)。


清单 9. 运行中的 NumberFormatter

$(document).ready(function() {

 

// use the AlphaNumeric plug-in to limit the input
$(".decimal").decimal();
$(".numeric").numeric();

// you want to change the defaults to use the German locale
// this will change the default for every method call on the
// entire page, so you won't have to pass in the "locale"
// argument to any function
$.fn.format.defaults.locale = "de";
$.fn.parse.defaults.locale = "de";

// when the salary field loses focus, format it properly
$("#salary").blur(function(){
$(this).format({format:"#,###.00"});
});

// when the house field loses focus, format it properly
$("#houseWorth").blur(function(){
$(this).format({format:"#,###"});
});

// when the kids field loses focus, format it properly
$("#kids").blur(function(){
$(this).format({format:"#"});
});

// calculate the tax
$("#calculate").click(function(){
// parse all the numbers from the fields
var salary = $("#salary").parse();
var house = $("#houseWorth").parse();
var kids = $("#kids").parse();
// make some imaginary tax formula
var tax = Math.max(0,(0.22*salary) + (0.03*house) - (4000*kids));
// place the result in the tax field, and then format the resulting number
// you need one intermediate step though, and that's the "formatNumber" function
// because all numbers in JavaScript use a US locale when made into a String
// you need to convert this Number into a German locale String before
// calling format on it.
// So, the steps are:
// 1) the tax is a Number that looks like 9200.54 (US locale)
// 2) formatNumber converts this to a String of 9200,54 (German locale)
// 3) put this String in the #tax field
// 4) Call format() on this field
$("#tax").text($.formatNumber(tax)).format({format:"#,###"});
});

});


在结束之前,关于 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
// unfortunately, it's not any easier
// tested on FF, IE, Safari, Chrome
String locale = "us"; // or your default locale
String accLang = request.getHeader("Accept-Language");
if (accLang.length() > 5)
{
accLang = accLang.substring(0,5);
locale = accLang.substring(accLang.indexOf("-")+1);
}

%>

$(document).ready(function() {

// take advantage of the ability to override defaults by using the JavaScript
// Bridge here. Then your page can use the format() and parse() functions
// elsewhere in the page without modifying them for a user's locale.
$.fn.format.defaults.locale = "<%=locale%>";
$.fn.parse.defaults.locale = "<%=locale%>";

});


共享插件

最后,编写和测试插件都做完了。最后一步是与他人共享该插件,并将它上传到 jQuery 网站的插件存储库。

结束语

在本文中,我主要介绍如何为 jQuery JavaScript 框架创建插件。我从头开始,构思了一个想法并将其付诸实现,介绍了创建该插件要使用的几个步骤。然后,我阅读了 jQuery “戒律”,它是为确保插件的一致性而设置的插件规则。我还在文章中提醒这些规则,因为我看到许多规则在插件中都没有得到遵守,尤其是只能在插件中使用 “jQuery” 而不是 “$”(当然,我遵守了该规则)。

介绍了插件的背景,以及编写插件的规则之后,您了解了基本的插件框架以及在插件中编写方法与编写函数的不同。方法应该在采用选定元素作为参数并对其执行某些操作时使用。提供页面元素的任务由调用方法的人负责。另一方面,函数应该在对选定元素不感兴趣时使用,因为您已经知道要执行操作的页面元素。提供页面元素的任务由编写该函数的开发人员负责。这两种形式的插件都是有效的,仅在在插件需求上有所不同。最后,查看设置默认选项的基本方式以及如何让用户提供自己的选项。

本文的下一步是为插件提供一些亮点,以增加它的先进性。该步骤给整个插件画上了句号,有效地创建了私有函数和公共函数。我创建了一些在插件内部调用的函数,以便插件外部的人无法调用它们。您还可以看到如何向插件用户展示默认值,让用户定义他们自己的默认值,轻松实现编码。

最后,在样例 web 应用程序中使用插件展示它的行为。本文的最后一部分是这些努力工作的最终成果 —— 您需要将插件上传到 jQuery 插件社区站点,让它成为 JavaScript 库的一部分。 

您可能感兴趣的文章:
阅读全文