FlatpickrにInput Requiredをつける

仕事で「Calendarライブラリにrequiredが効かないんで困っていますー」って相談があったので調べた結果をまとめておく。


今使っているライブラリは vue-flatpickr-component という、 flatpickr をvueでwrapしたもの。

flatpickr はstar数も多く有名なライブラリみたいだ。


動かない原因の前に、htmlの挙動について確認しておく。

inputにrequiredとreadonlyが同時についた場合には以下のような挙動になる。 このstackoverflowが参考になる。

Constraint validation: If the readonly attribute is specified on an input element, the element is barred from constraint validation.

https://html.spec.whatwg.org/multipage/input.html#the-readonly-attribute

<form>
    <input required> <!-- 動く -->
    <input readonly> <!-- 動く -->
    <input required readonly> <!-- Chrome Version 80.0.3987.149 (Official Build) (64-bit)ではreadonlyのみが適用される -->
    <button type="submit">submit</button>
</form>

今回の要件は、「日時入力を必須にしたい」だった。

調べたところ、flatpickrはdefaultでreadonlyが付いてるからrequiredが効いてくれないというのが動かない原因だった。

issueを眺めていたところ、 configに allowInput: true をつけてあげれば readonly が外れて良い感じみたいだ。

HTML5 form validation disabled on Flatpickr inputs #892

vue-flatpickr-component はpropsでconfigを渡せるので入れてみたら正常に挙動した。 https://github.com/ankurk91/vue-flatpickr-component/blob/master/src/component.js#L37-L43


最終的には以下のようなvue componentに落ち着いたみたいだ。

<template>
  <div class="form-group">
    <div class="input-group">
      <div class="input-group-prepend">
        <span class="input-group-text"><i class="ni ni-calendar-grid-58" /></span>
      </div>
      <flat-pickr
        v-model="inputDate"
        :config="config"
        class="form-control"
        :required="isRequired"
        :placeholder="placeholder"
        :name="name"
      />
    </div>
  </div>
</template>

<script>
import flatPickr from "vue-flatpickr-component"
import { Japanese } from "flatpickr/dist/l10n/ja.js"
import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect"
import "flatpickr/dist/plugins/monthSelect/style.css"
import "flatpickr/dist/flatpickr.css"
export default {
  name: "Datepicker",
  components: {
    flatPickr,
  },
  props: {
    value: {
      type: String,
      default: "",
    },
    name: {
      type: String,
      default: "",
    },
    isRequired: {
      type: Boolean,
      default: false,
    },
    isMonthpicker: {
      type: String,
      default: "false",
    },
    placeholder: {
      type: String,
      default: "日付を選択",
    },
  },
  data() {
    return {
      inputDate: "",
      config: {
        wrap: true,
        altInput: true,
        altFormat: "Y年 m月 d日",
        dateFormat: "Y-m-d",
        locale: Japanese,
        plugins: [],
        allowInput: true,
      },
      monthSelect: new monthSelectPlugin({
        shorthand: true,
        dateFormat: "Y-m-d",
        altFormat: "Y年 M",
      }),
    }
  },
  watch: {
    inputDate() {
      this.$emit("input", this.inputDate)
    },
  },
  created() {
    this.inputDate = this.value !== "" ? this.value : ""
    if (this.isMonthpicker === "true") this.config.plugins.push(this.monthSelect)
  },
}
</script>