先讲一下背景,最近发现项目中有些js文件的规模越来越大,接近2000行,开始出现维护困难的苗头;刚好实现的一个功能需要用到tree插件,在网上搜到一个bootstrap treeview插件可以用,但该插件无法支持懒加载和动态添加功能,网上现有的扩展方案都无法完全满足我的要求。花了一些时间看了bootstrap treeview的代码和Jquery插件的编写方法,对其进行了扩展并在项目中实现了一个简单的Select插件;另外计划把项目中以前组件化的自定义UI控件也全部用Jquery插件实现,以便复用并且清除冗余代码,这一过程我会陆续发布出来,本篇主要以我的select插件为例讲述如何才能编写一个Jquery插件:
要想编写一个Jquery插件,首先必须对Jquery有所了解,尤其是以下几个知识点:
有了以上知识,我们来实现一个Select的Jquery插件,可以方便的创建、添加选项、删除选项等等。代码如下:
/**
* Created by gavinli on 17-3-30.
*/
;(function ($) {
'use strict';
var pluginName = 'myList';
var _default = {};
_default.settings = {};
var MyList = function (element, options) {
this.$element = $(element);
this.init(options);
return {
init: $.proxy(this.init, this),
add: $.proxy(this.add, this),
remove: $.proxy(this.remove, this),
list: $.proxy(this.list, this),
clear: $.proxy(this.clear, this),
getSelected: $.proxy(this.getSelected, this)
}
};
MyList.prototype.init = function (options) {
this.items = [];
if (options.data) {
if (typeof options.data === 'string') {
options.data = $.parseJSON(options.data);
}
this.items = $.extend(true, [], options.data);
delete options.data;
}
this.options = $.extend({}, _default.settings, options);
this.render();
this.subscribeEvents();
};
MyList.prototype.subscribeEvents = function () {
//TODO:
};
MyList.prototype.add = function (items) {
if (!(items instanceof Array)) {
items = [items];
}
var _this = this;
$.each(items, function (i, value) {
_this.items.push(value);
});
this.filterDup();
this.render();
}
//Remove all duplicated items
MyList.prototype.filterDup = function () {
var _this = this;
var values = {}
$.each(_this.items, function (i, value) {
if (values[value]) {
_this.items[i] = null;
} else {
values[value] = true;
}
});
}
MyList.prototype.remove = function (items) {
var _this = this;
var toBeRemoved = {};
$.each(items, function (i, value) {
toBeRemoved[value] = true;
});
$.each(_this.items, function (i, value) {
if (toBeRemoved[value] == true) {
_this.items[i] = null;
}
});
this.render();
}
MyList.prototype.getSelected = function () {
return this.$wrapper.val();
}
MyList.prototype.list = function (item) {
var result = [];
$.each(this.items, function (i, value) {
if (value) {
result.push(value);
}
});
return result;
}
//Clear all items
MyList.prototype.clear = function () {
delete this.items;
this.items = [];
this.render();
}
MyList.prototype.render = function () {
if (!this.initialized) {
this.$wrapper = $(this.template.list);
this.initialized = true;
}
//Append select element to $element
this.$element.empty().append(this.$wrapper.empty());
//Build select options
this.buildList(this.items);
}
MyList.prototype.buildList = function (items) {
var _this = this;
$.each(items, function (i, value) {
if (value) {
var option = $(_this.template.item);
option.append(value);
_this.$wrapper.append(option);
}
});
}
MyList.prototype.template = {
list: '<select multiple class="form-control"></select>',
item: '<option></option>'
};
$.fn[pluginName] = function (options, args) {
var result;
this.each(function () {
var _this = $.data(this, pluginName);
if (typeof options === 'string') {
if (!_this) {
//logError('Not initialized, can not call method : ' + options);
}
else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
//logError('No such method : ' + options);
}
else {
if (!(args instanceof Array)) {
args = [args];
}
result = _this[options].apply(_this, args);
}
}
else if (typeof options === 'boolean') {
result = _this;
}
else {
$.data(this, pluginName, new MyList(this, $.extend(true, {}, options)));
}
});
return result || this;
};
})(jQuery);
下面针对其中的关键方法进行分析讲解:
MyList函数:MyList对象的construnctor方法,接受options参数(options参数包含所有options的数组)
MyList.prototype.init:根据options的data构建并渲染Select控件
MyList.prototype.add:添加option到Select中并渲染,其它remove,list,getSelected方法大家自行研究
上面代码中,最核心的部分在于如何将MyList对象和Dom元素结合、并且扩展到Jquery中,具体参考如下注释代码:
////扩展jQuery的prototype对象,这里的plugName等于myList,相当于给jQuery对象添加了一个"myList"方法
$.fn[pluginName] = function (options, args) {
var result;
////这里的this是一个jQuery对象
this.each(function () {
//下面的this不是jQuery对象,而是jQuery对象中的Dom对象
//从Dom对象中获取"data-myList"属性绑定的对象
var _this = $.data(this, pluginName);
//options是方法名,例如$('#list1').MyList('add',[]),这里的options等于'add'
if (typeof options === 'string') {
if (!_this) {
//logError('Not initialized, can not call method : ' + options);
}
else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
//logError('No such method : ' + options);
}
else {
if (!(args instanceof Array)) {
args = [args];
}
//调用MyList对象的方法
result = _this[options].apply(_this, args);
}
}
else if (typeof options === 'boolean') {
result = _this;
}
else {
//创建MyList对象并绑定到Dom对象的data-myList属性
$.data(this, pluginName, new MyList(this, $.extend(true, {}, options)));
}
});
return result || this;
};
如何使用该插件的方法如下所示:
首先在html中定义一个<div id="list1"></div>,然后这样使用它:
//创建一个Select包含三个options
$('#list1').MyList(['Tom','Mary','Alice']);
//添加新的option
$('#list1').MyList('add', [['James','Richard']]);
//删除option
$('#list1').MyList('remove', [['Alice']]);
最后我们可以在以上例子中发现创建jQuery插件的总体思路:
- 自定义对象,对象中包含数据和jQuery对象本身
- 定义对象的方法,并且根据对象中数据的变化渲染Dom对象(通过jQuery对象获得Dom对象)
- 将该自定义对象方法扩展到jQuery原型对象中
- 创建自定义对象,并绑定到jQuery中Dom对象的data属性
通过以上实现,我们便可以像使用jQuery对象一样的方式使用控件,屏蔽对Dom元素的操作,简单又方便