Home Reference Source

src/components/widgets/Tree/component/Tree.jsx

import React, { Component } from 'react';
import TreeBase from 'rc-tree';
import {
  pick,
  isEqual,
  map,
  eq,
  difference,
  filter,
  isArray,
  isFunction,
  values,
} from 'lodash';
import { HotKeys } from 'react-hotkeys';
//components
import { BaseNode } from '../TreeNodes';
import Filter from './Filter';
import ExpandBtn from './ExpandBtn';
//fns
import {
  createTreeFn,
  takeKeysWhenSearching,
  customTreeActions,
  FILTER_MODE,
  animationTree,
  singleDoubleClickFilter,
} from '../until';
import {
  propTypes,
  defaultProps,
  TREE_NODE_PROPS,
  TREE_PROPS,
} from './treeProps';
import Icon from '../../../snippets/Icon/Icon';
import CheckboxN2O from '../../../controls/Checkbox/CheckboxN2O';
import { KEY_CODES } from './constants';

class Tree extends Component {
  constructor(props) {
    super(props);

    this.treeRef = React.createRef();

    this.state = {
      expandedKeys: [],
      autoExpandParent: true,
      searchValue: '',
      checkedKeys: [],
      selectedKeys: [],
      searchKeys: [],
    };

    this.elems = [];

    this.createTree = createTreeFn(BaseNode);

    this.onFilter = this.onFilter.bind(this);
    this.onExpand = this.onExpand.bind(this);
    this.onHideAllTreeItem = this.onHideAllTreeItem.bind(this);
    this.onShowAllTreeItem = this.onShowAllTreeItem.bind(this);
    this.renderSwitcherIcon = this.renderSwitcherIcon.bind(this);
    this.onCustomActions = this.onCustomActions.bind(this);
    this.onCheck = this.onCheck.bind(this);
    this.onSelect = this.onSelect.bind(this);
    this.createSelectedKeys = this.createSelectedKeys.bind(this);
    this.selectedObjToTreeKeys = this.selectedObjToTreeKeys.bind(this);
    this.onDoubleClickHandler = this.onDoubleClickHandler.bind(this);
  }

  componentDidUpdate(prevProps) {
    if (!isEqual(prevProps.resolveModel, this.props.resolveModel)) {
      this.createSelectedKeys();
    }
  }

  onFilter(value) {
    const propsFromSearch = pick(this.props, [
      'labelFieldId',
      'filter',
      'valueFieldId',
      'datasource',
    ]);
    const expandedKeys = takeKeysWhenSearching({
      value,
      ...propsFromSearch,
    });

    this.setState({
      expandedKeys,
      autoExpandParent: true,
      searchKeys: expandedKeys,
      searchValue: value,
    });
  }

  onExpand(expandedKeys) {
    this.setState({
      expandedKeys,
      autoExpandParent: false,
    });
  }

  onShowAllTreeItem() {
    const { valueFieldId, datasource } = this.props;
    const filteredData = filter(datasource, item => !item.disabled);

    this.setState({
      expandedKeys: map(filteredData, valueFieldId),
      autoExpandParent: false,
    });
  }

  onHideAllTreeItem() {
    this.setState({
      expandedKeys: [],
    });
  }

  renderSwitcherIcon() {
    const { showLine } = this.props;
    if (!showLine) {
      return (
        <div className="icon-wrapper">
          <Icon className="switcher" name="fa fa-angle-right" />
        </div>
      );
    }
    return <CheckboxN2O inline />;
  }

  onSelect(keys, { nativeEvent }) {
    const { multiselect, hasCheckboxes } = this.props;
    const { selectedKeys } = this.state;

    if (multiselect && hasCheckboxes) {
      return false;
    }

    const multiOnlySelect = multiselect && !hasCheckboxes;

    let selectedKeysForResolve = null;

    if (multiOnlySelect && keys.length > 1) {
      if (nativeEvent.ctrlKey) {
        selectedKeysForResolve = keys;
      } else {
        selectedKeysForResolve = difference(keys, selectedKeys);
      }
    } else {
      selectedKeysForResolve = keys;
    }

    this.props.onResolve(selectedKeysForResolve);
  }

