Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
プラグイン作成

プラグイン/作成

jQueryを使うことが快適になってきたら、プラグインの作り方を知りたくなるでしょう。それは正解です!プラグインとメソッドでjQueryを利用することは、非常に協力で、さらに、プラグインの中に最も有効な機能を抽象化することで、開発にかける時間を大幅に節約出来ます。この記事は、プラグインを書き始める際の基本的な概要とベストプラクティス、さらに気をつける必要のある一般的な落とし穴についての記事です。

目次

  1. さあはじめよう
  2. コンテキスト
  3. 基本
  4. メソッドチェーンの維持
  5. デフォルトとオプション
  6. 名前空間
    6.1. プラグインのメソッド
    6.2. イベント
    6.3. データ
  7. 概要とベストプラクティス
  8. 翻訳

さあはじめよう

jQueryプラグインを書くのは、jQuery.fnオブジェクトに新しい関数のプロパティを追加することから開始します、その際のプロパティ名がプラグインの名前になります。

jQuery.fn.myPlugin = function() {

  // ここにあなたの素敵なプラグインを詰め込んでください

};

ちょっと待って下さい!上記にはあのご存知の、あの愛している素晴らしいドルマークはどこにあるのでしょうか?そこに存在してます、とはいえ他のプラグインでもまず利用されているドルマークが衝突しないように確認する必要があります。そのためのjQueryでのベストプラクティスはIIFE(即時関数)で、そこに出てきたドルマークは、そのスコープ内で実行することで他のライブラリに上書きされることはなくなります。

(function( $ ) {
  $.fn.myPlugin = function() {

  // ここにあなたの素敵なプラグインを詰め込んでください

  };
})( jQuery );

これが良いでしょう。このクロージャー内である限り、jQueryオブジェクトが置き換えられたドルマークを好きなように使えます。

コンテキスト

実際のプラグインのコードが記述できる領域を確保出来ました。しかし、その前にコンテキストについて解説します。プラグインの関数のスコープが有効な範囲内では、thisは呼び出されたプラグインのjQueryオブジェクトを参照しています。他のインスタンスではjQueryがコールバックを受け取った時ではthisはネイティブのDOM要素を参照しているので、ここは間違えやすいところです。これのせいでしばしば開発者は、不必要な'this'のjQuery関数でのラッピング(重複)を行なってしまいます。

(function( $ ){

  $.fn.myPlugin = function() {

    // ここでは"this"はすでにjQueryオブジェクトなので 
    // $(this)を行う必要はありません

    // ここでの$(this)は$($('#element'))と同じ事になります

    this.fadeIn('normal', function(){

      // ここでのthisはDOM要素になります

    });

  };
})( jQuery );
$('#element').myPlugin();

基本

jQueryプラグインのコンテキストを理解したので、実際に何か実行するプラグインを書いてみましょう。

(function( $ ){

  $.fn.maxHeight = function() {

    var max = 0;

    this.each(function() {
      max = Math.max( max, $(this).height() );
    });

    return max;
  };
})( jQuery );
var tallest = $('div').maxHeight(); // 一番高いdivの高さを返す

これは、.height()を利用して、ページ内で一番高いdivの高さを返すというシンプルなプラグインです。

メソッドチェーンの持続

上記の事例はページ内の一番高いdivの数値を返すものですが、プラグインはいくつかの方法で要素の集合をシンプルに書き換えていくというものが多く、続いている次のメソッドに対象の要素群を引き渡します。これはjQueryの言語デザインの美しさでもあり、jQueryがこれほど人気になった原因でもあります。ということでプラグインにおいてメソッドチェーンを持続させるために、プラグインはthisを返すようにする必要があります。

