👣

Vuetify の v-steppers コンポーネントで少し複雑なフォームを作ってみる

Leaner Technologies で業務委託している西辻です。
Vuetify の v-steppers コンポーネントで少し複雑なフォームを作ったので、その際の設定などをまとめていきます。
https://vuetifyjs.com/en/components/steppers/

v-steppers はステップ入力させるための親コンポーネントになっており、実際利用する際は、ステップを表現するためにいくつかの子コンポーネントを利用します。
構造的には以下のようになります。

  • v-stepper <-- 親コンポーネント
    • v-stepper-header <-- v-stepper-step を束ねるためのコンポーネント
      • v-stepper-step <-- ヘッダー内にある各ステップを表現
    • v-stepper-items <-- v-stepper-content を束ねるためのコンポーネント
      • v-stepper-content <-- 選択されたステップで表示したいコンテンツを表現するためのコンポーネント

各コンポーネントについて

v-stepper

https://vuetifyjs.com/en/api/v-stepper/
Steppers を表現するための親コンポーネントになります。
alt-labels を props で設定すると v-stepper-step で設定したラベルが表示できるようになります。
また、 v-model を設定することで、選択されているステップを取得できます。

v-stepper-header

https://vuetifyjs.com/en/api/v-stepper-header/
どのステップにいるかを表現するためのヘッダーコンポーネントになります。
API ドキュメントを見ると、設定できる項目はほとんどないですね、後述の v-stepper-step を束ねる役割がメインになります。

v-stepper-step

https://vuetifyjs.com/en/api/v-stepper-step/
v-stepper-step を束ねるためのコンポーネントになります。
complete props でそのステップが完了しているかどうかを判定できます。
例えば、今いるステップより前のものを完了としたい場合は、以下のようにします。

:complete='currentStep > s.step'

rules props を設定すると、バリデーションエラーをステップ上で表現できるようになります。
steps 自体は data として定義しておくとループ処理が書きやすくなるのでおすすめです。

interface Step {
  label: string
  rules: Function[]
  step: number
}

以下は Step2 の rules に関する設定例です。
this.currentStep <= 2 としているのは、新規入力時 Step1 から入力を始めた際に、未入力が前提となってしまう先のステップでエラーを表示させないためです。

steps: [
  {
    label: 'Step2の入力',
    rules: [
      () => this.form.isValidStep2 || this.currentStep <= 2,
    ],
    step: 2,
  },
]

click イベントを拾えるので、ステップアイコンがクリックされた時の動作も定義が可能です。

v-stepper-items

https://vuetifyjs.com/en/api/v-stepper-items/
v-stepper-content を束ねるためのコンポーネントになります。
役割的には前述の v-stepper-header と同じになります。

v-stepper-content

https://vuetifyjs.com/en/api/v-stepper-content/
選択されたステップで表示したいコンテンツを表現するためのコンポーネントになります。
ビジネスロジックに沿った入力フォームなど独自のコンテンツを配置します。

v-stepper-content で各ステップに入力フォームを用意する

今回、私が実装した時は、 Step が 5 つで各ステップごとに入力値のバリデーションが必要となりました。
v-stepper でステップごとのエラー表示は用意してくれてますが、各ステップでどのようにバリデーションを行い、 v-stepper と連携するかは利用者に委ねられています。
以下でその辺りをどう解消したか一例として紹介していきます。

まずは、利用するデータについて定義します。
各ステップごとに有効かどうかを持たせるため、以下のような構造とします。

  data(): Data {
    return {
      currentStep: 1,
      form: {
        isValidStep1: true,
        isValidStep2: true,
        isValidStep3: true,
        isValidStep4: true,
        ...
      }
    }
  }

各ステップのコンポーネントに isValidStep を同期させる形で props として渡します。
この時、 currentStep も渡しておきます。
ちなみにテンプレートは Pug になります。

v-stepper-content
  Step2Form(
    :currentStep='currentStep',
    :isValidStep.sync='form.isValidStep2'
  )

Step2Form は以下のようにします。

Step2Form
<template lang="pug">
v-form(
  @input='$emit("update:isValidStep", $event)',
  :value='isValidStep',
  ref='form'
)
</template>

<script lang="ts">
...
  watch: {
    currentStep: {
      handler() {
        if (this.currentStep > 2) {
          // @ts-ignore
          this.$refs.form.validate()
        }
      },
    },
  },
</script>

Vuetify のバリデーションサポートをフルに生かすため、v-form で包み、配下にある input 系のバリデーションエラーを検知できる形にします。
watch にて currentStep を監視、 Step2 より大きいステップへ移動した時 validate() を発火させます。
こうすることで、以下のように rules にて this.form.isValidStep2 を参照し、バリデーションエラーがある状態で先のステップへ進もうとすると v-stepper 上でエラーがあることを表現できるようになります。

steps: [
  {
    label: 'Step2の入力',
    rules: [
      () => this.form.isValidStep2 || this.currentStep <= 2,
    ],
    step: 2,
  },
]

まとめ

Vuetify の v-steppers の使い方について紹介でした。
Vuetify は設定できるオプションがたくさんあるのでコードを書くよりもドキュメントを読んでどんなことができるかの理解があると実装スピード出るイメージがありますね。
ただ、全てが用意されているコンポーネントで解決できることも多くはないので、少なからず独自の工夫を取り入れる箇所が出てきます。
そういった部分は、今回のように汎用的な部分についてはブログ化し、チームメンバーにある程度実装方針を共有していければ良さそうですね。

宣伝

Leaner Technologies では Vuetify を使いこなして、スピード感ある開発を一緒にやっていけるメンバーを探しています!
https://careers.leaner.co.jp/

リーナーテックブログ

Discussion