  onCheck(keys) {
    this.props.onResolve(keys);
  }

  onCustomActions(_, key) {
    const inState = pick(this.state, ['expandedKeys']);
    const inProps = pick(this.props, [
      'prefixCls',
      'valueFieldId',
      'parentFieldId',
      'datasource',
      'hasCheckboxes',
    ]);
    customTreeActions({
      key,
      treeRef: this.treeRef,
      ...inProps,
      ...inState,
    });
  }

  selectedObjToTreeKeys() {
    const { resolveModel, valueFieldId } = this.props;

    if (isArray(resolveModel)) {
      return map(resolveModel, valueFieldId);
    }
    if (!resolveModel) {
      return [];
    }
    return [resolveModel[valueFieldId]];
  }

  createSelectedKeys() {
    const { hasCheckboxes, multiselect } = this.props;
    if (hasCheckboxes && multiselect) {
      this.setState({
        selectedKeys: [],
        checkedKeys: this.selectedObjToTreeKeys(),
      });
    } else {
      this.setState({
        selectedKeys: this.selectedObjToTreeKeys(),
        checkedKeys: [],
      });
    }
  }

  onDoubleClickHandler() {
    this.onCustomActions(null, 'DB_CLICK');
  }

  //
  // onDrop(info) {
  //   const dropKey = info.node.props.eventKey;
  //   const dragKey = info.dragNode.props.eventKey;
  //   const dropPos = info.node.props.pos.split('-');
  //   const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
  //
  //   this.props.onDrop({ dragKey, dropKey, dropPosition });
  // }

  render() {
    const nodeProps = pick(this.props, TREE_NODE_PROPS);
    const treeOtherProps = pick(this.props, TREE_PROPS);

    const {
      expandedKeys,
      autoExpandParent,
      selectedKeys,
      checkedKeys,
      searchValue,
      searchKeys,
    } = this.state;
    const {
      filter,
      expandBtn,
      datasource,
      hasCheckboxes,
      multiselect,
      prefixCls,
      filterPlaceholder,
    } = this.props;

    const checkable =
      hasCheckboxes && multiselect ? <CheckboxN2O inline /> : false;

    return (
      <div className={`${prefixCls}-wrapper pt-4`}>
        {filter && FILTER_MODE.includes(filter) && (
          <Filter
            onFilter={this.onFilter}
            filterPlaceholder={filterPlaceholder}
          />
        )}
        {expandBtn && (
          <ExpandBtn
            onShowAll={this.onShowAllTreeItem}
            onHideAll={this.onHideAllTreeItem}
          />
        )}
        <HotKeys
          className="hotkey"
          keyMap={{ events: values(KEY_CODES) }}
          handlers={{ events: this.onCustomActions }}
        >
          <div tabIndex={1}>
            <TreeBase
              openAnimation={animationTree}
              ref={this.treeRef}
              treeData={this.createTree({
                datasource,
                ...nodeProps,
                searchKeys,
                searchValue,
              })}
              expandedKeys={expandedKeys}
              selectedKeys={selectedKeys}
              checkedKeys={checkedKeys}
              onCheck={this.onCheck}
              onSelect={singleDoubleClickFilter(this.onSelect, null, 200)}
              onDoubleClick={this.onDoubleClickHandler}
              multiple={multiselect}
              onDragEnter={this.onDragEnter}
              checkable={checkable}
              switcherIcon={this.renderSwitcherIcon}
              onExpand={this.onExpand}
              autoExpandParent={autoExpandParent}
              {...treeOtherProps}
            />
          </div>
        </HotKeys>
      </div>
    );
  }
}

Tree.propTypes = propTypes;
Tree.defaultProps = defaultProps;

export default Tree;