SoFunction
Updated on 2025-04-03

Vue secondary encapsulation el-select implements pull-down scroll loading effect (el-select infinite scrolling)

Preface

When we do business needs, we may encounter a very large amount of data, sometimes hundreds or thousands of pieces, and the backend generally writes a paginated interface, as long as we add page number parameters when requesting.

However, in the el-select drop-down menu component using element-ui, the official does not provide the corresponding method for multi-page loading.

At this time, we can implement a custom Vue instruction. Whenever we use el-select to scroll to the bottom of the list, we request the next page of data to achieve the purpose of pull-down scrolling and loading more.

Implement custom instructions

First implement a custom directive v-loadmore that is loaded by el-select drop-down:

// 
import Vue from 'vue'

("loadmore", {
    bind(el, binding, vnode) {
        const SELECTWRAP = (
            ".el-select-dropdown .el-select-dropdown__wrap"
        );
        ("scroll", function () {
            // scrollTop Here, it may be because the browser zoom has a decimal point, which causes scrolling to the bottom.            // scrollHeight minus scrollTop when scrolling to the bottom is still greater than clientHeight, resulting in the inability to request more data            // Here, scrollTop is rounded upwards to ensure that when scrolling to the bottom, the call is triggered            const CONDITION =  - () <= ;
            // !== 0 When entering, if the search results are very few, so that no scroll bar is seen, then the CONDITION calculation result at this time is true, and () will be executed. It should not be executed at this time, otherwise the search results do not match            if (CONDITION &&  !== 0) {
                ();
            }
        });
    },
});

Code description:

:querySelector()The method returns only the first element that matches the specified selector.

Minimum height required to accommodate content used in the viewport without using scroll bars (read-only)

Warning: On systems that use display scale scaling, scrollTop may provide a decimal.

Gets or sets the number of pixels to which the content of an element is scrolled vertically.

Reads the visible height of the element (read-only).

If the element scrolls to the end, the following equation returns true, and if none, it returns false.

// scrollTop Here, there may be a decimal point when the browser zoom is not equal to 100%, which causes scrolling to the bottom.

// scrollHeight minus scrollTop when scrolling to the bottom is still greater than clientHeight resulting in no loading event being triggered

// Here, scrollTop is rounded upwards to ensure that when scrolling to the bottom, the call is triggered

// This judgment is inaccurate: - === // Use the following judgment method to ensure

Any zoom can trigger:

 - () <= 

Register the v-loadmore directive globally in the project:

//

import directives from './'
(directives)

Finally, use this directive in component el-select:

&lt;template&gt;
    &lt;el-select v-model="selected" v-loadmore="loadMore"&gt;
        &lt;el-option
            v-for="option in options"
            :label=""
            :value=""
            :key=""
        &gt;&lt;/el-option&gt;
    &lt;/el-select&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
    data() {
        return {
            selected: "",
            options: [
                {
                    label: "1",
                    value: 1
                },
                // ... Multiple options are omitted here                {
                    label: "It's time to the bottom",
                    value: 9
                }
            ]
        };
    },
    methods: {
        loadMore() {
            ("more")
        }
    }
};

The effect of use is as follows:

As can be seen from the renderings, whenever the menu list scrolls to the bottom, the instruction will call the incoming loadMore function, and the console will print out "more".
Notes:

The number of arrays passed in must be greater than or equal to 8 options before the el-select component can be pulled down.
When scrolling does not exist in the list, the function passing in the incoming instruction cannot be triggered.

Perform secondary packaging

The instruction to call the function at the bottom has been implemented. Just call the interface and splice the data obtained from the next page into the current data.

Next, el-select is secondary encapsulated, and after encapsulating it into a common component, the necessary parameters can be called in the project.

First create a new file:

&lt;template&gt;
    &lt;el-select :value="value" v-loadmore="loadMore" @focus="focus" v-bind="$attrs" v-on="$listeners"&gt;
        &lt;el-option
            v-for="option in data"
            :label="option[dictLabel]"
            :value="option[dictValue]"
            :key=""
        &gt;&lt;/el-option&gt;
    &lt;/el-select&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
    props: {
        value: {
            type: String,
            default: ""
        },
        // List data        data: {
            type: Array,
            default: () =&gt; []
        },
        dictLabel: {
            type: String,
            default: "label"
        },
        dictValue: {
            type: String,
            default: "value"
        },
        // Call the interface for page count        request: {
            type: Function,
            default: () =&gt; {}
        },
        page: {
            type: [Number, String],
            default: 1
        }
    },
    data() {
        return {};
    },
    methods: {
        // Request data from the next page        loadMore() {
            ({ page:  + 1 })
        },
        // When there is no data in the drop-down box, the data on the first page will be automatically requested.        focus() {
            if (!) {
                ({page: 1})
            }
        }
    }
};
&lt;/script&gt;

