[nuxt.js]カレンダーライブラリflatpickrを使って楽ちんカレンダー表示

![スクリーンショット 2021-09-14 18.20.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/739b2b4a-4066-f257-8e2d-3307ff44b7ab.png)
初めてカレンダーライブラリのflatpickrを使ってみたので、その備忘録。
ちょっとした設定だけでカレンダーが実装できて大変嬉しい。

ざっくりかき4つのステップでいけます。
①flatpickrのインストール
②flatpickerのインポート
③テキストフィールドにidを指定
④scriptでオプションを指定



▼環境
- macOS Big Sur 11.2.3
- node v16.2.0
- npm 7.20.0
- nuxt.js @nuxt/cli v2.15.6
また当方、フレームワークはvuetifyを使用しています！

## ①flatpickrのインストール
getting Started. 
まずは`npm i -D flatpickr`または`yarn add flatpickr`にてflatpickrをインストール。

CDNでの読み込みも可能です。その他方法のインストールは公式をご参照ください。
[flatpickr公式](https://flatpickr.js.org/getting-started/)

追記：
nuxt向けにはこんなのもあるみたいです。これを入れると後の実装が変わります。module追記する必要があると思います。
[npm | nuxt-flatpickr](https://www.npmjs.com/package/nuxt-flatpickr)
```
npm i nuxt-flatpickr
```



## ②flatpickerのインポート
`<script>`内にflatpickrを宣言します。default必要。

```vuejs:pages/Example.vue
<script>
const flatpickr = require('flatpickr').default
</script>
```

参考： [stackflow | Why need default after require() method in Vue?](https://stackoverflow.com/questions/46215705/why-need-default-after-require-method-in-vue)


## ③テキストフィールドにidを指定

入力フォームを設置してidを指定します。

inputタグ(今回はvuetifyを使っているので`v-text-field`を使用)など、入力フォームにidを設定し、data内でidを定義します（id: 'datePick'）。

```vuejs:pages/Example.vue
<template>
  <div>
    <Header />
    <v-container>
      <v-text-field :id="id"></v-text-field>
    </v-container>
  </div>
</template>

<script>
const flatpickr = require('flatpickr').default
export default {
  data() {
    return {
      id: 'datePick',
    }
  },
}
</script>

```

もちろん、textfieldタグのidに直接定義しても大丈夫です。
```vuejs:pages/Example.vue
   <v-text-field id="datePick"></v-text-field>
```


④scriptでオプションを指定

```vuejs:pages/Example.vue
<script>
const flatpickr = require('flatpickr').default
export default {
  data() {
    return {
      id: 'datePick',
    }
  },
  mounted() {
    // DOMが描画されてからflatpickr呼び出し
    this.$nextTick(() => {
      this.flatpickr = flatpickr('#' + this.id, this.flatPickrConfig)
    })
  },
}
</script>
```

やったね！
![スクリーンショット 2021-09-14 17.41.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/4fb23a92-d7dd-bdf1-d8d6-5505a5438fcb.png)


## せっかくなので拡張してみる。

せっかくなので、親、子、孫、みたいな感じでデータを渡してみます。
getting Startedまでは同じです。

ファイル構成は、下記の通り。
親: pages/date/index.vue
子: components/molecules/ContentsCard.vue
孫: components/Atoms/DatePicker.vue


### 親で今日の日付を取得
親で今日の日付を取ってきて、初期値として表示。

```vuejs:index.vue
<template>
  <div>
    <Header />
    <v-container class="contents">
      <ContentsCard :today="today" />
    </v-container>
  </div>
</template>

<script>
export default {
  asyncData({ $dayjs }) {
    const dayjs = $dayjs()
    const today = dayjs.format('YYYY年MM月DD日')
    // console.log(today)
    return { today }
  },
}
</script>

```
![スクリーンショット 2021-09-14 17.56.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/31a823f5-18c0-acc4-9e8b-1ab5e875c9bb.png)
まっさら！

### 子ども（components/molecules/ContentsCard.vue）を作成
vuetifyのポップアップウィンドウは`v-dialog`でできます。
https://vuetifyjs.com/ja/components/dialogs/

```vuejs:ContentsCard.vue
<template>
  <div>
    <v-card class="mx-auto" min-width="360" outlined flat>
      <v-card-title>This it the flatpickr</v-card-title>
      <v-card-subtitle>Click the button then you'll see</v-card-subtitle>
      <v-card-text>
        <v-dialog v-model="dialog" width="500">
          <template #activator="{ on, attrs }">
            <v-btn
              color="primary"
              rounded
              elevation="false"
              v-bind="attrs"
              v-on="on"
            >
              open dialog
            </v-btn>
          </template>
          <v-card>
            <v-card-title class="text-h5"> flatpickr </v-card-title>

            <v-card-text> ここに孫のDatePicker.vueを入れるよ！ 

              <v-divider></v-divider>
            </v-card-text>

            <v-card-actions>
              <v-spacer></v-spacer>
              <v-btn color="primary" text @click="dialog = false">
                close
              </v-btn>

            </v-card-actions>
          </v-card>
        </v-dialog>
      </v-card-text>
    </v-card>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dialog: false,
    }
  },
}
</script>


```

![スクリーンショット 2021-09-14 18.15.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/21104eac-8b7f-d08b-29f6-de7e9a7613d1.png)

まだ孫を作成していないため、open dialogを押下してもテキストしかありません。
![スクリーンショット 2021-09-14 18.20.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/1b3c1f75-8f43-ea5d-9cca-9421a08c5153.png)


### 孫（components/atoms/DatePicker.vue）を作成

先ほどの`ContentsCard.vue`のテキストエリア
`<v-card-text> ここに孫のDatePicker.vueを入れるよ！ </v-card-text>`の中身を、
孫のコンポーネント名に書き換えます。


```vuejs:ContentsCard.vue
            <v-card-text>
              <DatePicker3></DatePicker3>

              <v-divider></v-divider>
            </v-card-text>
```


今日の日付をデフォルトの表示として孫のテキストフィールドに表示させたいため、propsとして親から受け取った値を孫に渡しておきます。はい、これお小遣いね。

```vuejs:ContentsCard.vue

<DatePicker v-model="itemToday" label="日付を選択"></DatePicker>

// 適当なところで日付が取れているか確認
{{ itemToday }}

<script>
export default {
  props: {
    today: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      dialog: false,
      itemToday: '',
    }
  },
  mounted() {
    this.itemToday = this.today
  },
}
</script>
```

それではやっと孫の中身を作成し始めます。
何はともあれ土台と、先ほど子どもで受け取った今日の日付を受け取っておきます。
選択した日付はそのまま親に渡すデータになるので、v-modelはvalueとして値を取得後、computedで値をセットして$emitしておきます。（親は@inputで受け取れます。）

※v-modelによるプロパティのバインディングは、:value(部品の初期値を指定)と@input(データとしてサーバに送信)をまとめた等位構文なので。バインドしたプロパティに対して参照と更新の両方が発生。

参考：　[v-modelにオブジェクトをバインディングする場合のコンポーネント実装を考える](https://qiita.com/clomie/items/7a69ee850a304595142e)

valueで複数型を指定しているのは、$dayjs()で渡される値がStringとNumberの可能性があるからです。
参考：　[Vue.js の props の type 指定時に String と Number の両方を許容させる](https://qiita.com/ysKuga/items/89b80458c98a34a9c5de)


```vuejs:DatePicker.vue
<template>
  <div>
    <v-text-field v-model="innerValue" :label="label"></v-text-field>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: [String, Number],
      default: '',
    },
    label: {
      type: String,
      default: '',
    },
  },
  computed: {
    innerValue: {
      get() {
        return this.value
      },
      set(val) {
        this.$emit('input', val)
      },
    },
  },
}
</script>


```

今日の日付を表示させられました！
![スクリーンショット 2021-09-14 19.15.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/291ba1f4-deea-26a4-e299-fd9d26d41ee1.png)


### 孫（components/atoms/DatePicker.vue）にflatpickrを実装！
前置きが長くなってしまったのでお忘れかもしれませんが…flatpickrを実装する上で必要なのは、
①flatpickrのインストール
②flatpickerのインポート
③テキストフィールドにidを指定
④scriptでオプションを指定

でした！

①はもうできているので、

#### ②flatpickerのインポート

```vuejs:DatePicker.vue

const flatpickr = require('flatpickr').default

// export defaultの上

```

####　③テキストフィールドにidを指定

そのままidの値を入れてもいいのですが、拡張性を持たせるためにプロパティで用意しておきます。

```vuejs:DatePicker.vue

<v-text-field :id="id" v-model="innerValue" :label="label"></v-text-field>

```

初期値を設定しておきます。

```vuejs:DatePicker.vue
<script>
export default {
  props: {
    // propsに追加
    id: {
      type: String,
      default: 'datePick',
    },
  },
</script>
```


####　④scriptでオプションを指定

flatpickrの初期値と、オプションをオブジェクトで用意しておきます。日本語対応させたいので、flatpickrが用意している日本語もimportしておきます。
※オプションは公式をご参照ください。
https://flatpickr.js.org/options/

```vuejs:DatePicker.vue
<script>
import { Japanese } from 'flatpickr/dist/l10n/ja'
export default {
  data() {
    return {
      flatpickr: null,
      flatpickrConfig: {
        altInput: true,
        locale: Japanese,
        altFormat: 'Y年m月d日',
        dateFormat: 'Ymd',
      },
    }
  },
}
</script>
```
mountedのタイミングで、flatpickrを描画させます。
`$nextTick`はVueが用意している機能です。
> Callbackを延期し、DOMの更新サイクル後に実行します。DOM更新を待ち受けるために、いくつかのデータを変更した直後に使用してください。
こちらを参考にしました。
[Vue.nextTickのコードリーディング](https://aloerina01.github.io/blog/2018-09-27-1)

```vuejs:DatePicker.vue
<script>

  mounted() {
    this.$nextTick(() => {
      this.flatpickr = flatpickr('#' + this.id, this.flatPickrConfig)
    })
  },
  methods: {},
}
</script>

```

表示できました！
![スクリーンショット 2021-09-14 19.39.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97385/7c44be20-8f8f-7b6c-f02c-5a1379209517.png)


## おまけ：オシャレにします。
お好みのスタイルを選ぶんだ！　=>  [公式](https://flatpickr.js.org/themes/)

scriptにテーマを追加するだけ！

```vuejs
<script>
import 'flatpickr/dist/themes/confetti.css'
</script>
```

## 初期値、オプション設定

flatpickrの基本は、データの初期値を用意して、オプションを設定するだけ。
私はvuetifyを使用しているため少しわかりにくいかもしれませんが、基本は下記の形です！

```javascript
<script>
<input class="input" id="unchi" type="text" />

<script>
flatpickr("#unchi", { オプション });
</script>
```

オプションは公式から確認できます。 => [オプション](https://flatpickr.js.org/options/)