cascader-node.vue 6.25 KB
<script>
  import ElCheckbox from 'element-ui/packages/checkbox';
  import ElRadio from 'element-ui/packages/radio';
  import { isEqual } from 'element-ui/src/utils/util';

  const stopPropagation = e => e.stopPropagation();

  export default {
    inject: ['panel'],

    components: {
      ElCheckbox,
      ElRadio
    },

    props: {
      node: {
        required: true
      },
      nodeId: String
    },

    computed: {
      config() {
        return this.panel.config;
      },
      isLeaf() {
        return this.node.isLeaf;
      },
      isDisabled() {
        return this.node.isDisabled;
      },
      checkedValue() {
        return this.panel.checkedValue;
      },
      isChecked() {
        return this.node.isSameNode(this.checkedValue);
      },
      inActivePath() {
        return this.isInPath(this.panel.activePath);
      },
      inCheckedPath() {
        if (!this.config.checkStrictly) return false;

        return this.panel.checkedNodePaths
          .some(checkedPath => this.isInPath(checkedPath));
      },
      value() {
        return this.node.getValueByOption();
      }
    },

    methods: {
      handleExpand() {
        const { panel, node, isDisabled, config } = this;
        const { multiple, checkStrictly } = config;

        if (!checkStrictly && isDisabled || node.loading) return;

        if (config.lazy && !node.loaded) {
          panel.lazyLoad(node, () => {
            // do not use cached leaf value here, invoke this.isLeaf to get new value.
            const { isLeaf } = this;

            if (!isLeaf) this.handleExpand();
            if (multiple) {
              // if leaf sync checked state, else clear checked state
              const checked = isLeaf ? node.checked : false;
              this.handleMultiCheckChange(checked);
            }
          });
        } else {
          panel.handleExpand(node);
        }
      },

      handleCheckChange() {
        const { panel, value, node } = this;
        panel.handleCheckChange(value);
        panel.handleExpand(node);
      },

      handleMultiCheckChange(checked) {
        this.node.doCheck(checked);
        this.panel.calculateMultiCheckedValue();
      },

      isInPath(pathNodes) {
        const { node } = this;
        const selectedPathNode = pathNodes[node.level - 1] || {};
        return selectedPathNode.uid === node.uid;
      },

      renderPrefix(h) {
        const { isLeaf, isChecked, config } = this;
        const { checkStrictly, multiple } = config;

        if (multiple) {
          return this.renderCheckbox(h);
        } else if (checkStrictly) {
          return this.renderRadio(h);
        } else if (isLeaf && isChecked) {
          return this.renderCheckIcon(h);
        }

        return null;
      },

      renderPostfix(h) {
        const { node, isLeaf } = this;

        if (node.loading) {
          return this.renderLoadingIcon(h);
        } else if (!isLeaf) {
          return this.renderExpandIcon(h);
        }

        return null;
      },

      renderCheckbox(h) {
        const { node, config, isDisabled } = this;
        const events = {
          on: { change: this.handleMultiCheckChange },
          nativeOn: {}
        };

        if (config.checkStrictly) { // when every node is selectable, click event should not trigger expand event.
          events.nativeOn.click = stopPropagation;
        }

        return (
          <el-checkbox
            value={ node.checked }
            indeterminate={ node.indeterminate }
            disabled={ isDisabled }
            { ...events }
          ></el-checkbox>
        );
      },

      renderRadio(h) {
        let { checkedValue, value, isDisabled } = this;

        // to keep same reference if value cause radio's checked state is calculated by reference comparision;
        if (isEqual(value, checkedValue)) {
          value = checkedValue;
        }

        return (
          <el-radio
            value={ checkedValue }
            label={ value }
            disabled={ isDisabled }
            onChange={ this.handleCheckChange }
            nativeOnClick={ stopPropagation }>
            {/* add an empty element to avoid render label */}
            <span></span>
          </el-radio>
        );
      },

      renderCheckIcon(h) {
        return (
          <i class="el-icon-check el-cascader-node__prefix"></i>
        );
      },

      renderLoadingIcon(h) {
        return (
          <i class="el-icon-loading el-cascader-node__postfix"></i>
        );
      },

      renderExpandIcon(h) {
        return (
          <i class="el-icon-arrow-right el-cascader-node__postfix"></i>
        );
      },

      renderContent(h) {
        const { panel, node } = this;
        const render = panel.renderLabelFn;
        const vnode = render
          ? render({ node, data: node.data })
          : null;

        return (
          <span class="el-cascader-node__label">{ vnode || node.label }</span>
        );
      }
    },

    render(h) {
      const {
        inActivePath,
        inCheckedPath,
        isChecked,
        isLeaf,
        isDisabled,
        config,
        nodeId
      } = this;
      const { expandTrigger, checkStrictly, multiple } = config;
      const disabled = !checkStrictly && isDisabled;
      const events = { on: {} };

      if (expandTrigger === 'click') {
        events.on.click = this.handleExpand;
      } else {
        events.on.mouseenter = e => {
          this.handleExpand();
          this.$emit('expand', e);
        };
        events.on.focus = e => {
          this.handleExpand();
          this.$emit('expand', e);
        };
      }
      if (isLeaf && !isDisabled && !checkStrictly && !multiple) {
        events.on.click = this.handleCheckChange;
      }

      return (
        <li
          role="menuitem"
          id={ nodeId }
          aria-expanded={ inActivePath }
          tabindex={ disabled ? null : -1 }
          class={{
            'el-cascader-node': true,
            'is-selectable': checkStrictly,
            'in-active-path': inActivePath,
            'in-checked-path': inCheckedPath,
            'is-active': isChecked,
            'is-disabled': disabled
          }}
          {...events}>
          { this.renderPrefix(h) }
          { this.renderContent(h) }
          { this.renderPostfix(h) }
        </li>
      );
    }
  };
</script>