Called in the page component:

&lt;!--  --&gt;

&lt;template&gt;
    &lt;div class="xxx-page"&gt;
        &lt;load-select v-model="selected" :data="data" :page="page" :request="getData"&gt;&lt;/load-select&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
// Import this componentimport loadSelect from "@/components/load-select/index";

export default {
    name: "app",
    components: {
        loadSelect
    },
    data() {
        return {
            selected: "",
            page: 1,
            more: true,
            data: []
        };
    },
    methods: {
        // Function passed to the load-select component        getData({ page = 1 } = {}) {
            // Output page count            (page)
            // Access the backend interface API            ({ page }).then(res =&gt; {
                 = [..., ...]
                 = 
            });
        },
        // Simulate the API of the backend interface        requestAPI({ page = 1, size = 10 } = {}) {
            return new Promise(resolve =&gt; {
                let responseData = []
                // Assume that there are 50 pieces of data in total                let total = 50; 
                for (let index = 1; index &lt;= size; index++) {
                    // serial: At the number of elements, how many sequence numbers will be displayed                    let serial = index + (page - 1) * size
                    if (serial &lt;= 50) {
                        ({
                            label: serial,
                            value: serial
                        });
                    }
                }
                // Simulate asynchronous requests, and return the interface data after 500ms                setTimeout(() =&gt; {
                    resolve({
                        total,
                        page,
                        size,
                        result: responseData
                    });
                }, 500);
            });
        }
    }
};
&lt;/script&gt;

Code parsing:

When the drop-down box is first clicked, the focus event will be triggered to request data on the first page. After that, as long as the list is scrolled to the bottom, the data on the next page will be automatically requested and then spliced ​​into the current array.

Let's take a look at the effect:

Perfect! However, during actual use, the interface may not have time to return data, and then the list scrolls down and triggers the request again, resulting in two copies of the same data being returned.

Now adjust the interface delay to 2000ms to reproduce this scenario:

When you scroll to the bottom twice quickly, the number of pages requested is 2. How to solve this problem? An intercept operation can be added to the load function. The load function is not called until the interface does not respond. However, in order to convert getData into an asynchronous function.

First, add an intercept operation to loadMore() in:

&lt;!--  --&gt;

&lt;template&gt;
    ...
&lt;/template&gt;

&lt;script&gt;
    // Request data from the next page    methods: {
        loadMore() {
            // If the intercept property is true, no data is requested            if () {
                return 
            }
             = true
            ({ page:  + 1 }).then(() =&gt; {
                // Set intercept to false after the interface responds                 = false
            })
        }
    }
&lt;/script&gt;

Then convert the getData() function in it into the form of an asynchronous function:

&lt;template&gt;
    ...
&lt;/template&gt;

&lt;script&gt;
    methods: {
        // Function passed to the load-select component        getData({ page = 1 } = {}) {
            // Return the Promise object            return new Promise( resolve =&gt; {
                // Access the backend interface API                ({ page }).then(res =&gt; {
                     = [..., ...]
                     = 
                    resolve()
                });
            })
        }, 
    
    }
&lt;/script&gt;

Now the question is:

Generally, the pagination interface supports keyword search. Can the component add keyword search function?

Keyword search function

Fortunately, the el-select component supports remote search function. As long as filterable and remote parameters are passed in, you can check the official document of element-ui for details.

Next, make the following modifications:

