0
votes

I have a Vuetify v-data-table with about a 1000 rows in it. Rendering it for the first time and when text searching on it, the render takes a few seconds. During this time I want to overlay it with a scrim + spinner to indicate things are happening.

The overlay:

    <v-overlay
        :value="loading"
        opacity="0.7"
        absolute
        v-mutate="onMutate"
    >
        <v-progress-circular indeterminate size="32" color="blue"></v-progress-circular>
    </v-overlay>

The v-data-table:

        <v-data-table
            :headers="cHeaders"
            :items="cLimitedData"
            disable-pagination
            hide-default-footer
            :search="searchIntercepted"
            @current-items="tstCurrentItems"
        >

The computed variable cLimitedData:

    cLimitedData() {
        if (this.indNoLimit) {
            return this.data
        } else {
            return this.data.slice(0,this.dtRowTo)
        }
    },

I watch the search variable, and when it changes, I set loading to true to activate the overlay.

watch: {
    search() {
        if (!this.indNoLimit) {
            // remove limit, this will cause cLimitedData to return all rows
            this.loading = true
            // -> moved to onMutate
            //this.$nextTick(function () {
            //    this.indNoLimit = true
            //})
        } else {
            this.searchIntercepted = this.search
        }
    },

However, the overlay doesn't activate until after the v-data-table had finished rendering. I've tried a million things, one of them is to put a v-mutate="onMutate" on the overlay, and only when it fired, would I this.indNoLimit = true to set things in motion, but that is still not good enough to have the scrim start before the v-data-table begins reloading itself.

    onMutate(thing1) {
        console.log('@onMutate',thing1)
        this.$nextTick(function () {
            this.indNoLimit = true
            this.searchIntercepted = this.search
        })
    },

I also found that the next tick in @current-items fairly reliably marked the end of the render of the v-data-table, thus the deactivation of the scrim is probably going to be ok:

    tstCurrentItems(thing1) {
        console.log('@current-items',thing1)
        this.$nextTick(function () {
            console.log('@current-items NEXT')
            this.loading = false
        })

I believe my question should be: how can I detect/wait for components to have rendered (the v-overlay+v-progress-circular) before making changes to other components (the v-data-table).

Note: To solve the initial wait time of loading of the table, I found a way to progressively load it by inserting div-markers that trigger a v-intersect. However, this does not solve the situation when a user searches the whole data set when only the first 50 rows are loaded.

EDIT: Tried to start the update of the table after the overlay has been activated using https://github.com/twickstrom/vue-force-next-tick, but still no luck. It almost looks like vue tries to aggregate changes to the DOM instead of executing them one by one.

1

1 Answers

0
votes

I have not figured out why the v-data-table seems to block/freeze, but here is a solution that can streamline any large v-data-table:

The v-data-table:

        <v-data-table
            :headers="cHeaders"
            :items="data"
            :items-per-page="dtRowTo"
            hide-default-footer
            hide-default-header
            :search="search"
            item-key="id"
        >
            <template
                v-slot:item="{item, index, isSelected, select, headers}"
            >
                <tr>
                    <td
                        :colspan="headers.length"
                    >
                        Stuff
                        <div v-if="(index+1)%25==0" v-intersect.quiet.once="onIntersect">{{index+1}}</div>
                    </td>
                </tr>
            </template>
        </v-data-table>

Add a div, or any other element, to intersect with at given intervals. Every time we intersect with it, we're going to increase the page size.

Variables:

    dtRowTo: 50,   // initial nr of rows
    dtRowIncr: 50, // increments

The onIntersect:

        onIntersect (entries, observer) {
            let index = entries[0].target.textContent
            if (index == this.dtRowTo) {
                this.dtRowTo += this.dtRowIncr
            }
        },

Unfortunately you cannot add the index as an argument like v-intersect.quiet.once="onIntersect(index)", as this executes the function before you intersect with it (not sure why), so we will have to take the index out of the textContext.

Basically what you do is you increase the page size every time you're at the bottom. Search will still operate on the whole dataset.

What does not work (which I found out the hard way), is incrementally increasing the items, like the computed function below. As search needs the entire dataset to be present at :items, this won't work:

        <v-data-table
            :items="cLimitedData"

computed: {
    cLimitedData() {
        return this.data.slice(0,this.dtRowTo)
    },
}

Doing so is fine (I guess?) as long as you don't need search or anything that operates on the entire data set.