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:
- Add ue-virtual-scroll-list to cascader-menu.
- Write a true-virtual-scroll list date-component.
- 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) :
- Data-sources: the data source for the list, for example
[ { label: 1, value: 1 }, { label: 2, value: 2} ]
- Data-key: each key of the list LI, which can default to ‘uid’, is automatically added by the component.
- 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.
- 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