user8739862
user8739862

Reputation:

How to wrap a component in VueJS?

Of course, UI components are already wrapped enough conveniently. But, I want to reuse some components with custom options.

Especially, I want to reuse data tables component.

If all views have completely same header and data, it's no problem. When each views have different data, it's not working.

Here's my code:

Wrapper.vue

<template>
    <v-card>
        <v-card-title>
            <span class="pr-3">{{ tableTitle }}</span>
            <slot name="actions"/>
            <v-spacer/>
            <v-text-field
                    append-icon="search"
                    label="search"
                    single-line
                    hide-details
                    v-model="search"
            />
        </v-card-title>
        <v-data-table
                :search="search"
                :headers="headers"
                :items="items"
                hide-actions
        >

            <!-- problem is here! -->
            <slot name="items" slot="items" slot-scope="props"></slot>

            <template slot="expand" slot-scope="props">
                <v-card flat>
                    <v-card-text>{{ props.item.note }}</v-card-text>
                </v-card>
            </template>
            <template slot="no-data">
                <v-alert :value="true" color="error" icon="warning">
                    no data.
                </v-alert>
            </template>
            <template slot="no-results">
                <v-alert :value="true" color="error" icon="warning">
                    no result.
                </v-alert>
            </template>
        </v-data-table>
    </v-card>
</template>

<script>
    export default {
        props: {
            tableTitle: {type: String},
            search: {type: String},
            headers: {type: Array},
            items: {type: Array}
        }
    }
</script>

Main.vue

<template>
    <v-layout fluid fill-height justify-center align-center row wrap>
        <v-flex sm12 md12 fill-height>
            <main-custom-table
                    tableTitle="table1"
                    :headers="headers"
                    :items="items"
            >
                <template slot="actions">
                    <v-btn color="info">
                        <v-icon>add</v-icon>
                        add
                    </v-btn>
                </template>

                <!-- problem is here! -->
                <template slot="items">
                    <tr>
                        <td class="text-xs-left">{{ items.id }}</td>
                        <td class="text-xs-left">{{ items.data1 }}</td>
                        <td class="text-xs-left">{{ items.data2 }}</td>
                        <td class="justify-center">
                            <v-btn icon class="mx-0" @click="">
                                <v-icon color="teal">edit</v-icon>
                            </v-btn>
                        </td>
                    </tr>
                </template>

            </main-custom-table>
        </v-flex>    
    </v-layout>
</template>

<script>
    export default {
        name: "main",
        data() {
            return {
                dialog: false,
                search: '',
                headers: [
                    {text: 'ID', value: 'id'},
                    {text: 'DATA1', value: 'data1'},
                    {text: 'DATA2', value: 'data2'}
                ],
                items: [
                    {
                        'id': 1,
                        'data1': 10,
                        'data2': 12,
                        'note': aaaaaa
                    },
                    {
                        'id': 2,
                        'data1': 20,
                        'data2': 13,
                        'note': bbbbbb
                    },
                    {
                        'id': 5,
                        'data1': 30,
                        'data2': 14,
                        'note': cccccc
                    }
                ]
            };
        }
    }
</script>

I want to write only tbody in Main.vue (and other views), and other optional elements in the Wrapper.vue.

Upvotes: 9

Views: 7798

Answers (2)

Harsh Karanpuria
Harsh Karanpuria

Reputation: 121

It is not working because usage of your slot scope is not proper. Below change should work.

Wrapper.vue

<v-data-table
    :search="search"
    :headers="headers"
    :items="items"
    hide-actions
>
    <!-- problem is here! -->
    <slot name="items" v-bind:item="props" slot-scope="props"></slot>

    <template slot="expand" slot-scope="props">
        <v-card flat>
            <v-card-text>{{ props.item.note }}</v-card-text>
        </v-card>
    </template>
    <template slot="no-data">
        <v-alert :value="true" color="error" icon="warning">
            no data.
        </v-alert>
    </template>
    <template slot="no-results">
        <v-alert :value="true" color="error" icon="warning">
            no result.
        </v-alert>
    </template>
</v-data-table>

Main.vue

<template slot="items" slot-scope="item">
    <tr>
        <td class="text-xs-left">{{ item.id }}</td>
        <td class="text-xs-left">{{ item.data1 }}</td>
        <td class="text-xs-left">{{ item.data2 }}</td>
        <td class="justify-center">
            <v-btn icon class="mx-0" @click="">
                <v-icon color="teal">edit</v-icon>
            </v-btn>
        </td>
    </tr>
</template>

For more information refer: https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots-with-the-slot-scope-Attribute

Upvotes: 0

Felipe Endlich
Felipe Endlich

Reputation: 610

In order to wrap a component you should use the following syntax.

  • inheritAttrs: false and v-bind:$attrs: Use that option on the component to pass all attributes used in the wrapper to the target component.
  • v-for="(index, name) in $scopedSlots" v-slot:[name]="data" and <slot :name="name" v-bind="data"></slot>: Use that to iterate over all slots defined on the target component, and define them in the wrapper either.
  • v-on:$listeners: Use that option to pass all events coming from the target component throug the wrapper.

After defining all that, the wrapper will work as desired. It'll just wrap the component. Then you can add your customizations.

Wrapper

<template>
  <v-data-table
    v-bind="$attrs"
    v-on="$listeners"
  >
    <template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
      <slot :name="name" v-bind="data"></slot>
    </template>
  </v-data-table>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

@Component({
    name: 'BaseTable',
    inheritAttrs: false
})
export default class extends Vue {
}
</script>

Upvotes: 2

Related Questions