Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.4k views
in Technique[技术] by (71.8m points)

vue.js - How to pass cell templates to a component with b-table?

I created a component that shows table data for various pages. That component uses b-table inside. Now for a couple pages I want to customize rendering of some columns, and Bootstrap Tables allow that using scoped field slots with special syntax:

    <template #cell(field)="data">
            {{ data.item.value }}
    </template>

where field - column name, coming from my array with columns, and data.item - cell item to be rendered.

The problem is that I have different fields for different pages, so this customization should come from parent component, and these templates should be created dynamically.

Here is how I tried to solve it:

Pass via property to MyTableComponent an array with customizable fields and unique slot names In MyTableComponent dynamically create templates for customization, and inside dynamically create named slots

From parent pass slot data to named slots

MyTableComponent:

    <b-table>
        <template v-for="slot in myslots" v-bind="cellAttributes(slot)">
            <slot :name="slot.name"></slot>
        </template>
    </b-table>

    <script>
        ...
        computed: {
            cellAttributes(slot) {
                return ['#cell(' + slot.field + ')="data"'];
            }
        }
        ...
    </script>

Parent:

    <MyTableComponent :myslots="myslots" :items="items" :fields="fields">
        <template slot="customSlot1">
                Hello1
        </template>
        <template slot="customSlot1">
                Hello2
        </template>
    </MyTableComponent>

<script>
    ...
    items: [...my items...],
    fields: [...my columns...],
    myslots: [
        { field: "field1", name: "customSlot1" },
        { field: "field2", name: "customSlot2" }
    ]
    ...
</script>

Unfortunately, b-table component just ignores my custom slots like if they are not provided. It works if I specify in the MyTableComponent it directly:

    <b-table>
          <template #cell(field1)="data">
            {{ data.item.value }}
          </template>
    </b-table>

But I need it to be done dynamically via component properties. Please help.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use Dynamic Slot Names feature of Vue 2 to pass all (or some) slots from parent to <b-table> inside child like this:

Child:

<b-table>
  <template v-for="(_, slotName) of $scopedSlots" v-slot:[slotName]="scope">
    <slot :name="slotName" v-bind="scope"/>
  </template>
</b-table>

$scopedSlots contains all slots passed to your component.

Now this will work:

    <MyTableComponent :items="items" :fields="fields">
          <template #cell(field1)="data">
            {{ data.item.value }}
          </template>
    </ MyTableComponent>

UPDATE 2 - Vue 3

To make above code work in Vue 3, just replace $scopedSlots with $slots as suggested by migration guide

UPDATE 1

You can filter $scopedSlots if you want (have some slot specific to your wrapper component you don't want to pass down to <b-table>) by creating computed

I mentioned this possibility in my original answer but it is a bit problematic so it deserves better explanation...

  1. Scoped slots are passed to a component as a functions (generating VNode's when called). Target component just executes those she knows about (by name) and ignores the rest. So lets say your wrapper has b-table (or v-data-table for Vuetify) inside and some other component, let's say pagination. You can use code above inside both of them, passing all slots to each. Unless there is some naming conflict (both components using same slot name), it will work just fine and does not induce any additional cost (all slot functions are already compiled/created when passed to your wrapper component). Target component will use (execute) only the slots it knows by name.

  2. If there is possible naming conflict, it can be solved by using some naming convention like prefixing slot names intended just for b-table with something like table-- and doing filtering inside but be aware that $scopedSlots object does contain some Vue internal properties which must be copied along !! ($stable, $key and $hasNormal for Vue 2 - see the code). So the filtering code below even it's perfectly fine and doesn't throw any error will not work (b-table will not recognize and use the slots)

<b-table>
  <template v-for="(_, slotName) of tableSlots" v-slot:[slotName]="scope">
    <slot :name="slotName" v-bind="scope"/>
  </template>
</b-table>
computed: {
    tableSlots() {
      const prefix = "table--";
      const raw = this.$scopedSlots;
      const filtered = Object.keys(raw)
        .filter((key) => key.startsWith(prefix))
        .reduce(
          (obj, key) => ({
            ...obj,
            [key.replace(prefix, "")]: raw[key],
          }),
          {}
        );
      return filtered;
    },
  },

This code can be fixed by including the properties mentioned above but this just too much dependency on Vue internal implementation for my taste and I do not recommend it. If it's possible, stick with the scenario 1...


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...