jQuery UIのDatepickerを閉じた時にイベントを実行したい

JavaScriptのjQueryにまつわるお話です。

jQuery UIには、カレンダーを使って日付を入力できるDatepicker Widgetがあります。
参考 : [Datepicker | jQuery UI](https://jqueryui.com/datepicker/)

このDatepickerのカレンダーで日付を入力するとカレンダーが閉じて選択された日付がテキストボックスに反映されます。

今回、複数のDatepickerを共通処理を使って一括で作成しているために個々のDatepickerにイベントを設定するのに苦戦しました。

# (最初に)Datepickerを使った入力欄を２つ作りました
そのDatepickerを使って、テキストボックスからでもカレンダーからでも日付を入力できる入力欄を２つ作りました。
![Datepickerを使った入力欄を２つ作りました.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1743295/c32206c8-fc2c-ae9d-0c30-e8f072a9a24d.gif)



## class属性を使ってまとめてDatepickerを作りました。
id属性を使って1つ1つ作る方法もありますが、同じ見た目と動きをして欲しかったのでclass属性を使ってまとめてDatepickerを作りました。
後から、同じDatepickerを追加することになってもHTMLだけの修正ですみます。

```javascript:sample.js
$(function() {
    createDatepicker();
});

/** Datepicker Widgetを作成する. */
function createDatepicker() {
    $('.calender').datepicker({
        showOn: 'button',
        buttonImageOnly : true,
        buttonImage : 'icon.png',
        dateFormat : 'yy/mm/dd',
        onSelect: function(dateText, inst){
            // 日付を選択したらテキストボックスに反映
            var group = $(this).closest('div');
            var dates = dateText.split('/');
            group.find('.year').val(dates[0]);
            group.find('.month').val(dates[1]);
            group.find('.day').val(dates[2]);
        },
    }).hide();
}
```



# (後から)片方のDatepickerだけ処理を追加することになりました
いろいろな利点を考えて、class属性を使ってまとめてDatepickerを作ったもののちょっと困ったことが発生しました。
2つあるうち片方の入力欄だけ処理を追加することになりました。

追加したい処理は、
「[誕生日]入力欄のテキストボックスかカレンダーで日付を入力したら、下に日付文字を表示する」
というものです。

以下のようなメソッドでやりたい処理を実装してみました。

```javascript
/** [誕生日]入力欄の下に入力された日付文字を表示する. */
var outputBirthday = function() {
    let year = $('#birth-year').val();
    let month = $('#birth-month').val();
    let day = $('#birth-day').val();
    if (year && month && day) {
        $('#birthday').text('お誕生日は西暦' + year + '年' + month + '月' + day + '日です。');
    }
}
```

## 問題発生
処理は実装できたものの、Datepickerにイベントを設定するのに問題がありました。

### 問題1 : Datepicker作成時にオプションを指定できない
Datepicker作成時に以下のように実装すると「カレンダーを閉じた時にやりたい処理を実行」することができます。

```javascript
$('.calender').datepicker({
    onClose: outputBirthday
}).hide();
```

しかし今回の場合は、まとめてDatepickerを作成しているので[誕生日]だけではなく[検診日]用のDatepickerにも「やりたい処理」が入り込んでしまいます。
というわけで、「Datepicker作成時にオプションを指定できない」ことになります。

### 問題2 : カレンダーで年月日を選択してもテキストボックスのチェンジイベントが発生しない
カレンダーで日付を選択するとにテキストボックスへ値を反映するようにしています。

```javascript
onSelect: function(dateText, inst){
    // ...省略...
    group.find('.year').val(dates[0]);
    group.find('.month').val(dates[1]);
    group.find('.day').val(dates[2]);
},
```

なので、以下のように「テキストボックスの値が変わったら処理を実行する」と実装するだけで「カレンダーで日付を入力しても処理が実行される」と予想しました。

```javascript
$(function() {
    createDatepicker();
    $('#birth-year').change(outputBirthday);
    $('#birth-month').change(outputBirthday);
    $('#birth-day').change(outputBirthday);
});
```

が、残念ながらカレンダーで年月日を選択してもテキストボックスのチェンジイベントが発生しませんでした。
![テキストボックスのチェンジイベントが発生しない.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1743295/63833ead-512a-b5a6-5069-5c5d4a636b4e.gif)


## 解決策 : Datepickerした後にidでオプションを追加する
「Datepicker作成時にオプションを指定できない」なら作成した後に追加すればいいじゃないか！
「id属性でDatepickerを特定」すればいいじゃないないか！

```javascript
$(function() {
    createDatepicker();
    $('#birth-calender').datepicker('option', 'onClose', outputBirthday);
    $('#birth-year').change(outputBirthday);
    $('#birth-month').change(outputBirthday);
    $('#birth-day').change(outputBirthday);
});
```

ということでこんな感じにDatepicker作成後にid属性を指定してonCloseオプションを指定しました。
うまくいきました！
![idでオプションを追加する.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1743295/1a68c4d9-ff58-5918-b233-587118be525b.gif)

# おわりに
良かれと思ってclass属性による処理を作っても、今回はそれが足枷になって結局id属性を使うことになってしまいました。
「class属性による実装」にも「id属性による実装」にもそれぞれの利点と欠点があります。
また、開発当初は「利点」だったこともシステムの仕様変更などで「欠点」になってしまうこともあります。
「class属性による実装」「id属性による実装」 どちらかが良くてどちらかが悪いというのではなく、それぞれの利点と欠点を知ってうまく組み合わせていきたいですね。

<details><summary>使ったHTMLとJavaScriptの全内容はこちら(▶をクリック)</summary><div>

```html:sample.html
<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8'>
  <title>Page Title</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.min.css">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
  <script src='sample.js'></script>
  <style type="text/css">
  <!--
    .group {
      display: flex;
      align-items: center;
      margin: 10px;
    }
  -->
  </style>
</head>
<body>
  <div class="group">
    検診日 : 西暦
    <input type="text" size="4" class="year" />年
    <input type="text" size="2" class="month" />月
    <input type="text" size="2" class="day" />日
    <input type="text" class="calender" />
  </div>
  <div class="group">
    誕生日 : 西暦
    <input type="text" id="birth-year" size="4" class="year" />年
    <input type="text" id="birth-month" size="2" class="month" />月
    <input type="text" id="birth-day" size="2" class="day" />日
    <input type="text" class="calender" id="birth-calender" />
  </div>
  <h2 id="birthday"></h2>
</body>
</html>
```

```js:sample.js
$(function() {
    createDatepicker();
    $('#birth-calender').datepicker('option', 'onClose', outputBirthday);
    $('#birth-year').change(outputBirthday);
    $('#birth-month').change(outputBirthday);
    $('#birth-day').change(outputBirthday);
});

/** Datepicker Widgetを作成する. */
function createDatepicker() {
    $('.calender').datepicker({
        showOn: 'button',
        buttonImageOnly : true,
        buttonImage : 'icon.png',
        dateFormat : 'yy/mm/dd',
        onSelect: function(dateText, inst){
            // 日付を選択したらテキストボックスに反映
            var group = $(this).closest('div');
            var dates = dateText.split('/');
            group.find('.year').val(dates[0]);
            group.find('.month').val(dates[1]);
            group.find('.day').val(dates[2]);
        },
    }).hide();
}

/** [誕生日]入力欄の下に入力された日付文字を表示する. */
var outputBirthday = function() {
    let year = $('#birth-year').val();
    let month = $('#birth-month').val();
    let day = $('#birth-day').val();
    if (year && month && day) {
        $('#birthday').text('お誕生日は西暦' + year + '年' + month + '月' + day + '日です。');
    }
}
```
</div></details>