대시 보드 내에서 각각 아이템을 드래그 해서 옮기는 업무를 맡게 되었다.
일단 drag 함수에 적응하기 위해서 더미 데이터로 테스트를 해보고 공부한것을 가지고 정리해본다.
드래그 라이브러리도 있었지만 왠지 내힘으로 한번 해보고 싶어서 DragEvent를 활용하게 되었다.
이벤트는 아래와 같다
dragstart 사용자가 객체(object)를 드래그하려고 시작할 때 발생.
dragenter 마우스가 대상 객체의 위로 처음 진입할 때 발생함. <- 아이템 위로 올라갔을때 진입할때 한번 실행!
dragover 드래그하면서 마우스가 대상 객체의 위에 자리 잡고 있을 때 발생함.<- 마우스 움직일때마다 함수가 실행된다 유의!
drag 대상 객체를 드래그하면서 마우스를 움직일 때 발생함.
drop 드래그가 끝나서 드래그하던 객체를 놓는 장소에 위치한 객체에서 발생함.
dragleave 드래그가 끝나서 마우스가 대상 객체의 위에서 벗어날 때 발생함.
dragend 대상 객체를 드래그하다가 마우스 버튼을 놓는 순간 발생함.
처음에는 dragover를 사용했지만 이게 드래그 시작부터 올라갈때까지 마우스가 움직일때마다 함수가 실행되서 무리를 주지 않고자 진입할때 한번 호출되는 dragenter를 사용했다.
내가 도움 받은 사이트에서는
e.dataTransfer.setData() 를 활용하면서 데이터 값 고유 id를 저장했는데 나는 저장은 되었는데 이동할때 호출하고자 했을때 호출이 정확하게 되지 않았다. undefiend가 뜰때가 있었다. 그래서 리액트의 렌더링에 영향을 미치지 않는 useRef를 사용해서 값들을 저장했다.
파라미터 는 이벤트 값을 받고 두번째는 옵션값을 받는다. 나는 고유 id값을 두번째 인자로 넘겨주었다.
const onDragStart = (e: React.DragEvent<HTMLDivElement>, id: string) => {
e.dataTransfer.effectAllowed = "move"; // 이동을 허용한다는 옵션
// copy : 드래그 중인 데이터가 현재 위치에서 드롭위치로 복사됨을 나타내는데 사용된다.
// move : 드래그 중인 데이터가 이동됨을 나타내는데 사용된다.
// link : source와 드롭 위치 사이에 어떤 형태의 관계나 연결이 생성될 것임을 나타내는데 사용된다.
draggingID.current = id; // 나는 useRef를 사용해서 id 를 저장했다.
};
함수 이름을 처음에 dragover로 쓰려고 네이밍했지만 마우스가 움직일때마다 함수가 호출되어서 진입할때만 함수 호출이 되는 dragenter로 사용할것이다. 첫번째 파라미터는 DragEvent를 받고 두번째 파라미터는 이동한 객체 id를 받는다.(내가 드래그 하는 객체가 아니고 over되었을때 아래 객체 값)
내가 이동할 아이템이 어디에 위치해야 할지 보여주기 위해서 temp라는 작업을 둬서 over된 값들을 뒤로 밀어주고 그 위치에 자리를 표시해두는 temp를 만들었다
const onDragOver = (e: React.DragEvent, overid: string) => {
e.preventDefault();
if (nowPositionId.current !== overid) {
nowPositionId.current = overid; // useRef를 사용해서 오버된 값을 저장한다.
} else {
return;
}
if (!draggingID.current || nowPositionId.current === draggingID.current)
return;
const findOverItemIdx = boardList.findIndex((item) => item.id === overid); //dashboard아이템 중에서 over된 아이템 값을 찾는다.
let temp = { // 테스트 하기 위해서 임시로 만든 임시 위치 지정값 옮겨질 위치를 볼수 있게 한다.
contents: (
<div className="border-2 border-yellow-500 min-w-96 min-h-96">
이곳으로 자리 생성할거야
</div>
),
id: "temp",
};
// 위에서 찾은 오버된 아이템을 뒤로 밀어주고 temp값을 넣어주면서 위치될 곳을 나타내게 한다.
setBoardList((prev) => {
let newList = prev.filter((item) => item.id !== "temp");
if (findOverItemIdx === 0) {
newList = [temp, ...newList];
} else if (findOverItemIdx === prev.length) {
newList = [...newList, temp];
} else {
let beforeArr = newList.slice(0, findOverItemIdx);
let afterArr = newList.slice(findOverItemIdx);
newList = [...beforeArr, temp, ...afterArr];
}
return newList;
});
};
드롭할때 실행되는 이벤트이다. 첫번째 파라메터로는 DragEvent를 받는다.
드롭할때 temp를 드롭된 값으로 변경해준다.
const onDragDrop = (e: React.DragEvent) => {
e.preventDefault();
if (draggingID.current === nowPositionId.current) return; // 옮기기 전과 같은 위치라면 리턴
const dragContents = boardList.filter(
(item) => item?.id === draggingID.current
)[0]; // 드래그 아이템을 찾아준다.
setBoardList((prev) =>
prev
.map((item) => {
if (item.id === "temp") {
return dragContents; // temp위치에 드래그 아이템으로 변경해준다.
}
if (item.id === draggingID.current) {
return null; //이전 아이템은 null값으로 변경
}
return item;
})
.filter((item) => item !== null) //null값은 에러가 나기 때문에 없애준다.
);
};
const container = [
{
contents: (
<div className="min-w-96 min-h-96 overflow-y-scroll border-2 border-black">
item-1
</div>
),
id: "item-1",
},
{
contents: (
<div className="min-w-96 min-h-96 overflow-y-scroll border-2 border-black">
item-2
</div>
),
id: "item-2",
},
{
contents: (
<div className="min-w-96 min-h-96 overflow-y-scroll border-2 border-black">
item-3
</div>
),
id: "item-3",
},
];
const [boardList, setBoardList] = useState(container);
const draggingID = useRef<null | string>(null);
const nowPositionId = useRef<null | string>(null);
const onDragStart = (e: React.DragEvent<HTMLDivElement>, id: string) => {
e.dataTransfer.effectAllowed = "move";
// e.dataTransfer.setData("ContainerId", id);
draggingID.current = id;
};
const onDragOver = (e: React.DragEvent, overid: string) => {
e.preventDefault();
if (nowPositionId.current !== overid) {
nowPositionId.current = overid;
} else {
return;
}
if (!draggingID.current || nowPositionId.current === draggingID.current)
return;
const findOverItemIdx = boardList.findIndex((item) => item.id === overid);
let temp = {
contents: (
<div className="border-2 border-yellow-500 min-w-96 min-h-96">
이곳으로 자리 생성할거야
</div>
),
id: "temp",
};
setBoardList((prev) => {
let newList = prev.filter((item) => item.id !== "temp");
if (findOverItemIdx === 0) {
newList = [temp, ...newList];
} else if (findOverItemIdx === prev.length) {
newList = [...newList, temp];
} else {
let beforeArr = newList.slice(0, findOverItemIdx);
let afterArr = newList.slice(findOverItemIdx);
newList = [...beforeArr, temp, ...afterArr];
}
return newList;
});
};
const onDragDrop = (e: React.DragEvent, id: string) => {
e.preventDefault();
if (draggingID.current === nowPositionId.current) return;
const dragContents = boardList.filter(
(item) => item?.id === draggingID.current
)[0];
setBoardList((prev) =>
prev
.map((item) => {
if (item.id === "temp") {
return dragContents;
}
if (item.id === draggingID.current) {
return null;
}
return item;
})
.filter((item) => item !== null)
);
};
<section className="grid auto-cols-[minmax(24rem,auto)] md:grid-cols-2 lg:grid-cols-3 gap-4">
{boardList &&
boardList.map((item) => {
if (!item) {
return;
}
return (
<div
className="min-w-96 overflow-y-scroll border-2 border-black"
onDragEnter={(e) => onDragOver(e, item.id)}
onDragEnd={(e) => onDragDrop(e, item.id)}
>
<div
className="cursor-pointer"
onDragStart={(e) => onDragStart(e, item.id)}
onDrop={(e) => onDragDrop(e, item.id)}
draggable
>
옮기기
</div>
<div>{item.contents}</div>
</div>
);
})}
</section>