jQuery彈簧插件編寫基礎(chǔ)之“又見彈窗”
本文將通過一個實例來引出jQuery插件開發(fā)中的一些細(xì)節(jié),首先介紹下jQuery插件開發(fā)的一些基礎(chǔ)知識。
jQuery的插件開發(fā)主要分為兩類:
1. 類級別,即在jQuery類本身上擴(kuò)展方法,類似與 $.ajax,$.get 等。
2. 對象級別,這里所謂的對象是指通過jQuery選擇器選中的jQuery對象,在該對象上添加方法。例如:$('div').css(), $('div').show() 等。
在實際開發(fā)中,我們通常選用對象級別的方法來開發(fā)插件,jQuery強大的選擇器及對象操作是我們選擇這種方式很大的一個原因。
接下來我們看看兩種方式的具體寫法是什么:
類級別的插件開發(fā)
$.extend({ foo: function() { //... }, bar: function() { //... } }) //調(diào)用 $.foo();
在這里,對擴(kuò)展方法的命名需要考究一些,以免與jQuery類中的原有方法重名。即便如此,當(dāng)我們需要在類上擴(kuò)展多個方法時仍有可能會出現(xiàn)命名沖突的情況,為此我們可以創(chuàng)建自定義的命名空間:
$.myPlugin = { foo: function() { //... }, bar: function() { //... } } //調(diào)用 $.myPulgin.foo();
對象級別的插件開發(fā)
$.fn.foo = function() { //doSomething... } //調(diào)用(假設(shè)擁有一個id為obj的元素) $('#obj').foo(); 有個會問 fn 是什么東東?粘一段別人截取的jQuery源碼就明白了: jQuery.fn = jQuery.prototype = { init: function(selector, context) { //.... } }
原來是原型鏈啊。。。
接收配置參數(shù)
在編寫一個插件時,我們可以讓使用插件的人能按自己的意愿設(shè)置插件的一些屬性,這就需要插件有接收參數(shù)的功能,同時當(dāng)使用插件的人不傳入?yún)?shù)時,插件內(nèi)部也有一套自己默認(rèn)的配置參數(shù)。
$.fn.foo = function(options) { var defaults = { color: '#000', backgroundColor: 'red' }; var opts = $.extend({}, defaults, options); alert(opts.backgroundColor); //yellow } $('#obj').foo({ backgroundColor: 'yellow' })
這里的關(guān)鍵就是 $.extend 方法,它能夠?qū)ο筮M(jìn)行合并。對于相同的屬性,后面的對象會覆蓋前面的對象。為什么extend方法第一個參數(shù)是一個空對象呢?因為該方法會將后者合并到前者上,為了不讓 defaults 被改變所以第一個參數(shù)設(shè)為空對象。
如果我們允許使用插件的人能夠設(shè)置默認(rèn)參數(shù),就需要將其暴露出來:
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); alert(opts.backgroundColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
這樣就可以在外部對插件的默認(rèn)參數(shù)進(jìn)行修改了。
適當(dāng)?shù)谋┞兑恍┓椒?/strong>
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); $.fn.foo.sayColor(opts.backgroundColor); } $.fn.foo.sayColor = function(bgColor) { alert(bgColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
改寫:
$.fn.foo.sayColor = function(bgColor) { alert('background color is ' + bgColor); }
暴露插件中的一部分方法是很牛逼的,它使得別人可以對你的方法進(jìn)行擴(kuò)展、覆蓋。但是當(dāng)別人對你的參數(shù)或方法進(jìn)行修改時,很可能會影響其他很多東西。所以在考慮要不要暴露方法時候要頭腦清楚,不確定的就不要暴露了。
保持函數(shù)的私有性
說到保持私有性,首先想到什么?沒錯,就是閉包:
;(function($) { $.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); debug(opts.backgroundColor); } function debug(bgColors) { console.log(bgColors); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' } })(jQuery)
這是jQuery官方給出的插件開發(fā)方式,好處包括:1.沒有全局依賴 2.避免其他人破壞 3.兼容 '$' 與 'jQuery' 操作符。
如上,debug 方法就成了插件內(nèi)部的私有方法,外部無法對其進(jìn)行修改。在閉包前面加 ; 是防止進(jìn)行代碼合并時,如果閉包前的代碼缺少分號從而導(dǎo)致后面報錯的情況。
合并
;(function($) { //定義插件 $.fn.foo = function(options) { //doSomething... } //私有函數(shù) function debug() { //doSomething... } //定義暴露函數(shù) $.fn.foo.sayColor = function() { //doSomething... } //插件默認(rèn)參數(shù) $.fn.foo.default = { color: '#000', backgroundColor: 'red' } })(jQuery);
以上的代碼就創(chuàng)建了一個完整且規(guī)范的插件骨架,看起來雖然很簡單但在實際開發(fā)中還是有很多技巧與注意事項,接下來我們通過一個實例來看看。
想了半天,覺得將彈窗做成插件當(dāng)作示例是比較合適的。在開發(fā)之前我們先構(gòu)想一下這個彈窗插件的結(jié)構(gòu)與功能等:
從上圖我們看出包括三個部分,標(biāo)題、內(nèi)容、以及按鈕組。這里需要申明一點,我們不想只做成瀏覽器里默認(rèn)的只包含一個按鈕的alert框,而是使用者可以自定義按鈕數(shù)量,這樣該彈出框也能完成類似confirm框的功能。
搭建插件骨架
function SubType($ele, options) { this.$ele = $ele; this.opts = $.extend({}, $.fn.popWin.defaults, options); } SubType.prototype = { createPopWin: function() { } }; $.fn.popWin = function(options) { //this指向被jQuery選擇器選中的對象 var superType = new SubType(this, options); superType.createPopWin(); }; $.fn.popWin.defaults = {};
1. 我們創(chuàng)建了基于對象且名為 popWin 方法,并將 defaults 默認(rèn)配置參數(shù)暴露出去以便使用的人進(jìn)行修改;
2. 這里使用面向?qū)ο蟮姆椒▉砉芾砦覀兊乃接泻瘮?shù),createPopWin 方法就是我們私有的用來創(chuàng)建彈窗的函數(shù)。
3. 在插件被調(diào)用時將jq對象與自定義的參數(shù)傳入構(gòu)造函數(shù)中并實例化。
調(diào)用
設(shè)想一下我們該怎么調(diào)用這個插件呢?我們可以在自己的文檔樹中合適的位置插入一個 div 元素,選中該 div 并調(diào)用我們定義在jQuery對象上的 popWin 方法。
$('#content').popWin({ a: 1, b: 2, callback: function() {} });
調(diào)用 popWin 的同時傳入自定義的配置參數(shù),之后被選中的 div 元素就被神奇的轉(zhuǎn)化成一個彈窗了!當(dāng)然,這只是我們的設(shè)想,下面開始碼代碼。
確定默認(rèn)配置
$.fn.popWin.defaults = { width: '600', //彈窗寬 height: '250', //彈窗高 title: '標(biāo)題', //標(biāo)題 desc: '描述', //描述 winCssName: 'pop-win', //彈窗的CSS類名 titleCssName: 'pop-title', //標(biāo)題區(qū)域的CSS類名 descCssName: 'pop-desc', //描述區(qū)域的CSS類名 btnAreaCssName: 'pop-btn-box', //按鈕區(qū)域的CSS類名 btnCssName: 'pop-btn', //單個按鈕的CSS類名 btnArr: ['確定'], //按鈕組 callback: function(){} //點擊按鈕之后的回調(diào)函數(shù) }
我們定義了如上的參數(shù),為什么有要傳入這么多的CSS類名呢?1. 為了保證JS與CSS盡可能的解耦。 2. 你的樣式有很大可能別人并不適用。所以你需要配置一份樣式表文件來對應(yīng)你的默認(rèn)類名,當(dāng)別人需要更改樣式時可以傳入自己編寫的樣式。
按鈕組為一個數(shù)組,我們的彈窗需要根據(jù)其傳入的數(shù)組長度來動態(tài)的生成若干個按鈕?;卣{(diào)函數(shù)的作用是在用戶點擊了某個按鈕時返回他所點擊按鈕的索引值,方便他進(jìn)行后續(xù)的操作。
彈窗DOM創(chuàng)建
var popWinDom,titleAreaDom,descAreaDom,btnAreaDom; SubType.prototype = { createPopWin: function() { var _this = this; //首次創(chuàng)建彈窗 //背景填充整個窗口 this.$ele.css({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', overflow: 'hidden' }); //窗口區(qū)域 popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName); //標(biāo)題區(qū)域 titleAreaDom = popWinDom.find('div:eq(0)') .text(this.opts.title) .attr('class',this.opts.titleCssName); //描述區(qū)域 descAreaDom = popWinDom.find('div:eq(1)') .text(this.opts.desc) .attr('class',this.opts.descCssName); //按鈕區(qū)域 btnAreaDom = popWinDom.find('div:eq(2)') .attr('class',this.opts.btnAreaCssName); //插入按鈕 this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr({'data-index':index, 'class':_this.opts.btnCssName}) .on('click', function() { _this.opts.callback($(this).attr('data-index')); })); }); this.$ele.append(popWinDom); } }
1. 首先命名了四個變量用來緩存我們將要創(chuàng)建的四個DOM,將傳入的jQuery對象變形成覆蓋整個窗口半透明元素;
2. 創(chuàng)建窗口DOM,根據(jù)傳入的高、寬來設(shè)置尺寸并居中,之后另上傳入的窗口CSS類名;
3. 創(chuàng)建標(biāo)題、描述、按鈕組區(qū)域,并將傳入的標(biāo)題、描述內(nèi)容配置上去;
4. 動態(tài)加入按鈕,并為按鈕加上data-index的索引值。注冊點擊事件,點擊后調(diào)用傳入的回調(diào)函數(shù),將索引值傳回。
好了,我們先看下效果。調(diào)用如下:
$('#content').popWin({ width: '500', height: '200', title: '系統(tǒng)提示', desc: '注冊成功', btnArr: ['關(guān)閉'], callback: function(clickIndex) { console.log(clickIndex); } });
可以看到一個彈窗的DOM已被渲染到頁面中了,當(dāng)點擊關(guān)閉按鈕時控制臺會打印出 "0",因為按鈕組只有一個值嘛,當(dāng)然是第0個了。
如果我們需要多次調(diào)用這個彈窗,每次都要傳入高、寬我會覺得很麻煩。這時我們可以直接在一開始修改插件內(nèi)部的默認(rèn)配置,這也是我們將默認(rèn)配置暴露的好處:
$.fn.popWin.defaults.width = '500'; $.fn.popWin.defaults.height = '200';
要注意的當(dāng)然是不能直接改變defaults的引用,以免露掉必須的參數(shù)。 這樣以后的調(diào)用都無需傳入尺寸了。
我們加一個按鈕并且傳入一個自定義的樣式看看好使不呢?
$('#content').popWin({ title: '系統(tǒng)提示', desc: '是否刪除當(dāng)前內(nèi)容', btnArr: ['確定','取消'], winCssName: 'pop-win-red', callback: function(clickIndex) { console.log(clickIndex); } });
可以看到都是生效了的,當(dāng)點擊“確定”按鈕時回調(diào)函數(shù)返回 0,點擊“取消”按鈕時回調(diào)函數(shù)返回 1。這樣使用插件的人就知道自己點擊的是哪一個按鈕,以完成接下來的操作。
顯示&隱藏
接下來要進(jìn)行打開、關(guān)閉彈窗功能的開發(fā)?;叵肷厦娼榻B的概念,我們想讓使用該插件的人能夠?qū)@兩個方法進(jìn)行擴(kuò)展或者重寫,所以將這兩個方法暴露出去:
$.fn.popWin.show = function($ele) { $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.hide(); }
之后在createPopWin方法中需要的地方調(diào)用這兩個方法。
這里多強調(diào)一點,也是做彈窗控件不可避免的一點:只有當(dāng)我們點擊按鈕以及灰色背景區(qū)域時允許彈窗關(guān)閉,點擊彈窗其他地方不允許關(guān)閉。由于彈窗屬于整個灰色區(qū)域的子節(jié)點,必然牽扯到的就是事件冒泡的問題。
所以在給最外層加上點擊關(guān)閉的事件時,要在彈窗區(qū)域阻止事件冒泡。
popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName).on('click', function(event) { event.stopPropagation(); });
二次打開
我們只需要在第一次調(diào)用插件時創(chuàng)建所有創(chuàng)建DOM,第二次調(diào)用時只更改其參數(shù)即可,所以在createPopWin方法最前面加入如下方法:
if (popWinDom) { //彈窗已創(chuàng)建 popWinDom.css({ width: this.opts.width, height: this.opts.height }).attr('class',this.opts.winCssName); titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName); descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName); btnAreaDom.html('').attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr('data-index',index) .attr('class',_this.opts.btnCssName) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); $.fn.popWin.show(this.$ele); return; }
合并整個插件代碼
;(function($) { function SubType(ele, options) { this.$ele = ele; this.opts = $.extend({}, $.fn.popWin.defaults, options); } var popWinDom,titleAreaDom,descAreaDom,btnAreaDom; SubType.prototype = { createPopWin: function() { var _this = this; if (popWinDom) { //彈窗已創(chuàng)建 popWinDom.css({ width: this.opts.width, height: this.opts.height }).attr('class',this.opts.winCssName); titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName); descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName); btnAreaDom.html('').attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr('data-index',index) .attr('class',_this.opts.btnCssName) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); $.fn.popWin.show(this.$ele); return; } //首次創(chuàng)建彈窗 this.$ele.css({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', overflow: 'hidden', display: 'none' }).on('click', function() { $.fn.popWin.hide(_this.$ele); }); popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName).on('click', function(event) { event.stopPropagation(); }); titleAreaDom = popWinDom.find('div:eq(0)') .text(this.opts.title) .attr('class',this.opts.titleCssName); descAreaDom = popWinDom.find('div:eq(1)') .text(this.opts.desc) .attr('class',this.opts.descCssName); btnAreaDom = popWinDom.find('div:eq(2)') .attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr({'data-index':index, 'class':_this.opts.btnCssName}) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); this.$ele.append(popWinDom); $.fn.popWin.show(this.$ele); } } $.fn.popWin = function(options) { var superType = new SubType(this, options); superType.createPopWin(); return this; } $.fn.popWin.show = function($ele) { $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.hide(); } $.fn.popWin.defaults = { width: '600', height: '250', title: 'title', desc: 'description', winCssName: 'pop-win', titleCssName: 'pop-title', descCssName: 'pop-desc', btnAreaCssName: 'pop-btn-box', btnCssName: 'pop-btn', btnArr: ['確定'], callback: function(){} } })(jQuery);
如上,一個完整的彈窗插件就在這里了。
說下這個標(biāo)紅的 return this 是干什么用的,前面已說過 this 在這里是被選中的jQuery對象。將其return就可以在調(diào)用完我們的插件方法后可以繼續(xù)調(diào)用jQ對象上的其他方法,也就是jQuery的鏈?zhǔn)讲僮鳎f玄乎點就叫級聯(lián)函數(shù)。
OK!趁熱打鐵,我們來看看暴露出去的兩個方法重寫之后效果怎么樣,畢竟對插件暴露部分的擴(kuò)展和重寫是很牛逼的一塊東西。
想象個情景,你用了這個插件后覺得簡單的show和hide效果簡直是low爆了,決定重寫這個彈出和隱藏的效果:
$.fn.popWin.show = function($ele) { $ele.children().first().css('top','-30%').animate({top:'30%'},500); $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.children().first().animate({top:'-30%'},500,function() { $ele.hide(); }); }
你在自己的代碼里加上上面兩段,然后發(fā)現(xiàn)彈窗有了一個簡單的上下滑動進(jìn)入屏幕的效果,同時又不會影響我們彈窗的創(chuàng)建,證明我們的暴露方法還算合理。
當(dāng)然你也可以讓它豎著進(jìn)、橫著進(jìn)、翻著跟頭進(jìn),這就看你自己了。
最后貼上默認(rèn)的樣式表,為了急著想粘回去試試的同學(xué)們。
.pop-win { border: 1px solid #fff; padding: 10px; background-color: #fff; -wekbit-border-radius: 6px; border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3); box-shadow: 0 3px 9px rgba(0,0,0,0.3); } .pop-win-red { padding: 10px; background-color: red; -wekbit-border-radius: 6px; border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3); box-shadow: 0 3px 9px rgba(0,0,0,0.3); } .pop-title { width: 100%; height: 20%; line-height: 40px; padding-left: 10px; box-sizing: border-box; border-bottom: 1px solid #eee; font-size: 17px; font-weight: bold; } .pop-desc { width: 100%; height: 60%; box-sizing: border-box; padding: 10px 0 0 10px; border-bottom: 1px solid #eee; } .pop-btn-box { width: 100%; height: 20%; text-align: right; } .pop-btn { margin: 10px 10px 0 0; width: 60px; height: 30px; }
當(dāng)然這只是個編寫插件的例子,如果要拿出去使用還需要仔細(xì)打磨。例子雖然簡單,旨在拋磚引玉。
相關(guān)文章
jquery全選checkBox功能實現(xiàn)代碼(取消全選功能)
這篇文章主要介紹了jquery實現(xiàn)checkBox全選功能、取消全選功能,代碼簡單,大家可以直接參考使用2013-12-12淺談jquery中的each方法$.each、this.each、$.fn.each
下面小編就為大家?guī)硪黄獪\談jquery中的each方法$.each、this.each、$.fn.each。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06jQuery學(xué)習(xí)筆記之jQuery的DOM操作
jQuery學(xué)習(xí)筆記之jQuery的DOM操作實現(xiàn)方法, 學(xué)習(xí)jquery與dom操作的朋友可以參考下。2010-12-12jQuery實現(xiàn)多級聯(lián)動下拉列表查詢框
這篇文章主要為大家介紹了jQuery實現(xiàn)多級聯(lián)動下拉列表查詢框,感興趣的小伙伴們可以參考一下2016-01-01