2012年3月10日 星期六

[jQuery] jQuery Plugin 寫法

由於網頁設計目前用到jQuery的機率越來越高,常常寫了一段程式碼需要重複使用,於是決定把它製作成模組使用

建立一個Plugin

(function($){
    $.fn.helloworld = function(){
        alert('Hello World');
    }
})(jQuery);

照jQuery文件說明,是可以這樣寫

jQuery.fn.helloworld = function(){
    alert('Hello World');
}

不過一方面為了方便使用「$」字號,且不與其他函式庫衝突,所以就使用外層包著,這樣就可以在裡面使用「$」字號,不會衝突。

this代表的意義

假設我們這麼寫了一段程式碼

(function($){
    $.fn.showbox = function(){
        this.fadeIn('slow');
    }
})(jQuery);

然後我們只要這樣使用

$('div').showbox();

div就會依照Plugin內的程式碼去執行,this就等於div這個物件,不需再另外再去指定。

保持鏈結(Maintaing Chainability)

下面有個範例<取自jQuery網站>

(function( $ ){

  $.fn.lockDimensions = function( type ) {  

    return this.each(function() {

      var $this = $(this);

      if ( !type || type == 'width' ) {
        $this.width( $this.width() );
      }

      if ( !type || type == 'height' ) {
        $this.height( $this.height() );
      }

    });

  };
})( jQuery );
$('div').lockDimensions('width').css('color', 'red');

照上面的範例,因為這個物件必須接著下面繼續的動作,所以在lockDimensions這個plugin必須回傳this,才能繼續接著.css()這類其他jQuery的函式,要是沒有回傳this的話,就會接不到物件,而後面動作都沒有效果。

設定預設值和選項(Defaults and Options)

許多功能性較複雜的plugin,會給予使用者很多選項去選擇所需的功能,來符合自己的需求,而這些選項必然需要預設值來確保plugin的運作正常,當使用者忘記給予一些參數時,預設值能自動替補上去,來讓程式運行正常。

(function( $ ){

  $.fn.box = function( options ) {  

    //設定選項和預設值
    var settings = $.extend( {
      'width'         : '200px',
      'height'        : '200px',
      'background-color' : 'blue'
    }, options);

    return this.each(function() {        
       alert(settings.width);
    });

  };
})( jQuery );

$("div").box();
//200px


$("div").box(
    {
        'width': 300px
    }
};
//300px                   

在沒有給予選項狀態下,plugin預設會使用程式碼編寫時的預設值。

預設值是一個非常重要的東西,常常再編寫程式碼時忘記給予參數造成程式掛點,如果有設定預設值,就算忘記給予參數,也可以讓程式順利執行。

命名(Namespacing)

這段是jQuery文件中提到的,我也非常認同好的且正確的命名plugin是應該做的事情,其實這也不也僅限於jQuery Plugin而已,在任何程式碼中正確的命名都是必須的,最好用幫自己小孩取名子的心態下去取,總不會把自己的小孩叫a,b,c這類的名稱吧,文件中提到正確的命名可以將名稱衝突的機率降到最低,避免太多隻plugin一起引入時,因為不好的命名結果造成覆蓋,程式無法正確執行,另一方面,正確的命名可以方便開發者快速的追蹤問題,這是非常重要的,畢竟時間就是金錢,因為今天自己的命名不小心結果造成要花更大的成本去檢查錯誤,最後發現命名衝突,這該有多嘔。

多種方法(Plugin Methods)

有的時候我們在寫一隻plugin,會有許多方法可以讓使用者可以選擇,但是每種方法都寫一隻plugin又太多餘,且難以維護管理,且互相都有相依性,這時候就可以這樣寫。

(function($){
    var methods = {
        init : function( options ) { 
            var settings = $.extend({
                'width': '200px'
            },options);
            this.css('width', settings.width); 
        },
        show : function( ) {
            this.show();
        },
        hide : function( ) { 
            this.hide();
        },
        spin : function( content ) { 
            alert(content);
        }
     };

     $.fn.box = function( method ) {
         // Method calling logic
         if ( methods[method] ) {
             return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
         }else if( typeof method === 'object' || ! method ) {
             return methods.init.apply( this, arguments );
         }else{
             $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
         }    
     };
})(jQuery);

然後可以這樣使用

$("div").box();
//call init function;
//set box width 200px.

$("div").box({width: '377px'});
//call init function with options
//set box width 377px.

$("div").box('hide');
//call hide funtion
//div hide

$("div").box('spin', 'This is box.');
//call spin function with argument
//This is box.

這樣就可以依照參數的不同去執行不同的動作,然後又屬於同一隻plugin內,不需要為每種動作都建立一個plugin,我覺得很方便。

事件(Events)

在寫plugin時,一定會用到許多事件問題,常常要綁定一些事件,例如:「click」「blur」這類的事件要處理,在有些情況下,必須要讓這些事件停止動作,我們就必須「unbind」這些事件。所以我們常常會這樣:

(function($){
    var methods = {
        init: function() {  
            return this.each(function(){
                 $(window).bind('click', methods.start);
                 //未加入namespacing
            );  
        },  
        stop: function() {  
            return this.each(function(){
                 $(window).unbind('click');
                 //有可能干擾到其他相同類型事件
            });  
        },
        start: function(){
             ..........
        }  
    }

    $.fn.runner = function( method ) {   
        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
        }     
    };
})(jQuery);

如果照上面那樣寫的話,在初始化runner這個物件時,會綁定click事件,這沒有甚麼太大問題,但是當執行stop時,避免unbind「click」時干擾到其他事件,所以我們可以這樣寫:

    var methods = {
        init: function() {  
            return this.each(function(){
                 $(window).bind('click.runner', methods.start);
                 //加入了namespacing
            );  
        },  
        stop: function() {  
            return this.each(function(){
                 $(window).unbind('click.runner');
                 //或是 $(window).unbind('.runner');
                 //這樣就不會影響其他的事件
            });  
        },
        start: function(){
             ..........
        }  
    }

    $.fn.runner = function( method ) {   
        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
        }     
    };

這樣寫的話就可以緊限於這個物件的事件,而不去影響到其他plugin物件的運作。

沒有留言:

張貼留言