3
votes

I'm trying to create custom component based on vuetify's v-menu component.

This menu should be called from vuetify's dialog twice:

  1. By pressing "Add row" button at the top of the dialog content
  2. By pressing on "Edit" icon button on each row of v-data-table component

In both cases a same menu should appear. The main difference is in the component that used to call the menu - in first case it should be v-btn with some text, in second case - v-icon with some icon.

According to vue and vuetify docs I think I should redefine v-slot:activator, but I'm experiencing some problems with it: the component always has a default value for v-slot:activator.

Vue 2.6.11, Vuetify 2.2.3.

My code is shown below:

Dialog.vue:

<template>
  <v-dialog v-model="dialog" max-width="500px" persistent>
    <v-card>
      <v-card-title>
        <v-spacer></v-spacer>
        <menu-component/>
      </v-card-title>
      <v-card-text>
        <v-data-table
          :headers="tableHeaders"
          :items="tableContent"
        >
          <template v-slot:item="props">
            <tr>
              <td>{{ props.item.id }}</td>
              <td>{{ props.item.text }}</td>
              <td class="text-center">
                <menu-component>
                  <template v-slot:activator="{ on }">
                    <v-icon v-on="on">mdi-pencil</v-icon>
                  </template>
                </menu-component>
              </td>
            </tr>
          </template>
        </v-data-table>
      </v-card-text>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn @click="close">{{ "Close dialog" }}</v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<script>
  import MenuComponent from "./MenuComponent";

  export default {
    components: {
      'menu-component': MenuComponent
    },
    data() {
      return {
        tableContent: [
          { id: 1, text: 'some text'}
        ],
        tableHeaders: [
          {text: 'ID'},
          {text: 'Text'},
          {text: 'Actions', align: 'center'}
        ]
      }
    },
    props: {
      dialog: Boolean
    },
    methods: {
      close() {
        this.$emit('close-dialog');
      }
    }
  }
</script>

MenuComponent.vue:

<template>
  <v-menu bottom left
          v-model="menu"
          :close-on-content-click="false">
    <template v-slot:activator="{ on }">
      <v-btn v-on="on">Add row</v-btn>
    </template>
    <v-card width="300">
      <v-container>
        <v-layout wrap>
          <v-text-field label="Text"/>
        </v-layout>
      </v-container>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn @click="menu=false">{{ "Close" }}</v-btn>
      </v-card-actions>
    </v-card>
  </v-menu>
</template>

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

What I'm trying to make:

expected

What happened now:

current

1

1 Answers

3
votes

Yes! What your are looking to do is to pass your slots to a child wrapped component. One guy made a gist over here to explain how to do it: https://gist.github.com/loilo/73c55ed04917ecf5d682ec70a2a1b8e2#gistcomment-3121626

Here is what you should write into your MenuComponent.vue:

<template>
  <v-menu bottom left
    v-model="menu"
    :close-on-content-click="false"
  >

    <!-- pass through scoped slots -->
    <template v-for="(_, scopedSlotName) in $scopedSlots" v-slot:[scopedSlotName]="slotData">
      <slot :name="scopedSlotName" v-bind="slotData" />
    </template>

    <!-- pass through normal slots -->
    <template v-for="(_, slotName) in $slots" v-slot:[slotName]>
      <slot :name="slotName" />
    </template>


    <v-card width="300">
      <v-container>
        <v-layout wrap>
          <v-text-field label="Text"/>
        </v-layout>
      </v-container>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn @click="menu=false">{{ "Close" }}</v-btn>
      </v-card-actions>
    </v-card>
  </v-menu>
</template>

Writing this, every slot will pass through your parent component to your child component.

This will work for every slot of a component. You could however just make it work for your usecase writing something like the following:

<template>
  <v-menu bottom left
    v-model="menu"
    :close-on-content-click="false"
  >

    <!-- pass through scoped slots -->
    <template v-slot:activator="slotData">
      <slot v-if="$scopedSlots.activator" name="activator" v-bind="slotData" />
      <v-btn v-else v-on="slotData.on">Add row</v-btn>
    </template>


    <v-card width="300">
      <v-container>
        <v-layout wrap>
          <v-text-field label="Text"/>
        </v-layout>
      </v-container>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn @click="menu=false">{{ "Close" }}</v-btn>
      </v-card-actions>
    </v-card>
  </v-menu>
</template>