jquery扩展以及jquery ui插件开发
- 在jquery命名空间添加全局函数,与普通类添加全局函数一样,定义一个立即调用函数表达式,接受jQuery作为参数并将参数命名为$保证在jQuery.noConflict()调用的情况下也能在内部使用$
1 (function ($) { 2 $.sum = function (array) 3 { 4 var total = 0; 5 $.each(array, function (index, value) { 6 value = parseFloat(value) || 0; 7 total += value; 8 }); 9 return total; 10 }; // end sum() 11 12 $.average = function (array) 13 { 14 if ($.siArray(array)) 15 { 16 return $.sum(array) / array.length; 17 } // end if 18 return ""; 19 }; // end average() 20 }(jQuery)); 21 22 var data = [3, 4, 3, 4, 3, 4, 3, 4]; 23 console.log("sum: " + $.sum(data)); // 28 24 console.log("avreage: " + $.average(data)); // 3.5
- 为jQuery对象添加方法,与普通类添加对象方法一样,
- 普通类通过默认为prototype添加方法,jQuery的prototype起了个简单的名字:fn,
- 函数调用上下文this为当前调用该方法的jQuery对象,
- 为适当方法返回this可支持链式调用
- 方法采用对象式传递更加方便
- 为方法设置可配置默认参数
- 下面是一个例子,用来说明基本思路,功能实现方法不推荐用于实际开发
1 (function ($) { 2 $.fn.shadow = function (opts) 3 { 4 var options = $.extend({}, $.fn.shadow.defaults, opts); 5 return this.each(function () { 6 var $originalelement = $(this); 7 for (var i = 0; i < options.copies; ++i) 8 { 9 var offset = options.copyOffset(i); 10 $originalElement.clone() 11 .css({ 12 position: "absolute", 13 left: $originalElement.offset().left + offset.x, 14 top: $originalElement.offset().top + offset.y, 15 margin: 0, 16 zIndex: -1, 17 opacity: options.opacity 18 }).appendTo("body"); 19 } // end for 20 }); // end each() 21 }; // end shadow() 22 23 // define comfigurable defaults for shadow 24 $.fn.shadow.defaults = { 25 copies: 5, 26 opacity: 0.1, 27 copyOffset: function (index) 28 { 29 return {x: index, y: index}; 30 } // end copyOffset() 31 }; // end defaults 32 }(jQuery)); 33 34 35 // test case 36 $.fn.shadow.defaults.copies = 10; 37 $("h1").shadow({ 38 copyOffset: function (index) 39 { 40 return {x: -index, y: index}; 41 } // end copyOffset() 42 });
- jquery ui插件开发比扩展jquery稍微复杂,但是jQuery UI包含了一个控件工厂$.widget()帮我们做了很多工作,通过这个工厂方法可以保证我们的代码遵守API标准,也就方便用户使用
- 插件具有stateful特性,这样可以通过检查状态,修改状态完成各种需求
- 用户选项和可配置默认选项自动归并
- 多个插件方法可以通过传递字符串为方法调用简单jQuery方法实现无缝连接,
- 自定义事件监控可以访问控件实例的数据
- jQuery UI插件通过调用$.widget()创建。这个函数接受两个参数
- 第一个是一个字符串,这是一个包含了命名空间的插件名如"ljq.tooltip"这样我们的插件就可以通过在jQuery对象上调用.tooltip()来安装了
- 第二个参数是一个对象,其属性包含一些widget()预定义属性,根据约定完成空间初始化、销毁等
- 为第二个参数设置_create,这是在插件初始化时控件工厂自动调用的函数:
1 (function ($) { 2 $.widget("ljq.tooltip", { 3 _create: function () 4 { 5 this._tooltipDiv = $("<div></div>") 6 .addClass("ljq-tooltip-text ui-widget " 7 + "ui-state-highlight ui-corner-all") 8 .hide().appendTo("body"); 9 this.element.addClass("ljq-tooltip-trigger") 10 .on("mouseenter.ljq-tooltip", $.proxy(this._open, this)) 11 .on("mouseleave.ljq-tooltip", $.proxy(this._close, this)); 12 }, // end _create() 13 _open: function () 14 { 15 var elementOffset = this.element.offset(); 16 this._tooltipDiv.css({ 17 position: "absolute", 18 left: elementOffset.left, 19 top: elementOffset.top + this.element.height() 20 }).text(this.element.data("tooltip-text")); 21 this._tooltipDiv.show(); 22 }, // end _open() 23 _close: function () 24 { 25 this._tooltipDiv.hide(); 26 } // end _close() 27 }); // end widget.toolip() 28 }(jQuery));
- 其中_create()调用上下文this为当前创建的控件实例,可以给它添加所需的属性,例如:this._tooltipDiv
- 控件实例包含一些有用的属性: this.element是创建空间的jQuery对象
- 通过为this.element绑定mouseenter和mouseleave事件监听器,控制tooltip的出现
- 销毁控件,上面的方法创建了一个jQuery新方法,在jQuery对象上调用即可创建tooltip,如$("a").tooltip(),同样也可以通过传入一个字符串作为参数,用于指定需要调用的方法,其中内建的destroy方法会将控件取消,.tooltip("destroy")可以调用,控件工厂可以帮我们做大多数工作,但是如果我们像tooltip一样创建的新的dom节点,我们必须自定义方法来完成清理
1 (function ($) { 2 $.widget("ljq.tooltip", { 3 _create: function () 4 { 5 this._tooltipDiv = $("<div></div>") 6 .addClass("ljq-tooltip-text ui-widget " 7 + "ui-state-highlight ui-corner-all") 8 .hide().appendTo("body"); 9 this.element.addClass("ljq-tooltip-trigger") 10 .on("mouseenter.ljq-tooltip", $.proxy(this._open, this)) 11 .on("mouseleave.ljq-tooltip", $.proxy(this._close, this)); 12 }, // end _create() 13 destroy: function () 14 { 15 this._tooltipDiv.remove(); 16 this.element.removeClass("ljq-tooltip-trigger") 17 .off(".ljq-tooltip"); 18 $.Widget.prototype.destroy.apply(this, arguments); 19 }, // end destroy() 20 _open: function () 21 { 22 var elementOffset = this.element.offset(); 23 this._tooltipDiv.css({ 24 position: "absolute", 25 left: elementOffset.left, 26 top: elementOffset.top + this.element.height() 27 }).text(this.element.data("tooltip-text")); 28 this._tooltipDiv.show(); 29 }, // end _open() 30 _close: function () 31 { 32 this._tooltipDiv.hide(); 33 } // end _close() 34 }); // end widget.toolip() 35 }(jQuery));
- 使用和禁用控件:控件内建了enable和disable方法,这样就可以设置this.options.disabled在true和false之间切换,我们只需要在方法之间检查这个值即可,例如在_open中检测
1 _open: function () 2 { 3 if (this.options.disabled) 4 { 5 return; 6 } // end if 7 var elementOffset = this.element.offset(); 8 this._tooltipDiv.css({ 9 position: "absolute", 10 left: elementOffset.left, 11 top: elementOffset.top + this.element.height() 12 }).text(this.element.data("tooltip-text")); 13 this._tooltipDiv.show(); 14 }, // end _open()
调用.tooltip("disable"); tooltip("enable");可以达到控制this.options.disabled的目的
- 控件接受参数:为控件添加options属性并配置所需的默认参数,控件工厂会判断用户调用控件时传递的参数并覆盖默认参数:
1 (function ($) { 2 $.widget("ljq.tooltip", { 3 options: { 4 offsetX: 10, 5 offsetY: 10, 6 content: function () { 7 return $(this).data("tooltip-text") + " default option"; 8 } 9 }, 10 _create: function () 11 { 12 this._tooltipDiv = $("<div></div>") 13 .addClass("ljq-tooltip-text ui-widget " 14 + "ui-state-highlight ui-corner-all") 15 .hide().appendTo("body"); 16 this.element.addClass("ljq-tooltip-trigger") 17 .on("mouseenter.ljq-tooltip", $.proxy(this._open, this)) 18 .on("mouseleave.ljq-tooltip", $.proxy(this._close, this)); 19 }, // end _create() 20 destroy: function () 21 { 22 this._tooltipDiv.remove(); 23 this.element.removeClass("ljq-tooltip-trigger") 24 .off(".ljq-tooltip"); 25 $.Widget.prototype.destroy.apply(this, arguments); 26 }, // end destroy() 27 _open: function () 28 { 29 if (this.options.disabled) 30 { 31 return; 32 } // end if 33 var elementOffset = this.element.offset(); 34 this._tooltipDiv.css({ 35 position: "absolute", 36 left: elementOffset.left + this.options.offsetX, 37 top: elementOffset.top + this.element.height() + this.options.offsetY 38 }).text(this.options.content.call(this.element[0])); 39 this._tooltipDiv.show(); 40 }, // end _open() 41 _close: function () 42 { 43 this._tooltipDiv.hide(); 44 } // end _close() 45 }); // end widget.toolip() 46 }(jQuery));
调用控件时.tooltip({offsetX: 0, offsetY: 1});会自动覆盖默认参数
- 自定义子方法,系统提供了默认的如destroy子方法。我们可以提供自定义子方法供用户调用,控件工厂会保证我们的方法支持链式调用
1 open: function () 2 { 3 this._open(); 4 }, // end open() 5 close: function () 6 { 7 this._close(); 8 }, // end close()
.tooltip("close"); tooltip("open");即可调用,自定义子方法可接受参数,通过控件调用子方法的字符串后传入参数如:
close: function (msg) { console.log(msg); } $("a").tooltip(); $("a").tooltip("close", "this is argument");
- 触发控件事件:在控件函数中调用this._trigger("eventname");会在添加了该控件的元素上触发一个自定义事件,事件名带有控件名作为前缀如this._trigger("open");会在元素上触发tooltipopen,这样就可以在元素上添加监听器了
1 _open: function () 2 { 3 if (this.options.disabled) 4 { 5 return; 6 } // end if 7 var elementOffset = this.element.offset(); 8 this._tooltipDiv.css({ 9 position: "absolute", 10 left: elementOffset.left + this.options.offsetX, 11 top: elementOffset.top + this.element.height() + this.options.offsetY 12 }).text(this.options.content.call(this.element[0])); 13 this._tooltipDiv.show(); 14 this._trigger("open"); 15 }, // end _open() 16 _close: function () 17 { 18 this._tooltipDiv.hide(); 19 this._trigger("close"); 20 } // end _close()
- 了解了为jQuery和jQuery UI添加扩展的常见内容,可以总结如下:
- 通过使用立即调用函数表达式保证$在自定义模块内不会失效
- 无论扩展$全局函数或者jQuery对象方法,最多在$定义一个属性。多余的公共方法和属性应该添加到控件名字空间($.myPlugin.publicMethod或者$.fn.myPlugin.pluginProperty)
- 为控件提供一个默认选项设置:$.fn.myPlugin.defaults = { size: "large"};
- 允许控件用户覆盖默认设置:$.fn.myPlugin.defaults.size = "medium"; $("div").myPlugin({size: "small"})
- 扩展jQuery对象原型时,返回this提供链式调用: $("div").myPlugin().find("p").addClass("foo")
- 扩展jQuery原型时,内部需要调用this.each()确保内部调用
- 适当提供回调函数增加控件灵活性
- jQuery UI控件通过控件工厂定义并且执行操作时检查控件状态
- 为控件提供自动单元测试,如使用QUnit框架
- 使用版本控制工具,如Git,把代码放到GitHub允许其他人对代码贡献
- 给控件选择一个合适的license
- 给控件提供完善的文档
- 完整代码
1 <!doctype html> 2 <html lang="zh"> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta name="description" content=""> 7 <meta name="author" content=""> 8 9 <title>Template Index</title> 10 <style> 11 </style> 12 13 14 </head> 15 <body> 16 <table id="inventory"> 17 <thead> 18 <tr class="one"> 19 <th>Product</th> 20 <th>Quantity</th> 21 <th>Price</th> 22 </tr> 23 </thead> 24 <tfoot> 25 <tr class="two" id="sum"> 26 <td>Total</td> 27 <td></td> 28 <td></td> 29 </tr> 30 <tr id="average"> 31 <td>Average</td> 32 <td></td> 33 <td></td> 34 </tr> 35 </tfoot> 36 <tbody> 37 <tr> 38 <td><a href="span.html" data-tooltip-text="Nutritious and delicious!">Spam</a></td> 39 <td>4</td> 40 <td>2.50</td> 41 </tr> 42 <tr> 43 <td><a href="egg.html" data-tooltip-text="Fram fresh or scrambled!">Egg</a></td> 44 <td>12</td> 45 <td>4.32</td> 46 </tr> 47 <tr> 48 <td><a href="gourmet-spam.html" data-tooltip-text="Chef Hermans's recipe">Gourmet Span</a></td> 49 <td>14</td> 50 <td>7.89</td> 51 </tr> 52 </tbody> 53 </table> 54 55 <script src="js/jquery.js"></script> 56 <script src="js/jquery-ui-core.js"></script> 57 <script> 58 (function ($) 59 { 60 $.sum = function (array) 61 { 62 var total = 0; 63 $.each(array, 64 function (index, value) 65 { 66 value = $.trim(value); 67 value = parseFloat(value) || 0; 68 total += value; 69 } // end function 70 ); 71 return total; 72 }; // end sum() 73 74 $.average = function (array) 75 { 76 if ($.isArray(array)) 77 { 78 return $.sum(array) / array.length; 79 } // end if 80 return ""; 81 }; // end average() 82 83 }(jQuery)); 84 85 (function ($) { 86 $.widget("ljq.tooltip", { 87 options: { 88 offsetX: 10, 89 offsetY: 10, 90 content: function () { 91 return $(this).data("tooltip-text") + " default option"; 92 } 93 }, 94 _create: function () 95 { 96 this._tooltipDiv = $("<div></div>") 97 .addClass("ljq-tooltip-text ui-widget " 98 + "ui-state-highlight ui-corner-all") 99 .hide().appendTo("body"); 100 this.element.addClass("ljq-tooltip-trigger") 101 .on("mouseenter.ljq-tooltip", $.proxy(this._open, this)) 102 .on("mouseleave.ljq-tooltip", $.proxy(this._close, this)); 103 }, // end _create() 104 destroy: function () 105 { 106 this._tooltipDiv.remove(); 107 this.element.removeClass("ljq-tooltip-trigger") 108 .off(".ljq-tooltip"); 109 $.Widget.prototype.destroy.apply(this, arguments); 110 }, // end destroy() 111 open: function () 112 { 113 this._open(); 114 }, // end open() 115 close: function () 116 { 117 this._close(); 118 }, // end close() 119 _open: function () 120 { 121 if (this.options.disabled) 122 { 123 return; 124 } // end if 125 var elementOffset = this.element.offset(); 126 this._tooltipDiv.css({ 127 position: "absolute", 128 left: elementOffset.left + this.options.offsetX, 129 top: elementOffset.top + this.element.height() + this.options.offsetY 130 }).text(this.options.content.call(this.element[0])); 131 this._tooltipDiv.show(); 132 this._trigger("open"); 133 }, // end _open() 134 _close: function () 135 { 136 this._tooltipDiv.hide(); 137 this._trigger("close"); 138 } // end _close() 139 }); // end widget.toolip() 140 }(jQuery)); 141 </script> 142 <script> 143 $(function (){ 144 var inventory = $("#inventory tbody"); 145 var quantities = inventory.find("td:nth-child(2)") 146 .map(function (index, qty){ 147 return $(qty).text(); 148 }).get(); 149 var sum = $.sum(quantities); 150 $("#sum").find("td:nth-child(2)").text(sum); 151 152 var prices = inventory.find("td:nth-child(3)") 153 .map(function (index, qty){ 154 return $(qty).text(); 155 }).get(); 156 var average = $.average(prices); 157 $("#average").find("td:nth-child(3)").text(average.toFixed(2)); 158 159 var data = [3, 4, 3, 4, 3, 4, 3, 4]; 160 console.log("sum: " + $.sum(data)); 161 console.log("avreage: " + $.average(data)); 162 $("a").tooltip(); 163 164 }); 165 </script> 166 </body> 167 </html>