Transfer

Hyunwoo Seo·2022년 6월 13일
0

Ant.D

목록 보기
1/5
post-thumbnail

ant 디자인 Transfer 를 사용할 때, 기본적인 API 만 사용해도 쉽게 구현이 가능했다.

Transfer 에 Tree 를 더해 Tree 형 Transfer 를 구현하고 싶었고 데모버전이 있었다.

import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Transfer, Tree } from 'antd';

// Customize Table Transfer
const isChecked = (selectedKeys, eventKey) => selectedKeys.indexOf(eventKey) !== -1;

const generateTree = (treeNodes = [], checkedKeys = []) =>
  treeNodes.map(({ children, ...props }) => ({
    ...props,
    disabled: checkedKeys.includes(props.key),
    children: generateTree(children, checkedKeys),
  }));

const TreeTransfer = ({ dataSource, targetKeys, ...restProps }) => {
  const transferDataSource = [];
  function flatten(list = []) {
    list.forEach(item => {
      transferDataSource.push(item);
      flatten(item.children);
    });
  }
  flatten(dataSource);

  return (
    <Transfer
      {...restProps}
      targetKeys={targetKeys}
      dataSource={transferDataSource}
      className="tree-transfer"
      render={item => item.title}
      showSelectAll={false}
    >
      {({ direction, onItemSelect, selectedKeys }) => {
        if (direction === 'left') {
          const checkedKeys = [...selectedKeys, ...targetKeys];
          return (
            <Tree
              blockNode
              checkable
              checkStrictly
              defaultExpandAll
              checkedKeys={checkedKeys}
              treeData={generateTree(dataSource, targetKeys)}
              onCheck={(_, { node: { key } }) => {
                onItemSelect(key, !isChecked(checkedKeys, key));
              }}
              onSelect={(_, { node: { key } }) => {
                onItemSelect(key, !isChecked(checkedKeys, key));
              }}
            />
          );
        }
      }}
    </Transfer>
  );
};

const treeData = [
  { key: '0-0', title: '0-0' },
  {
    key: '0-1',
    title: '0-1',
    children: [
      { key: '0-1-0', title: '0-1-0' },
      { key: '0-1-1', title: '0-1-1' },
    ],
  },
  { key: '0-2', title: '0-3' },
];

const App = () => {
  const [targetKeys, setTargetKeys] = useState([]);
  const onChange = keys => {
    setTargetKeys(keys);
  };
  return <TreeTransfer dataSource={treeData} targetKeys={targetKeys} onChange={onChange} />;
};

ReactDOM.render(<App />, document.getElementById('container'));

데모 버전이고, 이미지는 이렇다.

image-20220307225006905


여기서 OneWay, Search 및 필요한 기능들을 추가, checkStrictly 를 해제하는 등 틀을 구현할 때 큰 문제는 없었으나 트리를 전체선택할 때의 문제가 있었다.

내가 생각한 기능은 전체 선택을 누르면 선택된 부모노드를 제외한 하위 자식노드들만 우측으로 넘어가길 원했었는데 여기서는 부모노드를 포함했고 심지어 우측으로 부모노드만 넘어갔다.

onSelect 하나만으로는 모든 선택을 담기가 어려웠다.

onSelectAll 을 추가하여 기능을 구현했다. 하지만 부모노드가 포함되는 것은 어쩔 수 없었다.

해결하기 위해 우측으로 넘어가는 targetKeys 를 결정하는 onChange 함수를 추가하고 그 onChange 내부에서 체크 시 부모노드만 골라가져와 필터링 해줬다.


const onChange = (keys) => {
    const resultKeys = keys.filter((ele) => filterKey.includes(ele));
    setTargetKeys(resultKeys.length === 0 ? keys : resultKeys);
  };
// 여기서 filterKey 는 <Tree> 내부 oncheck 안에서 구현.⇓⇓
...
onCheck={(allKey, info) => {
  const filteredParentNode = filteredItems.filter(ele => ele.hasOwnProperty('children') === true);
  setFilterKey(allKey.filter((ele) => !filteredParentNode.map(ele => ele.key).includes(ele)));

이후, 개별 선택 혹은 전체 선택 시 이전 선택아이템들과 비교하여 true/false 를 반환하는 isChecked, isCheckedAll 내부에서 마지막 return 하는 부분을 수정, 추가했다.

const isChecked = (selectedKeys, eventKey) => {
    //? 하나씩 클릭해서 넣고 빼니까 indexOf 사용해서 비교 가능.
    return selectedKeys.indexOf(eventKey) !== -1;
  };

  const isCheckedAll = (selectedKeys, eventKey) => {
    //? JSON.stringify() 로는 배열의 순서가 다르면 비교 불가. filter-includes 를 이용해 배열간의 차이가 하나도 없으면 true 를 반환.
    return eventKey.filter(selected => !selectedKeys.includes(selected)).length === 0;
  };

Tree 내부 onCheck 에서 isChecked 와 isCheckedAll 이 조건에 감싸여있지않아, 충돌이 계속 났다.

그래서 자식노드가 있을 때(전체 선택 시, 하위 자식노드가 존재하므로), 자식노드가 없을 때(개별선택 시, 자식 노드가 없으므로) 를 나누어 조건 처리해주었고 충돌은 없어졌다.

//하나 선택 시,
if (!childrenNode.length) onItemSelect(nodeKey, !isChecked(checkedKeys, nodeKey));
//전체 선택 시,
else onItemSelectAll(childrenNode.map((ele) => ele.key), !isCheckedAll(checkedKeys.filter(ele => childrenNode.map(ele => ele.key).includes(ele)), childrenNode.map((ele) => ele.key)));

0개의 댓글