先講一下背景,最近發(fā)現(xiàn)項(xiàng)目中有些js文件的規(guī)模越來越大,接近2000行,開始出現(xiàn)維護(hù)困難的苗頭;剛好實(shí)現(xiàn)的一個(gè)功能需要用到tree插件,在網(wǎng)上搜到一個(gè)bootstrap treeview插件可以用,但該插件無法支持懶加載和動(dòng)態(tài)添加功能,網(wǎng)上現(xiàn)有的擴(kuò)展方案都無法完全滿足我的要求。花了一些時(shí)間看了bootstrap treeview的代碼和Jquery插件的編寫方法,對(duì)其進(jìn)行了擴(kuò)展并在項(xiàng)目中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Select插件;另外計(jì)劃把項(xiàng)目中以前組件化的自定義UI控件也全部用Jquery插件實(shí)現(xiàn),以便復(fù)用并且清除冗余代碼,這一過程我會(huì)陸續(xù)發(fā)布出來,本篇主要以我的select插件為例講述如何才能編寫一個(gè)Jquery插件:
要想編寫一個(gè)Jquery插件,首先必須對(duì)Jquery有所了解,尤其是以下幾個(gè)知識(shí)點(diǎn):
/**
MyList函數(shù):MyList對(duì)象的construnctor方法,接受options參數(shù)(options參數(shù)包含所有options的數(shù)組)
MyList.prototype.init:根據(jù)options的data構(gòu)建并渲染Select控件
MyList.prototype.add:添加option到Select中并渲染,其它remove,list,getSelected方法大家自行研究
上面代碼中,最核心的部分在于如何將MyList對(duì)象和Dom元素結(jié)合、并且擴(kuò)展到Jquery中,具體參考如下注釋代碼:
////擴(kuò)展jQuery的prototype對(duì)象,這里的plugName等于myList,相當(dāng)于給jQuery對(duì)象添加了一個(gè)"myList"方法
首先在html中定義一個(gè)<div id="list1"></div>,然后這樣使用它:
//創(chuàng)建一個(gè)Select包含三個(gè)options
要想編寫一個(gè)Jquery插件,首先必須對(duì)Jquery有所了解,尤其是以下幾個(gè)知識(shí)點(diǎn):
- 知道(function($){...})(jQuery)是什么意思
(function($){...})(jQuery)定義了一個(gè)函數(shù),并且以jQuery為參數(shù)進(jìn)行了調(diào)用,一般情況下,這是實(shí)現(xiàn)jQuery插件的標(biāo)準(zhǔn)姿勢(shì),對(duì)這個(gè)有興趣的同學(xué)可以繼續(xù)深入研究。
- $.proxy(fun, context)
該方法返回一個(gè)fun函數(shù)的代理,返回的代理函數(shù)功能和fun相同,不同的是返回函數(shù)使用context參數(shù)作為調(diào)用上下文。這段話有點(diǎn)繞口,不明白的同學(xué)請(qǐng)參考:
http://www.cnblogs.com/hongchenok/p/3919497.html - $.each(arr, function(){...})
該方法在每個(gè)arr中的元素上執(zhí)行callback方法,存在很多變體。詳細(xì)請(qǐng)參見http://www.jb51.net/article/24581.htm
- $.extend(deep, obj, obj1, ..)
Jquery提供的用來擴(kuò)展一個(gè)對(duì)象的方法,即將對(duì)象obj1合并到對(duì)象obj,一般被用來向jQuery對(duì)象中添加方法。詳細(xì)請(qǐng)參考http://www.cnblogs.com/tianguook/p/4084061.html
- $.data(dom, data-name, data-value)
Jquery提供的用來綁定對(duì)象到dom對(duì)象中的方法,如果沒有定義data-value參數(shù),是讀取data-name的值;如果定義了data-value則是設(shè)置data-name的值。
/**
* 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);
下面針對(duì)其中的關(guān)鍵方法進(jìn)行分析講解:*/
;(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函數(shù):MyList對(duì)象的construnctor方法,接受options參數(shù)(options參數(shù)包含所有options的數(shù)組)
MyList.prototype.init:根據(jù)options的data構(gòu)建并渲染Select控件
MyList.prototype.add:添加option到Select中并渲染,其它remove,list,getSelected方法大家自行研究
上面代碼中,最核心的部分在于如何將MyList對(duì)象和Dom元素結(jié)合、并且擴(kuò)展到Jquery中,具體參考如下注釋代碼:
////擴(kuò)展jQuery的prototype對(duì)象,這里的plugName等于myList,相當(dāng)于給jQuery對(duì)象添加了一個(gè)"myList"方法
$.fn[pluginName] = function (options, args) {
var result;
////這里的this是一個(gè)jQuery對(duì)象
this.each(function () {
//下面的this不是jQuery對(duì)象,而是jQuery對(duì)象中的Dom對(duì)象
//從Dom對(duì)象中獲取"data-myList"屬性綁定的對(duì)象
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];
}
//調(diào)用MyList對(duì)象的方法
result = _this[options].apply(_this, args);
}
}
else if (typeof options === 'boolean') {
result = _this;
}
else {
//創(chuàng)建MyList對(duì)象并綁定到Dom對(duì)象的data-myList屬性
$.data(this, pluginName, new MyList(this, $.extend(true, {}, options)));
}
});
return result || this;
};
如何使用該插件的方法如下所示:var result;
////這里的this是一個(gè)jQuery對(duì)象
this.each(function () {
//下面的this不是jQuery對(duì)象,而是jQuery對(duì)象中的Dom對(duì)象
//從Dom對(duì)象中獲取"data-myList"屬性綁定的對(duì)象
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];
}
//調(diào)用MyList對(duì)象的方法
result = _this[options].apply(_this, args);
}
}
else if (typeof options === 'boolean') {
result = _this;
}
else {
//創(chuàng)建MyList對(duì)象并綁定到Dom對(duì)象的data-myList屬性
$.data(this, pluginName, new MyList(this, $.extend(true, {}, options)));
}
});
return result || this;
};
首先在html中定義一個(gè)<div id="list1"></div>,然后這樣使用它:
//創(chuàng)建一個(gè)Select包含三個(gè)options
$('#list1').MyList(['Tom','Mary','Alice']);
//添加新的option
$('#list1').MyList('add', [['James','Richard']]);
//刪除option
$('#list1').MyList('remove', [['Alice']]);
最后我們可以在以上例子中發(fā)現(xiàn)創(chuàng)建jQuery插件的總體思路://添加新的option
$('#list1').MyList('add', [['James','Richard']]);
//刪除option
$('#list1').MyList('remove', [['Alice']]);
- 自定義對(duì)象,對(duì)象中包含數(shù)據(jù)和jQuery對(duì)象本身
- 定義對(duì)象的方法,并且根據(jù)對(duì)象中數(shù)據(jù)的變化渲染Dom對(duì)象(通過jQuery對(duì)象獲得Dom對(duì)象)
- 將該自定義對(duì)象方法擴(kuò)展到j(luò)Query原型對(duì)象中
- 創(chuàng)建自定義對(duì)象,并綁定到j(luò)Query中Dom對(duì)象的data屬性