読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

Select2-4.0系へ移行するために必要な修正点メモ

javascript

コンボボックスをスーパー便利にしてくれる素晴らしいjQueryプラグイン、Select2。
使っている人も多いと思います。

http://select2.github.io/


本日4.0系がリリースされたので早速、移行してみました。


リリースノートでは「できるだけ互換性は維持した」とありますが、オプションを一切指定しないような使い方をしていない限り、おそらくそのままでは動かないと思います。

少なくとも僕は、まともに動くようになるまでそれなりに修正が必要でした。


以下、自分の対応内容をメモっときます。

オプションの一覧について

ドキュメントには、指定可能なオプションに関する完全な記述が無い模様。(※パッと見)
ソースを読んだほうが早いです。
http://github.com/select2/select2/blob/master/src/js/select2/defaults.js


当該箇所を抜粋したものが以下。

    this.defaults = {
      amdBase: './',
      amdLanguageBase: './i18n/',
      closeOnSelect: true,
      debug: false,
      dropdownAutoWidth: false,
      escapeMarkup: Utils.escapeMarkup,
      language: EnglishTranslation,
      matcher: matcher,
      minimumInputLength: 0,
      maximumInputLength: 0,
      maximumSelectionLength: 0,
      minimumResultsForSearch: 0,
      selectOnClose: false,
      sorter: function (data) {
        return data;
      },
      templateResult: function (result) {
        return result.text;
      },
      templateSelection: function (selection) {
        return selection.text;
      },
      theme: 'default',
      width: 'resolve'
    };
  };

見てわかると思いますが、オプション名が結構変更されています。

formatResult→templateResult

formatResultはtemplateResultという名前に変更されました。
メソッドの仕様は同じなので変更不要。

formatSearching、formatNoMatches→オプション指定不可

formatSearching、formatNoMatchesはオプション指定できなくなりました。

ではどうするかというと、以下のように強引にデフォルト値を書き換えます。

// 元はformatNoMatchesだったもの
$.fn.select2.defaults.defaults.language.noResults = function() {
    return '一致するものがありません';
};

// 元はformatSeachingだったもの
$.fn.select2.defaults.defaults.language.searching = function() {
    return '検索中...';
};

あるいは、手抜きせずきちんとやるなら次の通り。

var jpLang = {
    errorLoading: function() {
        return '内容を取得できません';
    },
    inputTooLong: function(args) {
        var overChars = args.input.length - args.maximum;

        return '入力が ' + overChars + ' 文字超過しています';
    },
    inputTooShort: function(args) {
        var remainingChars = args.minimum - args.input.length;

        return '入力が ' + remainingChars + ' 文字不足しています';
    },
    loadingMore: function() {
        return '更に読み込んでいます...';
    },
    maximumSelected: function(args) {
        return '一度に選択できるのは ' + args.maximum + ' つまでです';
    },
    noResults: function() {
        return '一致するものがありません';
    },
    searching: function() {
        return '検索中...';
    }
};

var select2Opts = {
    ...
    language: jpLang
};

$select.select2(select2Opts);


ところで、理由は調べてませんが

$.fn.select2.defaults.set('language', jpLang);

のようにデフォルト値にセットしても動かないのでご注意を。

イベント名の変更

例えばselect2-openselect2:openへ。その他も同じ命名規約です。

詳しくはサンプルのページの"Events"の項を参照。

選択解除時にコンボボックスがオープンされる振る舞いを解除

選択解除(コンボボックス右側の×マークを押す操作)した時にコンボボックスがオープンされてしまうようです。
ウザったいので、以下のようにして強引に阻止します。

$select.on('select2:unselecting', function(e) {
    // イベントをキャンセル
    e.preventDefault();

    // 手動で選択解除する
    $select.val('').change();
});

.select2-choice→.select2-selection (※ただし厳密には違う)

選択済みoption要素には.select2-choiceではなく.select2-selectionというクラスが付くようになりました。

しかし厳密には同じではなく、選択解除ボタンの"×"のテキストも含まれてしまいます。
ちなみにselect2-4.0系ではアイコン画像が廃止されました。


要するにこういうことです。

// select2-3.0系
$(".foo .select2-choice").text() === "あいう"


// select2-4.0系 
$(".foo .select2-selection").text() === "×あいう"


Seleniumでテストを書いていたりするとコレは致命的です。

assertEquals($(".foo .select2-selection").text(), "あいう")

のようなコードが全て失敗するようになるからです。


とりあえずの回避策としては

$.fn.select2.defaults.set('templateSelection', function(result) {
    return $("<span class='select2-choice'/>").text(result.text);
});

のように、下位互換性のあるHTMLが生成されるようにすればよいかと。

matcherのインタフェースが変更

元は次のようなインタフェースでしたが、

/**
  * @param {!string} term 検索文字列
  * @param {!string} label option要素の内容
  * @param {!jQuery} option要素(jQuery型)
  * @return {!boolean} マッチしたか否か
  */
function(term, label, $option) { ... }


select2-4.0系ではこのようになりました。

/**
  * @typedef {
  *     term:(string|undefined)
  *  }
  */
SelectionParams;

/**
  * @typedef {
  *     text:!string, 
  *     title:!string, 
  *     selected:!boolean, 
  *     disabled:!boolean,
  *     element:!HTMLOptionElement
  *  }
  */
SelectionData;

/**
  * @param {!SelectionParams} params 検索条件
  * @param {!SelectionData} data 検索対象および結果
  * @return {?SelectionData} 検索結果(マッチしたならdata, しないならnullを返す)
  */
function(params, data) { ... }


手っ取り早く対応するには、

function matcherAdapter(matcher) {
    return function(params, data) {
        return matcher(
            params.term || '',
            data.text,
            $(data.element)) ? data : null;
    };
}

のようなアダプタをかませばよいかと。