First look at the renderings:

Github project address: address.

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

The process of modifying the entire cascader component is as follows:

  1. Add a full selection button to see first on the page.
  2. Add click logic for all buttons.
  3. Update the status of all buttons according to the check status of cascader-Node collection.

Step 1: Add all buttons

The Cascader-Node component is rendered in the Cascader-Menu component with the following rendering code:

renderNodeList(h) { const { menuId} = this; const { isHoverMenu } = this.panel; const events = { on: {} }; if (isHoverMenu) { events.on.expand = this.handleExpand; } // This is the cascader-node set to render 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> ); }); // Render the Cascader-Node collection, we just need to insert all the selected components here. return [ ...nodes, isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null ]; }Copy the code

The code after adding the select all button looks like this:

return [ config.checkAll && index === 0 && <el-checkbox class="checkAll" indeterminate={isIndeterminate} Value ={checkAll} onChange={handleCheckAllChange}> All </el-checkbox>,...nodes, isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null ];Copy the code

Config. checkAll is the props parameter passed to the Cascader component.

For example, if I want to enable the checkAll function, I pass in the checkAll: true parameter.

 <el-cascader
    :options="options"
    :props="props"
    collapse-tags
    clearable></el-cascader>

data() {
      return {
        props: { multiple: true, checkAll: true, expandTrigger: 'hover' },
      }
}
Copy the code

That all selected item is going to be rendered.

Step 2: Add click logic for all buttons

Check the logical address of the el-checkbox.

Take a look at the cascader-Menu rendering block again:

return [ config.checkAll && index === 0 && <el-checkbox class="checkAll" indeterminate={isIndeterminate} Value ={checkAll} onChange={handleCheckAllChange}> All </el-checkbox>,...nodes, isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null ];Copy the code

We added two properties and a method to the el-Checkbox component:

  • Value: true-> All, false-> To be determined.
  • Indeterminate: When value is true, this attribute is invalid. If value is false, the property is not selected; if true, the property is not selected.
  • OnChange: Triggered when the button is clicked.

Take a look at the onChange trigger function first, take a closer look at the code comment:

handleCheckAllChange() { const { nodes, panel } = this; // Reverse the state of the all-select button this.checkAll =! this.checkAll; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; // Trigger the doCheck method for all cascader-Node components in the current cascader-menu group. The effect and logic behind it is the same as clicking cascader-Node. node.doCheck(this.checkAll, true); } // Trigger the cascader-panel method, which updates the checkValue. panel.calculateMultiCheckedValue(); },Copy the code

We write a method updateInDeterminate in the Cascader-Menu component that updates the status of the all button based on the check status of the Cascader-Node collection.

    updateInDeterminate() {      
      const { panel, nodes } = this;      
      if (panel.config.checkAll) {        
        let counter = 0;        
        for (let i = 0; i < nodes.length; i++) {         
            const node = nodes[i];          
            (node.checked || node.indeterminate) && counter++;        
        }        
        this.checkAll = counter === nodes.length;        
        this.isIndeterminate = !(counter === nodes.length || counter === 0);      
        }    
    },
Copy the code

When the updateInDeterminate method fires:

1, cascader-menu creation

created() { if (this.panel.config.checkAll && this.index === 0) { this.updateInDeterminate(); this.$on('updateInDeterminate', this.updateInDeterminate); }},Copy the code

2. When the node parameters passed in cascader-menu change

watch: { nodes() { this.panel.config.checkAll && this.index === 0 && this.updateInDeterminate(); }}Copy the code

3, Cascader-panel component checkValue (this refers to the cascader selected value) changes

watch: { checkedValue(val) { if (! isEqual(val, this.value)) { this.checkStrictly && this.calculateCheckedNodePaths(); Broadcast ('ElCascaderMenu', 'updateInDeterminate'); // This. Broadcast ('ElCascaderMenu', 'updateInDeterminate'); this.$emit('input', val); this.$emit('change', val); }}}Copy the code

4. Delete the cascader component tag

deleteTag(tag) { const { checkedValue, panel } = this; const current = tag.node.getValueByOption(); const val = checkedValue.find(n => isEqual(n, current)); this.checkedValue = checkedValue.filter(n => ! isEqual(n, current)); // If select all is enabled, then delete tag, If (this.config.checkAll) {this.$nextTick(() => {panel.$refs.menu.forEach((menu) => {panel. menu.updateInDeterminate(); }); }); } this.$emit('remove-tag', val); },Copy the code

Instead, the updateInDeterminate function is triggered in the Cascader-Menu component by deep listening for changes in the check property of the node collection. However, this would degrade component performance, so it was not adopted.