jQuery UIのdatepickerを使って特定の日付だけマーキングさせる

# 0. はじめに
　少しずつ開発している日報アプリのlytnote。日報を毎日記入していくけど、なにか良いフィードバックが欲しいところ。ラジオ体操のスタンプカードのような、githubの草のような。

　というわけでカレンダーをつくって、日報があったところが塗られるようにした。

# 1. datepickerの導入

　カレンダーを自前で作るのはかなり大変なので、有名なライブラリを使うことにした。ここはjQuery UIのdatepickerだろう。

https://jqueryui.com/datepicker/

　導入は簡単で、CDNを貼るだけだった。以下のサイトからここからできる。
https://releases.jquery.com/

私の場合は`application.html.erb`に貼った。
```erb:app/views/layouts/application.html.erb
<!-- 略 -->
<head>
<title>lytnote</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<!-- 略 -->
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<!-- 略 -->
</head>
```

あとはviewファイルとjsファイルは以下のようにする。

```erb:app/views/reports/index.html.erb
<!-- 略 -->
<div id="datepicker"></div>
<!-- 略 -->
```

```js:app/javascript/home.js
$(document).on("page:load turbolinks:load", function() {
//略
    $( "#datepicker" ).datepicker();
});
```

```js:app/javascript/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

require("jquery")
require("@nathanvda/cocoon")

require ('home')
```

これでdatepickerが表示される。

# 2.「特定の日」を取得してデータを受け渡す
　日報一覧の画面が出た時点で、特定の日、つまりは日報が作成された日付の配列を取得する。コントローラを以下のようにする。

```ruby:app/controllers/reports_controller.rb
def index
    @reports= Report.includes(:report_items).where(user_id: current_user.id).page(params[:page]).order(reported_on: :desc)
    reported_days_original = @reports.map(&:reported_on)
    reported_days = reported_days_original.map{
      |days| days.strftime("%F")
    }
    @reported_days = reported_days.to_json
end

#略...
```

　`@reports`云々は気にしなくていい。それ以降が重要だ。

railsでは指定したカラムのデータを取得するのに、`pluck`というメソッドがある。しかし、`@reports.pluck(:reported_on)` とは使用できない。（使用できるけど値が重複してしまったりする。）

以下のような理由かららしい。


>知っておくと良い注意点は、リレーションオブジェクトにincludesがあると、eager loadingが不必要なクエリにおいてでも、pluckがeager loadingを引き起こすことです。
https://railsguides.jp/active_record_querying.html#pluck

　このeager loadingが詳しくわからなかった。ひとまずはmapで置き換えることにした。


- `days.strftime("%F")`によってreported_daysの配列の要素を`2023-01-01`のように変換している。
　
- to_jsonを使って、railsからJSへデータの受け渡しを行えるように整形しておく。


　viewファイルに以下を追記する。

```erb:app/views/reports/index.html.erb
<!-- 略 -->
<input type="hidden" id="reported" value="<%= @reported_days %>">
<div id="datepicker"></div>
<!-- 略 -->
```

inputタグをhiddenにして記入しておくと、表示せずにデータだけ受け渡すことが可能となる。



# 3. カレンダーの特定の日付にCSS classを付与
　特定の日付にだけclassを付与させる場合は`beforeShowDay` メソッドを使う。functionと戻り値の記述方法は以下のようにする。

```js
$( "#datepicker" ).datepicker({
    beforeShowDay: function(date) {
        return [false, '', ''];
    }
});
```

functionのなかのdateには日付が入っている。Chromeのデベロッパーツールで確認すると、一ヶ月とその前後3~4日を一つずつループして確認しているようだ。戻り値は配列で記述しなければいけない。それぞれ
```
[日付選択の可否(true/false),CSSのclass名,日付にカーソルをあてると出てくる値]
```
となる。うしろの二つは""と空白にしても構わない。

もう少し詳しく書くと以下になる。

```js:app/javascript/home.js
$(document).on("page:load turbolinks:load", function() {
//略...

 let reportedDays_json = document.getElementById('reported').value;
  if (reportedDays_json !== null) {
    let reportedDays = JSON.parse(reportedDays_json);
    $( "#datepicker" ).datepicker({
      beforeShowDay: function(date) {
          let formattedDay = dayjs(date).format('YYYY-MM-DD');
          if (reportedDays.indexOf(formattedDay) != -1) {
            return [false, 'reported-days', ''];
          }else{
            return [false, '', ''];
          }
      }
    });
  }
});
```

`reportedDays`は日報が登録された日付の配列となる。`beforeShowDay`メソッドで、ひとつひとつのdateがそのなかに入っているかどうか確認する。入っていたら`reported-days` classを付与させる。

`dayjs(date).format`は dayjsを利用している。導入方法は以下が詳しい。

https://day.js.org/docs/en/installation/browser


# 4. CSSを変更
　今のままだとデフォルトのdatepickerのデザインになってしまう。デザインを確かめながらCSSを変更していく。

```css:app/assets/stylesheets/application.scss
/* 略..*/

.ui-datepicker-next:hover,.ui-datepicker-prev:hover {
  background: none;
}

.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default,
.ui-button,
html .ui-button.ui-state-disabled:hover,
html .ui-button.ui-state-disabled:active {
  border: 0px;
  background-image: none;
  background:inherit;
	font-weight: normal;
	color: #182736;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
	opacity: 1;
	filter:none; /* support: IE8 */
}
```

これで背景のグラデーションが消えて、すこし今風のデザインになる。こだわると時間がかかりそうなので、一旦はここまで。


# 4. 完成画面
![スクリーンショット 2023-02-13 5.16.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104211/1b3b26a0-fb13-bf3e-ee49-c7f1922f52d1.png)

　
# 5.　おわりに

　少しずつ自分の思うサービスになってきた気がする。これからも開発を続けるぞ。

　今回の機能はまだ本番環境には反映させてません。もう少しいろいろなおしてからあげます。