クラスを使用したjQueryプラグインのテンプレート

# 前置き
いくつかjQueryプラグインを作成したことで、ようやく自分の中でお決まりのパターンのようなものが出来上がりつつあります。いまさら感満載ではありますが、プラグイン開発時のテンプレートの一例として、投稿しておきます。

テンプレートといってHogeとかFugaとかいうコードににしてしまうとイメージが湧きづらいので、サンプルプラグインを実装して公開することにします。プラグイン開発に慣れていない方のことも考慮して、お節介にもコメントもふんだんに盛り込んでみました。

# サンプル Loggingプラグインの仕様
* テキストフィールド等のコントロールの変更を監視し、値が変更されたらコンソールに変更前と変更後の値を出力する
* プラグイン初期化時にオプションを指定できる

```js
$('.watch-me').logging({
  label: '[変更されました]',
});
```
* 各要素個別のオプションをdata属性にて指定できる

```html
<input type="text" class="watch-me" data-label="「あなた、変わったわ・・・」" />
```
* 後から監視をオフできる

```js
var $inputs = $('.watch-me').logging();
...
$('#stop-button').on('click', function(e) {
  $inputs.logging('off');
});
```

# テンプレート
```js
;(function($) {
  'use strict';

  /*
   * methods: プラグインメソッド
   *
   * ここで指定したメソッドはjQueryオブジェクトから呼び出すことができます。
   *
   * 例）
   * var $inputs = $('.watch-me').logging();
   *
   * $('#stop-button').on('click', function(e) {
   *   $inputs.logging('off');
   * });
   *
   */
  var methods = {
    init: function(options) {
      var settings = $.extend({
        /*
         * デフォルトオプションをここに記述します。
         * 各要素のdata属性で上書きされるので、キーはキャメルケースで記述します。
         *   o greatWonderfulOption
         *   x great_wonderful_option
         */
        label: '[changed]',
      }, options);

      return this.each(function() {
        var $this = $(this);
        var logging = new Logging($this, mergeOptions($this, settings));

        // ここに初期化処理を記述します
        logging.observe();

        $this.data('plugin_logging', logging);
      });
    },

    off: function() {
      return this.each(function() {
        var logging = $(this).data('plugin_logging');
        logging.stopObserving();
      });
    },
  };

  /*
   * 要素個別のdata属性をマージしたオプションを返します。
   *
   * 例えば greatWonderfulOption というオプションを上書きしたい場合は、
   * 次のようにdataタグを記述します。
   * <input type="text" class="watch-me" data-great-wonderful-option="オプション値" />
   */
  var mergeOptions = function($el, settings) {
    return $.extend({}, settings, $el.data());
  };

  /*
   * jQueryプラグイン登録
   */
  $.fn.logging = 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.logging');
    }
  };


  /*
   * プラグイン用クラス
   *
   * プラグインのメイン処理はここに書きます。
   *
   */
  var Logging = (function() {

    var NAMESPACE = '.logging';

    /*
     * コンストラクタ
     */
    var Logging = function($el, settings) {
      this.$el = $el;
      this.settings = settings;
    };

    // クラスメソッド（あれば）
    // Logging.classMethod = function() {};

    /*
     * インスタンスメソッド
     */
    $.extend(Logging.prototype, { /*親クラス.prototype（あれば）*/ }, {

      observe: function() {
        /*
         * NAMESPACEについて
         * $jquery.onでイベントリスナーを登録する際にネームスペースを指定することができます。
         * 指定しておけば、ネームスペース単位でリスナーを解除できるので、
         * 自分で登録したイベントだけを安全に解除できます。
         *   $('.watch-me').on('change.myPlugin', function(e) {...});
         *   $('.watch-me').off('.myPlugin'); //ネームスペース.myPluginだけをオフ
         */
        this.$el.on('focus' + NAMESPACE, this._bind('onFocus'));
        this.$el.on('change' + NAMESPACE, this._bind('onChange'));
      },

      stopObserving: function() {
        this.$el.off(NAMESPACE);
      },

      /*
       * focusやchangeなどイベントリスナーから呼び出されるメソッドについては、
       * "onリスナー名"という命名にしておけば分かりやすくて良いかと思います。
       */
      onFocus: function(e) {
        this.originalValue = this.$el.val();
      },

      onChange: function(e) {
        console.debug([this.settings.label, this.originalValue, '->', this.$el.val()].join(' '));
      },

      /*
       * 指定したメソッドのthisがLoggingクラスのインスタンスになるように束縛します
       */
      _bind: function(funcName) {
        var that = this;
        return function() { that[funcName].apply(that, arguments) };
      },
    });

    return Logging;
  })();

})(jQuery);
```

# ソース
https://github.com/itmammoth/jqueryPluginTemplateUsingClass

# 参考
https://gist.github.com/maepon/4754210