【Vue】コンポーネントのボタンを親から指定する

ダイアログ等を含むコンポーネントを作成した際に起動ボタンは呼び出しページ毎に異なるものを指定したい場面があると思います。そんな時にVuetifyのv-slot:activatorの様に呼び出し元でボタンを指定可能なコンポーネントを作成します。

## コンポーネントのトリガを外出しする
Vuetifyのv-slot:activatorの様にコンポーネントを起動するボタンを外部から指定可能なコンポーネントを作成します。

```vuejs:FormDialog.vue
<template>
  <v-dialog v-model="dialog" persistent max-width="600px">
    <template v-slot:activator="{ on }">
      <!-- slotで呼び出し元から起動ボタンを指定可能にする -->
      <slot
        name="activator"
        :on="{ click: open }"
      ></slot>
    </template>
    <v-card>
      <v-card-title>
        <span class="headline">入力フォーム</span>
      </v-card-title>
      <v-card-text>
        <form>
          <v-textarea
            v-model="text"
            label="フォーム"
            :counter="1000"
          ></v-textarea>
        </form>
      </v-card-text>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn color="blue darken-1" text @click.native="dialog = false">
          キャンセル
        </v-btn>
        <v-btn color="blue darken-1" text @click.native="save()">
          更新
        </v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<script>
export default {
  data() {
    return {
      // ダイアログ制御用変数
      dialog: false,

      // 入力用変数
      text: ''
    }
  },
  methods: {
    // 何もせずにただダイアログを開く
    open() {
      // ダイアログをオープン
      this.dialog = true
    }
  }
}
</script>

```

slotでイベントを親コンポーネントに渡します。ここでは以下の様にonClickでコンポーネント内のopen()メソッドを呼び出す様設定しています。

```vuejs
<slot
  name="activator"
  :on="{ click: open }"
></slot>
```

呼び出し元のコンポーネントは以下の通り

```vuejs:index.vue（呼び出し元）
<template>
  <v-row align="center" justify="center">
    <v-col cols="12">
      <h1>ボタン配置ページ</h1>
    </v-col>
    <v-col cols="12">
      <!-- コンポーネントの呼び出し -->
      <FormDialog>
        <!-- v-slotでボタンを親から指定する -->
        <template v-slot:activator="{ on }">
          <v-btn v-on="on">
            Dialog
          </v-btn>
        </template>
      </FormDialog>
    </v-col>
  </v-row>
</template>

<script>
export default {
  components: {
    FormDialog: () => import('@/components/FormDialog')
  }
}
</script>
```
以下で呼び出し側のボタンにFormDialogで指定したイベントが登録されます。

```vuejs
<template v-slot:activator="{ on }">
  <v-btn v-on="on">
    Dialog
  </v-btn>
</template>
```

## 別のハンドラを追加する
呼び出し元毎にボタンを押下した際の動作が異なる場合はイベントハンドラーを別名で渡すこともできます。

```vuejs:FormDialog.vue一部抜粋
<slot
 name="activator"
 :on="{ click: open }"
 :onFillin="{ click: openWithFillin }" <!-- 別のメソッドを登録 -->
></slot>

<中略>

  methods: {
    // 何もせずにただダイアログを開く
    open() {
      // ダイアログをオープン
      this.dialog = true
    },

    // デフォルト値をフィルインしてダイアログを開く
    openWithFillin() {
      // 値をフィルイン
      this.text = 'デフォルト値'
      // ダイアログをオープン
      this.dialog = true
    }
  }
}
</script>
```
呼び出し元で以下の通りonFillin を利用する。

```vuejs:index.vue（呼び出し元一部抜粋）
<FormDialog>
  <template v-slot:activator="{ onFillin }">
    <v-btn v-on="onFillin">
      Dialog
    </v-btn>
  </template>
</FormDialog>
```


## Vuetifyのv-slot:activatorと併用する
例えばダイアログのボタンにホバーした際はボタンの説明を出し、クリックした際はダイアログを開く様にしたい場合は、v-tooltip等と併用します。

こんな場合
![activator-sample - activator-sample (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420680/79b18ca9-5b71-ff46-68bc-dde09997545f.gif)



この場合、コンポーネントのハンドラのマージを行います。


```vuejs:index.vue（呼び出し元一部抜粋）
<FormDialog>
  <template v-slot:activator="{ onFillin }">
    <v-tooltip top>
      <template v-slot:activator="{ on }">
        <!-- v-tooltipのonとFormDialogのonFillinをマージ -->
        <v-btn icon v-on="Object.assign(on, onFillin)">
          <v-icon>mdi-information</v-icon>
        </v-btn>
      </template>
      <span>
        ボタンの説明
      </span>
    </v-tooltip>
  </template>
</FormDialog>
```
## 参考
[Vue.js スコープ付きスロット](https://jp.vuejs.org/v2/guide/components-slots.html#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E4%BB%98%E3%81%8D%E3%82%B9%E3%83%AD%E3%83%83%E3%83%88)
[Vuetifyのv-slot:activatorの使い方](https://qiita.com/koushisa/items/3f74e1d103be1b969c47)