&lt;!--  --&gt;
&lt;template&gt;
    &lt;el-select
        :value="value"
        v-loadmore="loadMore"
        @focus="focus"
        filterable
        remote
        :filter-method="handleSearch"
        :loading="loading"
        clearable
        v-bind="$attrs"
        v-on="$listeners"
    &gt;
        &lt;el-option
            v-for="option in data"
            :label="option[dictLabel]"
            :value="option[dictValue]"
            :key=""
        &gt;&lt;/el-option&gt;
        &lt;!-- Loading herevalueCan be set as you like,As long as it does not repeat with other data --&gt;
        &lt;el-option v-if="hasMore" disabled label="loading..." value="-1"&gt;&lt;/el-option&gt;
    &lt;/el-select&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
    props: {
        value: {
            default: ""
        },
        // List data        data: {
            type: Array,
            default: () =&gt; []
        },
        dictLabel: {
            type: String,
            default: "label"
        },
        dictValue: {
            type: String,
            default: "value"
        },
        // Call the interface for page count        request: {
            type: Function,
            default: () =&gt; {}
        },
        // Incoming page number        page: {
            type: [Number, String],
            default: 1
        },
        // Is there any more data        hasMore: {
            type: Boolean,
            default: true
        }
    },
    data() {
        return {
            // Used to store keywords            keyword: "", 
            loading: false
        };
    },
    methods: {
        // Request data from the next page        loadMore() {
            // If there is no more data, no request            if (!) {
                return
            }
            // If the intercept property is true, no data is requested.            if () {
                return
            }
             = true;
            ({
                page:  + 1,
                more: true,
                keyword: 
            }).then(() =&gt; {
                 = false
            });
        },
        // When there is no data in the drop-down box, the data on the first page will be automatically requested.        focus() {
            if (!) {
                ({ page: 1 })
            }
        },
        // Keyword search        handleSearch(keyword) {
             = keyword
             = true
            ({ page: 1, keyword }).then(() =&gt; {
                 = false
            });
        },
        // When deletion is selected, if the keyword is requested, clear the keyword and then request data on the first page.        clear() {
            if () {
                 = ""
                ({ page: 1 })
            }
        }
    }
};
&lt;/script&gt;

When the page is called, the getData() request function needs to receive keyword and more parameters and perform corresponding processing:

&lt;!--  --&gt;

&lt;template&gt;
    &lt;div class="xxx-page"&gt;
        &lt;load-select v-model="selected" :data="data" :page="page" :hasMore="more" :request="getData"&gt;&lt;/load-select&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
// Import this componentimport loadSelect from "@/components/load-select/index";

export default {
    name: "app",
    components: {
        loadSelect
    },
    data() {
        return {
            selected: "",
            page: 1,
            more: true,
            data: []
        };
    },
    methods: {
        // Function passed to the load-select component        getData({ page = 1, more = false, keyword = "" } = {}) {
            return new Promise(resolve =&gt; {
                // Access the backend interface API                ({ page, keyword }).then(res =&gt; {
                    // If more is loaded, merge the previous data                    if (more) {
                         = [..., ...]
                    } else {
                         = 
                    }

                     = ;
                    let { total, page, size } = res
                    // If it is the last page, set more to false                     = page * size &lt; total
                     = page
                    resolve()
                });
            });
        },
        // Simulate the API of the backend interface        requestAPI({ page = 1, size = 10, keyword = "" } = {}) {
            return new Promise(resolve =&gt; {
                // If there is a keyword parameter, data with keyword is returned                if (keyword) {
                    setTimeout(() =&gt; {
                        resolve({
                            total: 3,
                            page: 1,
                            size: 10,
                            result: [
                                {
                                    label: keyword,
                                    value: 1
                                },
                                {
                                    label: keyword + 1,
                                    value: 2
                                },
                                {
                                    label: keyword + 2,
                                    value: 3
                                }
                            ]
                        })
                    }, 500)
                    return
                }

                let responseData = [];
                // Assume that there are 50 pieces of data in total                let total = 50; 
                for (let index = 1; index &lt;= size; index++) {
                    // serial: At the number of elements, how many sequence numbers will be displayed                    let serial = index + (page - 1) * size
                    if (serial &lt;= 50) {
                        ({
                            label: serial,
                            value: serial
                        });
                    }
                }
                setTimeout(() =&gt; {
                    resolve({
                        total,
                        page,
                        size,
                        result: responseData
                    })
                }, 500)
            })
        }
    }
};
&lt;/script&gt;

Next, let’s take a look at the results of searching for keywords:

The search function is also completed!

Summarize

In order to be suitable for most request interfaces, when designing this component, the request can only be separated from the component, and the ease of use is not too high. However, we can appropriately pass in some simple and necessary parameters to maintain basic use.

Of course, when we encounter certain fixed loading requests in the project, we can also re-encapsulate the component, and we can modify it according to our own business needs.

This is the article about Vue secondary encapsulation of el-select to implement the pull-down scroll loading effect (el-select infinite scrolling). For more related Vue el-select infinite scrolling content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!