BootstrapVue: What if I want to refactor scoped field slots in tables

TL;DR

  1. You can use scoped field slots to render what a cell in a table should look.
  2. The # sign in a component property is a shorten alias of the v-slot statement. Therefore, #cell(index) is a shorten alias of v-slot:cell(index).
  3. If you need to loop scoped field slots, consider using the render function.
  4. Vue uses virtual DOM to compile codes, so does the render function.
  5. v-slot uses the scopedSlots property in the render function, so v-slot:cell(index) will be complied to scopedSlots: { "cell(index)": () => {} }.

First: The problem

Have you used BootstrapVue before? If so, you may have known that you can control what a cell should look like in a table by using scoped field slots.

Simply said, you can use <template #cell(prop)=""> on <b-table> template; and add a ["prop"] item to fields property. Like example shows:

<template>
    <div>
        <b-table small :fields="fields" :items="items" responsive="sm">
            <!-- A virtual column -->
            <template #cell(index)="data">
                {{ data.index + 1 }}
            </template>

            <!-- A custom formatted column -->
            <template #cell(name)="data">
                <b class="text-info">{{ data.value.last.toUpperCase() }}</b>, <b>{{ data.value.first }}</b>
            </template>

            <!-- A virtual composite column -->
            <template #cell(nameage)="data">
                {{ data.item.name.first }} is {{ data.item.age }} years old
            </template>

            <!-- Optional default data cell scoped slot -->
            <template #cell()="data">
                <i>{{ data.value }}</i>
            </template>
        </b-table>
    </div>
</template>

<script>
export default {
    data() {
        return {
            fields: [
                // A virtual column that doesn't exist in items
                'index',
                // A column that needs custom formatting
                { key: 'name', label: 'Full Name' },
                // A regular column
                'age',
                // A regular column
                'sex',
                // A virtual column made up from two fields
                { key: 'nameage', label: 'First name and age' }
            ],
            items: [
                { name: { first: 'John', last: 'Doe' }, sex: 'Male', age: 42 },
                { name: { first: 'Jane', last: 'Doe' }, sex: 'Female', age: 36 },
                { name: { first: 'Rubin', last: 'Kincade' }, sex: 'Male', age: 73 },
                { name: { first: 'Shirley', last: 'Partridge' }, sex: 'Female', age: 62 }
            ]
        }
    }
}
</script>

But what if your scoped field slots of a table are more and more? How to maintain it when you have more than thirty scoped field slots of the table?

I found that I can't use v-bind and v-for to make slots into an iterable array. Therefore, I should consider the render function.

Second: What exactly the render function is

Like React, Vue uses Virtual DOM to build the whole website structure from version 2.

So this code:

<i class="foo">{{ value }}</i>

Will be complied to:

function (createElement) {
    return createElement("i", { class: ["foo"] }, this.value);
}

Third: Exercise

But how about the table I mentioned before? Will the code in the first paragraph:

<template>
    <div>
        <b-table>
            <template #cell(index)="data">
                {{ data.index + 1 }}
            </template>
        </b-table>
    </div>
</template>

look like this?

createElement("div", [
    createElement("b-table", { /* bypass */ }, [
        createElement("template", { "#cell(index)": "data" }, data.index + 1)
    ])
]);

Hint: type npm run build to see the result.

Fourth: The answer

The answer is NO.

Actually, the code from the complied file is more like this:

t("div", [
    t("b-table", {
        scopedSlots: e._u([
            {
                key: "cell(index)",
                fn: function (n) { return [e._v(" " + e._s(n.index + 1) + " ")] }
            },
        ])
    })
], 1)

The compressed code shows that the # sign in a component property means v-slot, which complied in the scopedSlots property.

And now, you know what it should look like when putting them in the render function based on the manual:

{
    render: function (createElement) {
        return createElement("div", [
            createElement("b-table", {
                scopedSlots: {
                    "cell(index)": data => createElement("span", data.index + 1)
                }
            })
        ])
    }
}

Now you can use "the full programmatic power of JavaScript":

{
    render: function (createElement) {
        const scopedSlots = {
            "cell(index)": data => createElement("span", data.index + 1)
        };
        return createElement("div", [
            createElement("b-table", { scopedSlots })
        ])
    }
}

If you are in interested now, how about turning an array to scopedSlots by using Object.fromEntries() method?