(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');

プラグインは直下のスコープ内のthisを返すことで、メソッドチェーンの持続ができ、jQueryは上記の例の.cssのようにjQueryのメソッドで操作を継続できます。もし、プラグインが固有の値を返すものでなければ、プラグインの関数直下のthisを返すようにしてください。また、あなたが考えているように、プラグインに渡された引数もプラグインの関数直下のスコープで呼び出します。上記の例では、'width'という文字列がプラグインの関数のtypeという引数になっています。

デフォルトとオプション

より複雑でカスタマイズができるプラグインは多くのオプションを用意しています。そのためのベストプラクティスはプラグインが呼び出された時に拡張可能($.extendを利用)なデフォルトのセッティングを持っておくことです。

(function( $ ){

  $.fn.tooltip = function( options ) {

    // 渡された任意のオプションで拡張されるデフォルトをいくつか作成する
    var settings = $.extend( {
      'location'         : 'top',
      'background-color' : 'blue'
    }, options);

    return this.each(function() {

      // Tooltipプラグインのコードはここに

    });

  };
})( jQuery );
$('div').tooltip({
  'location' : 'left'
});

この例では、オプションを付加してtooltipプラグインを呼び出した後に、デフォルトの位置設定は'left'に上書きされる一方、背景色の設定はデフォルトの'blue'のままです。というわけで最終的な設定は以下のようになります。

{
  'location'         : 'left',
  'background-color' : 'blue'
}

これは、利用可能なオプションを定義するために開発者を必要とすること無く、高度な設定を要求するすばらしい方法です。

名前空間の指定

プラグインに適切な名前空間を指定することは、プラグイン開発において非常に重要です。名前空間を正しく指定することは、確実にプラグインが同一ページ内の他のプラグインや動作中のコードに上書きされる危険性を極めて小さくします。

プラグインのメソッド

一つのプラグインがjQuery.fnオブジェクトに対して複数のネームスペースを要求しなければいけない状況というのは存在しません。

(function( $ ){

  $.fn.tooltip = function( options ) {
    // これは
  };
  $.fn.tooltipShow = function( ) {
    // 悪い例
  };
  $.fn.tooltipHide = function( ) {
    // です
  };
  $.fn.tooltipUpdate = function( content ) {
    // !!!
  };

})( jQuery );

これは$.fnのネームスペースを混乱させるので推奨しません。これに対処するには、オブジェクトリテラルにプラグインのメソッド全てを集約し、プラグインのメッソドの文字列を渡すことで呼び出す必要があります。

(function( $ ){

  var methods = {
    init : function( options ) {
      // これは
    },
    show : function( ) {
      // 良い例
    },
    hide : function( ) {
      // です
    },
    update : function( content ) {
      // !!!
    }
  };

  $.fn.tooltip = 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 );

// 初期化メソッドの呼び出し
$('div').tooltip();

// 初期化メソッドの呼び出し
$('div').tooltip({
  foo : 'bar'
});
// hideメソッドの呼び出し
$('div').tooltip('hide');
// updateメソッドの呼び出し
$('div').tooltip('update', 'This is the new tooltip content!');

このタイプのプラグインのアーキテクチャーは、全てのメソッドをプラグインの親クロージャーにカプセル化し、渡された最初の文字列によってメソッドが呼び出されます、さらに、そのメソッドが必要となる場合には追加のパラメーターも渡します。このカプセル化とアーキテクチャーは、jQueryプラグインのコミュニティではスタンダードであり、jQueryUIのプラグインやウィジェットも含めて数えきれないプラグインで活用されています。

イベント

あまり知られていませんがbindメソッドの機能は、バインドされたイベントの名前空間の利用ができるということです。もし、プラグインがイベントをバインドするのであれば、名前空間のよいプラクティスになります。この方法は、後ほどunbindする必要がある場合も、バインドされていた同様のタイプのイベントを干渉することはありません。イベントに対して、バインドしているイベントタイプに“.”を追加することで名前空間の指定ができます。

訳者注) bind/unbindメソッドは現在ではon/offメソッドと置き換えて読んだほうがいいでしょう。

(function( $ ){

  var methods = {
     init : function( options ) {

       return this.each(function(){
         $(window).bind('resize.tooltip', methods.reposition);
       });

     },
     destroy : function( ) {

       return this.each(function(){
         $(window).unbind('.tooltip');
       })

     },
     reposition : function( ) {
       // ...
     },
     show : function( ) {
       // ...
     },
     hide : function( ) {
       // ...
     },
     update : function( content ) {
       // ...
     }
  };

  $.fn.tooltip = 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 );
$('#fun').tooltip();
// 中略...
$('#fun').tooltip('destroy');

この例では、tooltipがinitメソッドで初期化された際に、'tooltip'という名前空間で、ウィンドウのリサイズイベントに対してrepositionメソッドをバインドします。後からtooltipを破棄する必要が出てきた時には、アンバインドするメソッドの名前空間(今回のケースではtooltip)を渡すことで、プラグインがバインドしているイベントをアンバインドできます。

訳者注) bind/unbindメソッドは現在ではon/offメソッドと置き換えて読んだほうがいいでしょう。

データ

プラグインの開発中にしばしば、状態の維持もしくは与えられたメソッドが初期化済みであるかをチェックする必要がでてくることがあります。jQueryのdataメソッドを利用することは、要素ごとの値の補足を続けるには優れた方法です。しかしながら、異なる名前空間で分離したデータの束を補足し続けるよりも、ひとつの全ての値を保持する単一のオブジェクトリテラルの利用と、一つのデータの名前空間のオブジェクトにアクセスすることが一番です。

(function( $ ){

  var methods = {
     init : function( options ) {

       return this.each(function(){

         var $this = $(this),
             data = $this.data('tooltip'),
             tooltip = $('<div />', {
               text : $this.attr('title')
             });

         // もしプラグインが初期化されていない場合
         if ( ! data ) {

           /*
             ここに設定の項目を置く
           */

           $(this).data('tooltip', {
               target : $this,
               tooltip : tooltip
           });

         }
       });
     },
     destroy : function( ) {

       return this.each(function(){

         var $this = $(this),
             data = $this.data('tooltip');

         // ネームスペースはここでおしまい
         $(window).unbind('.tooltip');
         data.tooltip.remove();
         $this.removeData('tooltip');

       })

     },
     reposition : function( ) { // ... },
     show : function( ) { // ... },
     hide : function( ) { // ... },
     update : function( content ) { // ...}
  };

  $.fn.tooltip = 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 );

dataを使うことで、プラグインからメソッドをまたいで変数やステータスの捕捉し続けるようになります。データを一つのオブジェクトリテラルで名前空間に指定することは、一箇所からプラグインのプロパティに容易にアクセスできるようになり、同様に必要があればすぐに削除して名前空間のデータを減少させることができます。

概要とベストプラクティス

jQueryのプラグインを書くことは、ライブラリを最大限に活用できるようになり、あなたの持つ最も合理的で有用な機能を時間を節約し開発をより合理的にしてくれる再利用可能なコードへと抽象化します。以下はこのエントリーの簡単な要約と次以降のjQueryプラグインを作成する際の注意点です。

  • 常にプラグインはクロージャーの中に(function( $ ){ /* プラグインはここに */ })( jQuery );
  • プラグインの関数直下のスコープ内のthisを重複してラップしない。
  • プラグインが固有の値を返さない限りは、メソッドチェーンの維持のためにプラグインは常にthisを返す。
  • 長大な引数を要求するよりは、プラグインの設定をオブジェクトのリテラル内にそのプラグインのデフォルトとして拡張できるように用意する。
  • プラグインが複数のネームスペースを持つことでjQuery.fnオブジェクトをちらかさない。
  • メソッドとイベントとデータは常にネームスペースの中に。

翻訳

この記事を翻訳したり、それに準じるようなブログの記事をポストした場合、ここにリンクしてください。完全な翻訳ならば(t)と準じるものなら(s)と印をしてください。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment