需求場景
最近項目改版中,發現很多地方有這樣一個操作(見下圖gif動畫演示),很多地方都有用到。這里不討論它的用戶體驗怎么樣。
僅僅是從復用的角度,如果每個頁面都去寫text和select元素,兩個button按鈕,增加add和delete對應的js函數,無疑大大增加了工作量和維護成本。
select有預設值的情況:
下面就開始動手把這4個html元素做成一個JQuery插件。
jquery插件結構
如果你是零基礎,請參考Jquery官網對jquery plugin的介紹:http://learn.jquery.com/plugins/
整個插件做好后是一個js文件,我們首先來看下它的整體結構,如下圖:
調用時的代碼(無參):
$(".demo1").dlpcustomSelect();
調用時的代碼(有參):
$(".demo1").dlpcustomSelect({ addButtonText:'Add', delButtonText:'>>' });
前台HTML:
<select class="demo1"></select>
這樣就把插件定義的html元素全部渲染出來了,插件自帶了相關js函數和功能。代碼維護起來非常方便。
下面我們開始這個Jquery插件的制作過程:
一. 定義插件結構,插件名稱,默認值和構造函數
1.插件結構,名稱,默認值
新建dlpcustomselect.js文件后,我們首先書寫這樣的代碼:
;(function ($, window, document, undefined) { //Author:HANGWEI //Create the defaults once var pluginName = 'dlpcustomSelect', defaults = { addButtonEnabled : true, addButtonText: 'Add', delButtonText:'Delete' }; //... other code ... // A really lightweight plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[ pluginName ] = function (options) { var args = arguments; // Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin. if (options === undefined || typeof options === 'object') { return this.each(function () { // If this is not a select if (!$(this).is('select')) { $(this).find('select').each(function(index, item) { // For each nested select, instantiate the dlp custom select $(item).dlpcustomSelect(options);//注意此處的插件名稱 }); } else if (!$.data(this, 'plugin_' + pluginName)) { // Only allow the plugin to be instantiated once so we check that the element has no plugin instantiation yet // if it has no instance, create a new one, pass options to our plugin constructor, // and store the plugin instance in the elements jQuery data object. $.data(this, 'plugin_' + pluginName, new DlpCustomSelect(this, options));//注意此處插件的構造函數 } }); // If the first parameter is a string and it doesn't start with an underscore or "contains" the `init`-function, // treat this as a call to a public method. } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { // Cache the method call to make it possible to return a value var returns; this.each(function () { var instance = $.data(this, 'plugin_' + pluginName); // Tests that there's already a plugin-instance and checks that the requested public method exists if (instance instanceof DlpCustomSelect && typeof instance[options] === 'function') {//注意此處插件構造函數名 // Call the method of our plugin instance, and pass it the supplied arguments. returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1)); } }); // If the earlier cached method gives a value back return the value, // otherwise return this to preserve chainability. return returns !== undefined ? returns : this; } }; })(jQuery, window, document);
在上述代碼中,pluginName是插件名稱,defaults規定了插件的三個參數及其默認值;
$.fn[pluginName]=function(options){};函數的功能,如代碼注釋所說,阻止多個插件實例被創建。
;(function ($, window, document, undefined) { 這句代碼的詳細解釋請參考 這里 。
2. 構造函數
// The actual plugin constructor function DlpCustomSelect(element, options) { this.element = $(element); // jQuery has an extend method which merges the contents of two or // more objects, storing the result in the first object. The first object // is generally empty as we don't want to alter the default options for // future instances of the plugin this.settings = $.extend({}, defaults, options); this._defaults = defaults; this._name = pluginName; this.init(); }
構造函數用於初始化和參數定義,如無特殊需求, 可參考上述寫法來定義。
二. 編寫插件核心部分代碼
下面我們開始章節一代碼結構中: other code 的部分
1. 構造函數名.prototype={...};

1 DlpCustomSelect.prototype = { 2 init: function () { 3 // Add the custom HTML template 4 this.container = $('' + 5 '<div class="dlpcustomselect-container">' + 6 '<table class="box1">' + 7 ' <tr>' + 8 ' <td><input class="waitAddValue" type="text" size="48" /></td>' + 9 ' <td><input class="btn-pull-buttom" type="button" /></td>' + 10 ' </tr>' + 11 ' <tr>' + 12 ' <td><select style="width: 265px;height: 100px" multiple="multiple"></select></td>' + 13 ' <td><input class="btn-delete-buttom" type="button" /></td>' + 14 ' </tr>' + 15 '</table>'+ 16 '</div>') 17 .insertBefore(this.element); 18 19 // Cache the inner elements 20 this.elements = { 21 originalSelect: this.element, 22 box1: $('.box1', this.container), 23 filterInput1: $('.box1 .waitAddValue', this.container), 24 select1: $('.box1 select', this.container), 25 addButton: $('.box1 .btn-pull-buttom', this.container), 26 deleteButton: $('.box1 .btn-delete-buttom', this.container) 27 }; 28 29 // Set select IDs 30 this.originalSelectName = this.element.attr('name') || ''; 31 var select1Id = 'dlpcustomselect-list_' + this.originalSelectName; 32 this.elements.select1.attr('id', select1Id); 33 34 // Apply all settings 35 this.setAddButtonEnabled(this.settings.addButtonEnabled); 36 this.setAddButtonText(this.settings.addButtonText); 37 this.setDelButtonText(this.settings.delButtonText); 38 39 //updateSelectionStates(this); 40 // Hide the original select 41 this.element.hide(); 42 43 bindEvents(this); 44 refreshSelects(this); 45 46 return this.element; 47 }, 48 setAddButtonEnabled: function(value, refresh) { 49 this.settings.addButtonEnabled = value; 50 if (value) { 51 this.container.find('.btn-pull-buttom').removeAttr("disabled"); 52 } else { 53 this.container.find('.btn-pull-buttom').attr("disabled","disabled"); 54 } 55 if (refresh) { 56 //refreshSelects(this); 57 } 58 return this.element; 59 }, 60 setAddButtonText: function(value, refresh) { 61 this.settings.addButtonText = value; 62 if (value) { 63 this.elements.addButton.show().val(value); 64 //if upper code type doesn't work,use this code. 65 //this.container.find('.btn-pull-buttom').show().val(value); 66 } else { 67 this.elements.addButton.hide().val(value); 68 //if upper code type doesn't work,use this code. 69 //this.container.find('.btn-pull-buttom').hide().val(value); 70 } 71 if (refresh) { 72 //refreshSelects(this); 73 } 74 return this.element; 75 }, 76 setDelButtonText: function(value, refresh) { 77 this.settings.delButtonText = value; 78 if (value) { 79 this.elements.deleteButton.show().val(value); 80 } else { 81 this.elements.deleteButton.hide().val(value); 82 } 83 if (refresh) { 84 //refreshSelects(this); 85 } 86 return this.element; 87 }, 88 getCustomData: function(){ 89 var terms = new Array(); 90 this.container.find('.box1 select option').each(function(index, item) { 91 terms.push(item['value']); 92 }); 93 return terms; 94 }, 95 getContainer: function() { 96 return this.container; 97 }, 98 destroy: function() { 99 this.container.remove(); 100 this.element.show(); 101 $.data(this, 'plugin_' + pluginName, null); 102 return this.element; 103 } 104 };
init:function(){...} 需要實現的init函數,這里我們用來創建html模板、應用所有的設置、調用綁定事件。
方法 refreshSelects(this)用來將前台select中的元素(如果有的話)copy到插件中。
方法 getCustomData用來返回插件的值
這里的設計思路是:隱藏你在前台寫的select元素,返回插件模板中的自定義html串。另外,如果前台select中有option元素,則同步copy到插件中。
三. 綁定插件按鈕事件和書寫自定義功能函數
//bind events for button function bindEvents(dlpCustomSelect) { dlpCustomSelect.elements.addButton.on('click', function() { addOption(dlpCustomSelect); }); dlpCustomSelect.elements.deleteButton.on('click', function() { deleteOption(dlpCustomSelect); }); //backup method. /* $(document).on('click', '.box1 .btn-pull-buttom', function() { addOption(dlpCustomSelect); }); */ }
如以上代碼所示,綁定插件的兩個按鈕的事件。其中addOption和deleteOption函數具體實現這里不再詳細闡述。
根據項目實際需要,增加自定義綁定事件和函數。
四. 完成並測試
調用插件:
$(".demo1").dlpcustomSelect({ addButtonText:'Add', delButtonText:'>>' });
不更改參數值,直接調用(使用默認參數值):
$(".demo2").dlpcustomSelect();
獲取插件中的值:$(".demo1").dlpcustomSelect('getCustomData')
demo下載
總結
本插件的實現大量參考了國外jquery插件制作的通用做法。希望本篇文章能起到拋磚引玉的作用,能引導開發人員自己動手寫jquery插件。
本來想掛在github上,但鑒於這個插件的功能很少,就不費周章了。需要的直接在本篇文章下載吧。
另外本人水平有限,如有錯誤之處還請各位批評指正。
希望本文對你有幫助。