JqueryのDatepickerがTurbolinksで動かなくなることへの対応

投稿者: | 2019年12月25日

JqueryのDatepickerがTurbolinksにより初期表示時に動かない(カレンダーが出ない)という事象に見まわれました。原因特定までで試したこと、対応について記載します。

事象

まず事象についてですが、下記のような現象が発生しました。

  • 画面初期表示時に$(セレクタ).datepicker()は呼び出せているが、カレンダが有効にならない。
  • F5でリロードするとカレンダが有効になる。
  • リンク(アンカー)を別タブで踏むとカレンダが有効になる。

厄介なことに動かない割にはこの処理($(セレクタ).datepicker)の呼び出しそのものはエラーも吐かずに通っています。

原因

原因はTurbolinksを介してページを遷移したとき、「発火しないイベントが有るため。」だそうです。そのためTurbolinks用のトリガーが定義されており、初期表示の実装はこのトリガーに紐付けて実装する必要があるようです。

確認したこと

イベントが発火しないと言われていますが、datepickerの呼び出し処理までは、初期表示時に発火しています。これはconsole.logやalertで確認しています。恐らくはdatepikerファンクションの内部に、発火しない実装が含まれているものと思われます。

Turbolinksが原因であることの確認には、リンク(アンカー)で設定を無効にした場合に、カレンダが有効になるかを確認しています。Turbolinksのバージョン5であれば下記の通りです。バージョンにより少々属性名が異なるため、「Turbolinksを無効にした(つもり)けれどカレンダーが有効にならないから原因が別」とならないようにしましょう。(←事実ちょっと疑った)

<a href="/" data-turbolinks="false">Disabled</a>

私の場合の対策

下記の点を考慮し、Turbolinksを使わない、あるいはTurbolinks前提の実装や検証をせず、「Jqueryのdatepickerを使わない」ことにしました。

  • datepickerの要件としては特にこだわりがなく、Jqueryのdatepickerである必要性がない。
  • 全体のソースコードに占める割合として、極めてdatepicker部分が少ない。

代わりに選んだのは、「js-datepicker」です。

https://www.npmjs.com/package/js-datepicker

jqueryのdatepickerと比べると少々コンパクトですが、サイジングもできる模様です。私の場合は単純に日付を選択させるだけの要件なので、ほぼ困るところはありません。

定義側サンプル

(function($) {
    var obj = {
        customMonths: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
        showAllDates: true,
        formatter: function(input, date, instance) {
            const value = date.toLocaleDateString();
            input.value = value;
        }
    }
    $.fn.bindDatepicker = function(defaultDate = undefined) {
        var day = null;
        if (defaultDate === undefined) {
            obj["dateSelected"] = new Date();
        } else {
            obj["dateSelected"] = new Date(defaultDate);
        }
        datepicker(this.selector, obj);
    }
})(jQuery);

上記はパラメータを逐一設定するのが面倒であるので、ちょっとしたライブラリにしたものです。

画面側の呼び出し

    $("#commit_from").bindDatepicker( new Date( moment().startOf('month').format('YYYY-MM-DD')  ));
    $("#commit_to").bindDatepicker(   new Date( moment().endOf('month').format('YYYY-MM-DD')  ));

初期値として当月の月初、月末を設定して、かつカレンダー化しています。(あ、momentのオブジェクトを渡すことがあるため、new Dateしてますが若干冗長ですね)