본문 바로가기
JavaScript

[javascript] Table의 Row요소 drag&drop

by teamnova 2023. 4. 25.

안녕하세요. 이전에 자바스크립트로 drag&drop을 구현해본 적 있는데요, 오늘은 테이블의 row를 drag&drop으로 옮기는 것을 구현해보려고 합니다. 

 

 

 

dragableTable.html

<table>
  <thead>
    <tr>
      <th>이름</th>
      <th>나이</th>
      <th>성별</th>
    </tr>
  </thead>
  <tbody>
    <tr id="1">
      <td>홍길동</td>
      <td>20</td>
      <td>남</td>
    </tr>
    <tr id="2">
      <td>이순신</td>
      <td>35</td>
      <td>남</td>
    </tr>
    <tr id="3">
      <td>유관순</td>
      <td>17</td>
      <td>여</td>
    </tr>
    <tr id="4">
      <td>강감찬</td>
      <td>45</td>
      <td>남</td>
    </tr>
  </tbody>
</table>

 

 

dragableTable.css

table {
  padding:10px;
  border-collapse: collapse;
  width: 50%;
  margin:auto;
}

th,
td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}
/*옮길때 불투명으로 보이는 부분*/
tr.dragging {
  opacity: 0.5;
}

 

 

dragableTable.js


// 드래그 앤 드롭을 위한 이벤트 리스너 등록
document.addEventListener('DOMContentLoaded', () => {

    // tbody 요소의 모든 자식 요소 중 tr 요소를 선택하여 rows 변수에 할당
    const rows = document.querySelectorAll('tbody > tr');
    // rows에 forEach 메서드를 호출하여 각각의 tr 요소에 대해 드래그 이벤트 리스너를 등록
    rows.forEach(row => {
    // 드래그가 가능하도록 draggable속성을 true로 설정
    row.draggable = true;
    // 드래그 이벤트가 시작될 때 실행할 콜백 함수 등록
    row.addEventListener('dragstart', e => {
    
    //drag와 관련된 이벤트에서는 모두 DataTransfer 객체를 사용할 수 있다.
    //dataTransfer 객체가 setData()함수를 제공.
    //setData():데이터를 드래그할 때 전달하고자 하는 정보를 설정하는 메서드. 
    //첫 번째 인자로 데이터의 형식을 지정할 수 있으며, 두 번째 인자로 전달하려는 값(value)을 지정.
    // setData 메서드를 호출하여 드래그 데이터 설정 - 데이터 형식은 text/plain, 값은 해당 tr 요소의 id 값.
    //이렇게 설정된 드래그 데이터는 드래그 이벤트가 발생할 때 dataTransfer 객체에 저장되어 드롭 이벤트가 발생할 때 사용된다.
    e.dataTransfer.setData('text/plain', row.id);

	});
});

// tbody 요소에 드롭 이벤트 리스너 등록
const dropzone = document.querySelector('tbody');

dropzone.addEventListener('dragover', e => {

    // 기본 이벤트 동작을 막음
    e.preventDefault();
    
    // 드래그된 요소를 드롭할 위치를 계산하는 함수를 호출하여 그 결과값을 afterElement 변수에 할당
    const afterElement = getDragAfterElement(dropzone, e.clientY);
    
    // dragging 클래스를 가진 요소를 선택하여 draggable 변수에 할당
    const draggable = document.querySelector('.dragging');
    
    // afterElement가 null일 경우, 마지막 자식 요소로 추가하고 그렇지 않은 경우, afterElement 바로 전 위치에 추가
    if (afterElement == null) {
    	dropzone.appendChild(draggable);
    } else {
    	dropzone.insertBefore(draggable, afterElement);
    }
});

// 드롭 대상 행 중 이동할 위치를 결정하는 함수
function getDragAfterElement(container, y) {

    // container 내의 모든 tr 요소들 중 dragging 클래스를 가지지 않은 요소들을 선택하여 draggableElements 변수에 할당
    const draggableElements = [...container.querySelectorAll('tr:not(.dragging)')];
    
    // draggableElements 배열을 reduce 메서드를 이용하여 y값에 따라 가장 근접한 요소를 찾아낸다.
    return draggableElements.reduce((closest, child) => {

	const box = child.getBoundingClientRect();
    
	// 드래그 위치 - 드롭 대상 요소의 top 위치 - 드롭 대상 요소의 높이 / 2 값을 offset 변수에 할당
	const offset = y - box.top - box.height / 2;
    
	// offset 값이 음수이고 closest.offset보다 더 큰 경우, closest를 현재 child 요소로 대체
        if (offset < 0 && offset > closest.offset) {
            return { offset: offset, element: child };
        } else {
            return closest;
        }
	}, { offset: Number.NEGATIVE_INFINITY }).element;
    // reduce 함수에서 초기 값으로 사용되는 객체. 
    //offset 속성은 "-Infinity"로 설정하여, reduce 함수가 실행되면서 만들어지는 첫 closest.offset 값이 항상 offset보다 크도록 보장한다.
}

// 드래그 중인 행의 스타일 설정.
//'dragging' 클래스는 드래그 중인 요소를 스타일링하기 위해 사용되므로 드래그가 종료되면 스타일링 제거
// 어떤 요소에서든지 드래그 이벤트가 발생하면 실행됨
document.addEventListener('dragend', e => {

  // 'dragend' 이벤트가 발생한 요소 중 가장 가까운 <tr> 요소를 찾아서
  const draggable = e.target.closest('tr');
  // 해당 <tr> 요소의 'dragging' 클래스를 제거하기
  draggable.classList.remove('dragging');
  
});

 

 

위 코드로 완성되면 아래와 같이 동작하는 것 확인 할 수 있습니다. 

 

 

-자세한 내용은 아래 링크 참고 바랍니다. 

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragstart_event

 

HTMLElement: dragstart event - Web APIs | MDN

The dragstart event is fired when the user starts dragging an element or text selection.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/HTMLElement/dragend_event

 

Document: dragend 이벤트 - Web API | MDN

dragend 이벤트는 마우스 버튼을 놓거나 이스케이프 키를 눌러 드래그 작업이 끝나게 되면 발생합니다.

developer.mozilla.org

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragover_event

 

HTMLElement: dragover event - Web APIs | MDN

The dragover event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds).

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

 

Array.prototype.reduce() - JavaScript | MDN

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/Element/getBoundingClientRect

 

Element.getBoundingClientRect() - Web API | MDN

Element.getBoundingClientRect() 메서드는 엘리먼트의 크기와 뷰포트에 상대적인 위치 정보를 제공하는 DOMRect 객체를 반환합니다.

developer.mozilla.org

https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/setData

 

DataTransfer.setData() - Web APIs | MDN

The DataTransfer.setData() method sets the drag operation's drag data to the specified data and type. If data for the given type does not exist, it is added at the end of the drag data store, such that the last item in the types list will be the new type.

developer.mozilla.org