Datepicker で minDate/maxDate を変更したときに勝手に日付を調整させない

jQuery UI の Datepicker で期間を指定するとき、終了日を設定した状態で、開始日をそれより前に変更すると、終了日が勝手に変更されるという現象が発生しました。

例えば、

  1. 終了日を6月28日
  2. 開始日を6月29日

という順に指定すると、終了日が勝手に6月30日になってしまいます。

datepicker の例では開始日のカレンダーが、終了日までしか選択できないようになっていますが、キー入力することで同様の現象が発生します。

これは、開始日の change イベントで、終了日の datepicker の最小日(minDate)を6月30日に変更する処理をしているためです。

この挙動がとってもいやで、勝手に変えないようにしたかったので調べました。

jquery-ui.js を見てみると Datepicker クラスの _restrictMinMax メソッドが見つかりました。

        /* Ensure a date is within any min/max bounds. */
        _restrictMinMax: function( inst, date ) {
                var minDate = this._getMinMaxDate( inst, "min" ),
                        maxDate = this._getMinMaxDate( inst, "max" ),
                        newDate = ( minDate && date < minDate ? minDate : date );
                return ( maxDate && newDate > maxDate ? maxDate : newDate );
        },

これを呼び出している _adjustInstDate や、その前の _setDate でも、_restrictMinMax を呼ばないような条件文はないので、_restrictMinMax 自体をオーバーライドすることにしました。

    /* override */
    $.datepicker._restrictMinMax = function(inst, date){
            return date;
    };

jquery-ui.js はいじらずに、上記コードを datepicker を作っている JS に書いておけばOKです。

これで勝手に日付を変更されなくなります。

ちょっと動作がわかりにくいと思うので、サンプルを書いてみました。

下記コードを html ファイルにしてブラウザで開くと、「override?」チェックボックスが、チェックされていなければデフォルトの勝手に変更するモード、チェックすればオーバーライドした勝手に変更されないモードになります。

開始日を終了日の後に変更すると、チェックなしは終了日が開始日の翌日に変更され、チェックありは「NG」が表示されます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>jQuery UI Datepicker - Select a Date Range</title>
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.13.3/themes/base/jquery-ui.css">
    <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
    <script src="https://code.jquery.com/ui/1.13.3/jquery-ui.js"></script>
    <script>
    $(function() {
        const DATE_FORMAT = "yy-mm-dd";
        const from = $("#from")
            .datepicker({
                dateFormat: DATE_FORMAT,
                minDate: '0',
            })
            .on("change", function() {
                let today = new Date();
                today.setHours(0,0,0,0);
                let from_date = getDate(from);
                if (from_date) {
                    if (from_date < today) {
                        from[0].setCustomValidity("NG");
                    } else {
                        from[0].setCustomValidity("");
                    }
                }
                updateFromMessage();
                let to_min = from_date? from_date : today;
                to_min.setDate(to_min.getDate() + 1);
                to.datepicker("option", "minDate", to_min);
                to.datepicker("option", "defaulDate", to_min);
                let to_date = getDate(to);
                if (to_date && to_date < to_min) {
                    to[0].setCustomValidity("NG");
                } else {
                    to[0].setCustomValidity("");
                }
                updateToMessage();
            });

        var to = $("#to")
            .datepicker({
                dateFormat: DATE_FORMAT, 
                minDate: '1d',
            })
            .on("change", function() {
                let today = new Date();
                today.setHours(0,0,0,0);
                let to_min = getDate(from);
                if (!to_min) {
                    to_min = today;
                }
                to_min.setDate(to_min.getDate() + 1);

                let to_date = getDate(to);
                if (to_date) {
                    if (to_date < to_min) {
                        to[0].setCustomValidity("NG");
                    } else {
                        to[0].setCustomValidity("");
                    }
                }
                updateToMessage();
            });
        
        function getDate(element) {
            var date;
            try {
                date = $.datepicker.parseDate(DATE_FORMAT, element.val());
            } catch(error) {
                date = null;
            }
            
            return date;
        }

        function updateFromMessage() {
            if (from[0].checkValidity()) {
                $("#from_mess").text("");
            } else {
                $("#from_mess").text(from[0].validationMessage);
            }
        };
        function updateToMessage() {
            if(to[0].checkValidity()){
                $("#to_mess").text("");
            } else {
                $("#to_mess").text(to[0].validationMessage);
            }
        };

        /* override to don't adjust any date */
        const original_restrictMinMax = $.datepicker._restrictMinMax;
        const dont_restrictMinMax = function(inst, date){
            return date;
        };
        
        $("#is_override_restrictMinMax").on("change", function() {
            $.datepicker._restrictMinMax 
                = this.checked? dont_restrictMinMax : original_restrictMinMax;
        });
    });
    </script>
  </head>
  <body>
    <form onsubmit="return false;">
      <div>
        <label for="from">From</label>
        <input type="search" id="from" name="from" autocomplete="off" required>
        <span id="from_mess" style="color:red;"></span>
      </div>
      <div>
        <label for="to">to</label>
        <input type="search" id="to" name="to" autocomplete="off" required>
        <span id="to_mess" style="color:red;"></span>
      </div>
    <input type="checkbox" id="is_override_restrictMinMax">override?</input>
    </form>
  </body>
</html>

そもそも、datepicker の例でもそうですが、期間指定する際、開始日のカレンダーの maxDate を終了日までしか選択できなくするようなインターフェースは腐っていると思います。

例えば、ホテルの予約を入れるとき、datepicker で7/10~7/11を選んだあとで、実は8/10~8/11だと気づき変更しようとすると、開始日を8/10に変更できないっておかしいですよね。

このようなケースでは終了日から修正すれば変更できる、とか言い出すやつは蹴ってあげてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です