Vue/Vuetify ダイアログの設置と親子コンポーネント間のデータ送受信

Vue.jsにおける子コンポーネントの設置方法とpropsと使った親コンポーネントから子コンポーネントへのデータ送受信、および$emitを使った子コンポーネントから親コンポーネントへのデータ送受信をVuetifyのダイアログを例に説明します。
ダイアログは登録/編集の確認やサーバエラー、不適切な操作等で頻繁に使用すると思います。応用して汎用性の高いダイアログをつくりましょう。

開発環境

Laradock v10
Laravel 7
Vue 2.6

子コンポーネントの配置について

コンポーネント(親)に子コンポーネントを配置するには以下の手順となります。

①子コンポーネントをインポートします。

例)
import ChildComponent from './ChildComponent.vue'

②子コンポーネントをタグに紐づけます。

例)
components: {
  'child-component': ChildComponent,
},

③紐づけたタグを記述します。

例)
<child-component></child-component>

ParentComponent.vueにChildComponent.vueを配置する場合は以下のようになります。

<template>
  <div>
    <h1>Parent</h1>
    <child-component></child-component>
  </div>
</template>

<script>
  import ChildComponent from './ChildComponent.vue'
  export default {
    components: {
      'child-component': ChildComponent,
    },
  }
</script>
<template>
  <div>
    <h2>Child</h2>
  </div>
</template>
laravel-vue-vuetify-dialog-1

親コンポーネントから子コンポーネントへのデータ受け渡しについて

親コンポーネントからバインド(:)を設定することで、子コンポーネントはprops(プロパティ)として受けとることができます。

<template>
  <div>
    <h1>Parent</h1>
    <child-component
      :text="test"
    ></child-component>
  </div>
</template>

<script>
  import ChildComponent from './ChildComponent.vue'
  export default {
    components: {
      'child-component': ChildComponent,
    },
    data () {
      return {
        test: 'てすと1',
      }
    },
  }
</script>
<template>
  <div>
    <h2>Child</h2>
    {{ text }}
  </div>
</template>

<script>
  export default {
    props: {
      text: '',
    },
  }
</script>
laravel-vue-vuetify-dialog-2

ダイアログを表示させる

トリガーとなるボタンを親コンポーネントに配置して子コンポーネントのダイアログを表示させましょう。

ダイアログを表示する子コンポーネント作成

子コンポーネントとなるダイアログをAgreementDialogComponent.vueとして作成します。

