Effect:

Github project address, address.

First, if you want to understand the overall structure of the Cascader component, you can read this article first.

Since vue-virtual-scroll list is used as a third-party component, it is recommended that you first learn about the address of this component.

Implementation steps:

  1. Add ue-virtual-scroll-list to cascader-menu.
  2. Write a true-virtual-scroll list date-component.
  3. Overwrite the auto-scrolling method in Cascader-Panel.

Since the list on the component, the Cascader-Node collection, is rendered in cascader-Menu, we add the vue-virtual-scroll-list component to cascader-Menu.

Rendering the cascader-Node set in cascader-Menu:

renderNodeList(h) { const { menuId } = this; const { isHoverMenu } = this.panel; const events = { on: {} }; if (isHoverMenu) { events.on.expand = this.handleExpand; } const nodes = this.nodes.map((node, index) => { const { hasChildren } = node; return ( <cascader-node key={ node.uid } node={ node } node-id={ `${menuId}-${index}` } aria-haspopup={ hasChildren } aria-owns = { hasChildren ? menuId : null } { ... events }></cascader-node> ); }); return [ ...nodes, isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null ]; }Copy the code

VirtualScroll: true config.virtualScroll: true config.virtualScroll: true

renderNodeList(h) { const { menuId, nodes } = this; const { isHoverMenu, config } = this.panel; const events = { on: {} }; if (isHoverMenu) { events.on.expand = this.handleExpand; } this.virtualListProps.menuId = menuId; const nodeItems = nodes.map((node, index) => { const { hasChildren } = node; return ( <cascader-node key={ node.uid } node={ node } node-id={ `${menuId}-${index}` } aria-haspopup={ hasChildren } aria-owns = { hasChildren ? menuId : null } { ... events }></cascader-node> ); }); return [ config.virtualScroll ? <virtual-list ref="virtualList" class="el-cascader-menu__virtual-list" data-key="uid" data-sources={nodes} extra-props={this.virtualListProps} data-component={virtualListItem}> </virtual-list> : [...nodeItems], isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null ]; }Copy the code

We know how to use a vue-virtual-scroll list component (the virtual-list component in the code above) :

  1. Data-sources: the data source for the list, for example[ { label: 1, value: 1 }, { label: 2, value: 2} ]
  2. Data-key: each key of the list LI, which can default to ‘uid’, is automatically added by the component.
  3. Data-component: vue-virtual-scroll-list Renders each item of the list with the component passed in by this attribute, that is, the component that each item of the list belongs to.
  4. Extra-props: Additional parameters that can be passed in.

Since vue-virtual-scroll-list requires an item component, we create a virtualListItem component as follows:

<script>  
import CascaderNode from './cascader-node.vue';  
export default {    
    name: 'ElCascaderVirtualScrollItem',    
    components: {CascaderNode},   
    props: {      
        index: { // index of current item        
            type: Number      
        },     
        source: { // here is: {uid: 'unique_1', text: 'abc'}       
            type: Object,      
            default() {          
                return {};        
            }      
        },      
        menuId: {       
            type: String,        
            default() {          
                return '';        
            }      
        }    
    },   
    render(h) {      
        const { source, menuId, index } = this;      
        return (        
            <cascader-node          
                key={ source.id }          
                node={ source }          
                node-id={ `${menuId}-${index}` }          
                aria-haspopup={source.hasChildren }         
                aria-owns={source.hasChildren ? menuId : null }        
            ></cascader-node>      
        );    
    }  
};</script>
Copy the code

In fact, the above code is a simple cascader-node component.

Then make the following changes in the cascader-menu rendering function:

return ( config.virtualScroll ? <div class="el-cascader-menu"> { isEmpty ? this.renderEmptyText() : this.renderNodeList(h) } </div> : <el-scrollbar tag="ul" role="menu" id={ menuId } class="el-cascader-menu" wrap-class="el-cascader-menu__wrap" view-class={{ 'el-cascader-menu__list': true, 'is-empty': isEmpty }} { ... events }> { isEmpty ? this.renderEmptyText(h) : this.renderNodeList(h) } </el-scrollbar> );Copy the code

For the Cascader-Node component, we first add an inActivePath property to it and update the inActivePath where appropriate.

To explain what inActivePath stands for, look at the following image:

ActivePath is updated when any cascader-Node component is selected, such as activePath = southeast – Jiangsu – Nanjing at the moment.

InActivePath is used to record whether the cascader-node is inActivePath. For example, inActivePath = true in southeast and inActivePath = false in northwest.

The cascader-Node inActivePath is updated in the following code:

computed: { inActivePath() { let inActivePath = this.isInPath(this.panel.activePath); // Update this.node.inActivePath = inActivePath; return inActivePath; }}Copy the code

Let’s take a look at cascader-panel:

// This method is triggered when a dropdown box appears after clicking cascader, which is used to automatically scroll the dropdown box to the appropriate place. scrollIntoView() { if (this.$isServer) return; const menus = this.$refs.menu || []; Menus. ForEach (menu => {const menuElement = menu.$el; If (menuElement) {if (menuElement) {if (menuElement) {if (menuElement) {if (menuElement) {if (menuElement) {if (menuElement) {if (menuElement) { The first selected) cascader -node const container. = menuElement querySelector (' el - scrollbar__wrap '); const activeNode = menuElement.querySelector('.el-cascader-node.is-active') || menuElement.querySelector('.el-cascader-node.in-active-path'); // Automatically scroll cascader-menu to the cascader-node found by the above code. scrollIntoView(container, activeNode); }}); },Copy the code

Since we’re using vue-virtual-scroll list, we’ll have to write our own logic for this method.

scrollIntoView() { if (this.$isServer) return; const menus = this.$refs.menu || []; / / to iterate through all the menu menus. ForEach (menu = > {/ / when using the virtual rolling if (this. Config. VirtualScroll) {let currentNodeIndex = 1; // Find the first inActivePath in cascader-menu: True cascader-node menu.nodes.find((item, index) => {let flag = item.inactivePath; flag && (currentNodeIndex = index); return flag; }); if (currentNodeIndex ! == -1) {// If there is an inActivePath node, use vue-virtual-scroll-list to scroll to the node. menu.$refs.virtualList && menu.$refs.virtualList.scrollToIndex(currentNodeIndex); } else {// If inActivePath is not found, find the first checked: true node. Then scroll to change node menu. Nodes. Find ((item, index) = > {let flag = item. Checked | | item. The indeterminate; flag && (currentNodeIndex = index); return flag; }); menu.$refs.virtualList && currentNodeIndex === -1 ? menu.$refs.virtualList.reset() : menu.$refs.virtualList.scrollToIndex(currentNodeIndex); }} else {// menuElement = menu.$el; if (menuElement) { const container = menuElement.querySelector('.el-scrollbar__wrap'); const activeNode = menuElement.querySelector('.el-cascader-node.is-active') || menuElement.querySelector('.el-cascader-node.in-active-path'); scrollIntoView(container, activeNode); }}}); },Copy the code