Vuetify公式のダイアログ(https://vuetifyjs.com/ja/components/dialogs/)のサンプルからactivatorを使用しない場合をまるっとコピーして編集します。

<template>
  <v-row justify="center">

<!-- ①ボタンは親コンポーネントに移植します
    <v-btn
      color="primary"
      dark
      @click.stop="dialog = true"
    >
      Open Dialog
    </v-btn>
-->
    <!-- ②ダイアログのモデル名はagreementDialogとします -->
    <v-dialog
      v-model="agreementDialog"
      max-width="290"
      persistent
    >
      <v-card>
        <v-card-title class="headline">Use Google's location service?</v-card-title>

        <v-card-text>
          Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.
        </v-card-text>

        <v-card-actions>
          <v-spacer></v-spacer>

          <v-btn
            color="green darken-1"
            text
            @click="dialog = false"
          >
            Disagree
          </v-btn>

          <v-btn
            color="green darken-1"
            text
            @click="dialog = false"
          >
            Agree
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>


<script>
  export default {
    // ③親コンポーネントからのデータはpropsで受け取ることができます。
    props: {
      agreementDialog: false,
    },
  }
</script>

①ボタンは親コンポーネントに実装しますのでコメントアウトするなり、削除するなりしてください。
②ダイアログのモデル名はagreementDialogとします。
persistentを追加してダイアログの外側をクリックしてもアクティブのままとします。
③親コンポーネントからのデータpropsで受け取ることができます。モデル名も変更しておきましょう。

    data () {
      return {
        dialog: false,
      }
    },
↓
    props: {
      agreementDialog: false,
    },

※agreementDialogがtrueであればダイアログを表示、falseであれば非表示です。

親コンポーネント作成

<template>
  <div>
    <h1>Dialog Test</h1>

    <agreement-dialog-component
      :agreementDialog="agreementDialog"
    ></agreement-dialog-component>

    <!-- ダイアログから移植したボタン -->
    <v-btn
      color="primary"
      dark
      @click.stop="agreementDialog = true"
    >
      Open Dialog
    </v-btn>

  </div>
</template>

<script>
  import AgreementDialogComponent from './AgreementDialogComponent.vue'
  export default {
    components: {
      'agreement-dialog-component': AgreementDialogComponent,
    },
    data () {
      return {
        agreementDialog: false,
      }
    },
  }
</script>

ボタンについて

ダイアログから移植したボタンをクリックしたとき、agreementDialogをtrueにすることで子コンポーネントのダイアログが表示されるようになります。

@click.stop="dialog = true"
↓
@click.stop="agreementDialog = true"

※@click.stopは親のイベント等を実行させないときに使用しますが今回は単一のイベントなので@clickでも良いと思います。

子コンポーネントの配置

    <agreement-dialog-component
      :agreementDialog="agreementDialog"
    ></agreement-dialog-component>

子コンポーネントにデータを送るデータのプロパティ名をagreementDialogとして、値は以下のように設定します。

    data () {
      return {
        agreementDialog: false,
      }
    },

ここまででダイアログを表示することができました。
DISAGREEとAGREEのボタン操作はまだできませんが・・・

laravel-vue-vuetify-dialog-3

ダイアログのタイトルとテキストを親コンポーネントから設定

タイトルとテキストを親コンポーネントから設定できるようにしましょう。
親コンポーネントからバインド(:)を設定することで、子コンポーネントはprops(プロパティ)で受けとることができるので親コンポーネントにdialogItemsを設定してみます。

<template>
  <div>
    <h1>Dialog Test</h1>
    <agreement-dialog-component
      :agreementDialog="agreementDialog"
      :dialogItems="dialogItems"
    ></agreement-dialog-component>

    <!-- ダイアログから移植したボタン -->
    <v-btn
      color="primary"
      dark
      @click.stop="agreementDialog = true"
    >
      Open Dialog
    </v-btn>

  </div>
</template>

<script>
  import AgreementDialogComponent from './AgreementDialogComponent.vue'
  export default {
    components: {
      'agreement-dialog-component': AgreementDialogComponent,
    },
    data () {
      return {
        agreementDialog: false,
        dialogItems: {
          title: 'ダイアログタイトル',
          text: 'てきすとてきすとてきすとてきすとてきすと',
        },
      }
    },
  }
</script>

子コンポーネントはpropsに受け取るデータdialogItemsを設定してダイアログのタイトルとテキストに読み込みます。

<template>
  <v-row justify="center">
<!-- ボタンは親コンポーネントに移植します
    <v-btn
      color="primary"
      dark
      @click.stop="dialog = true"
    >
      Open Dialog
    </v-btn>
-->

    <v-dialog
      v-model="agreementDialog"
      max-width="290"
      persistent
    >
      <v-card>
        <v-card-title class="headline">{{ dialogItems.title }}</v-card-title>

        <v-card-text>
          {{ dialogItems.text }}
        </v-card-text>

        <v-card-actions>
          <v-spacer></v-spacer>

          <v-btn
            color="green darken-1"
            text
            @click="dialog = false"
          >
            Disagree
          </v-btn>

          <v-btn
            color="green darken-1"
            text
            @click="dialog = false"
          >
            Agree
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>


<script>
  export default {
    // 親コンポーネントからのデータ受信はpropsで行います
    props: {
      agreementDialog: false,
      dialogItems: {},
    },
  }
</script>
laravel-vue-vuetify-dialog-4

子コンポーネントから親コンポーネントへのデータ通信

子コンポーネントから親コンポーネントへのデータ送信

子コンポーネントから親コンポーネントへのデータ通信には$emitという機能があります。
これにより親コンポーネントのメソッドを子コンポーネントが引数を与えて実行することが可能となります。実装してみましょう。

<template>
  <v-row justify="center">
    <v-dialog
      v-model="agreementDialog"
      max-width="290"
      persistent
    >
      <v-card>
        <v-card-title class="headline">{{ dialogItems.title }}</v-card-title>

        <v-card-text>
          {{ dialogItems.text }}
        </v-card-text>

        <v-card-actions>
          <v-spacer></v-spacer>

          <v-btn
            color="green darken-1"
            text
            @click="disagree"
          >
            Disagree
          </v-btn>

          <v-btn
            color="green darken-1"
            text
            @click="agree"
          >
            Agree
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>

<script>
  export default {
    props: {
      agreementDialog: false,
      dialogItems: {},
    },
    methods: {
      disagree() {
        this.$emit('result', {'res': false, 'message': '同意されませんでした。'})
      },
      agree() {
        this.$emit('result', {'res': true, 'message': '同意されました。'})
      },
    },
  }
</script>

DISAGREEのボタンが押されたときメソッドdisagree()を実行、AGREEのボタンが押されたときメソッドagree()を実行。
それぞれのメソッドで、
this.$emit(親コンポーネントのイベント名, その引数)
とすることで親コンポーネントのイベントトリガーとして実現することができます。

親コンポーネントの設定

①子コンポーネントの$emitの第1引数が指定するイベント名とメソッドを

@result="response"
または
v-on:result="response"

と紐づけてresponseメソッドを実装します。

②responseメソッドでは子コンポーネントの$emitの第2引数で指定されたデータを受け取り
messageとしてマスタッシュ構文で表示させて、this.agreementDialog = falseでダイアログを非表示にします。

<template>
  <div>
    <h1>Dialog Test</h1>
    <agreement-dialog-component
      :agreementDialog="agreementDialog"
      :dialogItems=dialogItems
      @result="response"
    ></agreement-dialog-component>

    <!-- ダイアログから移植したボタン -->
    <v-btn
      color="primary"
      dark
      @click.stop="agreementDialog = true"
    >
      Open Dialog
    </v-btn>

    <p class="mt-1 text-danger">{{ message }}</p>
  </div>
</template>

<script>
  import AgreementDialogComponent from './AgreementDialogComponent.vue'
  export default {
    components: {
      'agreement-dialog-component': AgreementDialogComponent,
    },
    data () {
      return {
        agreementDialog: false,
        dialogItems: {
          title: 'ダイアログタイトル',
          text: 'てきすとてきすとてきすとてきすとてきすと',
        },
        message: '',
      }
    },
    methods: {
      response(obj) {
        this.message = obj.message
        this.agreementDialog = false
      }
    },
  }
</script>
laravel-vue-vuetify-dialog-5

後記

今回はVuetifyのダイアログのサンプルがたまたまDISAGREE/AGREEだったので、AgreementDialogとなりましたがエラーや確認など使い方はさまざまですが応用できますね!