B.TIL 10 : Drag&Drop

김기욱·2020년 12월 8일
1

B.TIL

목록 보기
11/15

draggable

드래그 앤 드롭은 HTML이나 CSS부터 바닐라자바스크립트로도 구현 할 수 있는 액션입니다.
하지만 vue를 쓸 때는 draggable 라이브러리를 추천드리고싶습니다.

draggable 라이브러리는 다음과 같은 강점을 가지고 있습니다.

첫째. 간편합니다.
굉장히 간단한 방식으로 드래그앤드롭을 구현할 수 있고, drop down영역을 따로 지정할 필요가 없습니다.

둘쨰. vue친화적입니다.
v-model과 v-fot문법과 쓸 때 매우 편리합니다.

셋째. 데이터 조작이 용이합니다.
v-model과 같이 쓸 경우 바인딩된 데이터의 정렬순서가 드래그앤드롭으로 바뀐 시각적인 위치를 그대로 반영되어 데이터의 조작이 용이해집니다.

더욱 자세한 정보와 기본적인 사용법은 다음 링크를 참조하시면 됩니다.
https://github.com/SortableJS/Vue.Draggable

사용법

예제 이미지를 구현한 간단한 소스코드와 함께 실습을 해봅시다.
우선 draggable사용을 위해 npm i -S vuedraggable을 이용해 설치해주도록 합시다.
이후 import로 사용할 스크립트에 draggable을 임포트해줍니다.

HTML

<template>
<div>
  <div>
      전체 : {{totalCount}}권
  </div>
  <table id="test-table">
    <thead>
      <tr class="header-box">
        <th class='toggle'></th>
        <th class='order'>정렬순서</th>
        <th class='cstype'>타이틀</th>
        <th calss='writer'>등록자</th>
        <th class='created'>등록일시</th>
        <th class='action'>액션</th>
      </tr>
    </thead>
    <tbody>
      <draggable v-model="items" draggable=".product-box" class="drag-box"> //1번
        <tr class='product-box' v-for="item in items" :key="item.index">
          <td class="toggle">:::</td>
          <td class="first-item" ref="order-no">{{item.order_no}}</td>
          <td class="second-item" @click="doMove(item.order_no)">
            <a>{{item.title}}</a>
          </td>
          <td class="third-item">{{item.writer}}</td>
          <td class="fourth-item">{{item.created_at}}</td>
          <td class="fifth-item">
            <a href="/" class="delete-btn" @click="deleteData(item.order_no)">삭제</a>
          </td>
        </tr>
      </draggable>
    </tbody>
  </table>
</div>
</template>

javascript

<script>
import draggable from 'vuedraggable' //import잊지말고 해줘야합니다

export default {
    components : { //컴포넌트에 draggable을 반드시 추가해줘야합니다
        draggable
    },
    data() {
        return{
            "items": [
                {
                    "no": 1,
                    "order_no": 1,
                    "created_at": "2020-12-02 18:13:12",
                    "is_deleted": 0,
                    "title": "죽은시인의 사회",
                    "writer": "이현경"
                },
                {
                    "no": 2,
                    "order_no": 2,
                    "created_at": "2020-12-02 18:13:12",
                    "is_deleted": 0,
                    "title": "멋진신세계",
                    "writer": "안내상"
                },
                {
                    "no": 3,
                    "order_no": 3,
                    "created_at": "2020-12-02 18:13:12",
                    "is_deleted": 0,
                    "title": "뿌리",
                    "writer": "고준"
                },
                {
                    "no": 4,
                    "order_no": 4,
                    "created_at": "2020-12-02 18:13:12",
                    "is_deleted": 0,
                    "title": "레미제라블",
                    "writer": "정지훈"
                },
                {
                    "no": 5,
                    "order_no": 5,
                    "created_at": "2020-12-02 22:47:21",
                    "is_deleted": 0,
                    "title": "톨스토이 단편전",
                    "writer": "추자현"
                },
                {
                    "no": 6,
                    "order_no": 6,
                    "created_at": "2020-12-02 18:13:12",
                    "is_deleted": 0,
                    "title": "향수",
                    "writer": "임현식"
                },
                {
                    "no": 7,
                    "order_no": 7,
                    "created_at": "2020-12-02 18:13:12",
                    "is_deleted": 0,
                    "title": "총, 균, 쇠",
                    "writer": "천원두"
                },
                {
                    "no": 8,
                    "order_no": 8,
                    "created_at": "2020-12-03 18:16:25",
                    "is_deleted": 0,
                    "title": "반지의제왕",
                    "content": "ㅇㅇㅇㅇdsdsssddsd",
                    "writer": "경선식"
                },
                {
                    "no": 9,
                    "order_no": 9,
                    "created_at": "2020-12-03 21:56:48",
                    "is_deleted": 0,
                    "title": "동물농장",
                    "writer": "오다미"
                },
                {
                    "no": 10,
                    "order_no": 10,
                    "created_at": "2020-12-04 17:14:30",
                    "is_deleted": 0,
                    "title": "정의란무엇인가",
                    "writer": "주결경"
                }
            ],
            count: '',
        }
    },
    methods: {
        doMove(num) {
            const number = String(num)
            this.$router.push('/' + number)
        }
    },
    computed: {
        totalCount() {
            return this.items.length
        }
    },
}
</script>

실제로 제가 구현한 코드는 items를 axios를 사용해 서버에서 받아와 쓰지만 지금 같은 경우는 예제코드기 때문에 데이터를 제가 직접 만들어 쓰겠습니다.

draggable을 쓰는 순서는 다음과 같습니다.

우선 draggable을 v-for를 사용한 태그 자리 상단에 감쌉니다.
또한 v-for에 사용된 데이터를 똑같이 v-model을 사용해 바인딩해줍니다.
draggable에 실제로 드래그 될 태그를 경로를 써서 지정해줍니다.
지금같은 경우는 class명이 product-box이므로 ".product-box"로 지정해줍니다.
이 과정만 끝나면 간단하게 Drag&Drop이 구현됩니다.

데이터의 총 개수 구하기(totalCount)

this.items.length를 통해 데이터안에 있는 요소들의 숫자를 구할 수 있습니다.
computed와 같이 쓴다면 데이터 변화를 포착해서 실시간으로 반영할 수 있게됩니다.

문제점

draggable을 사용할 때 다음과 같이 기껏 설정해놓은 css가 깨져버리는 경우가 자주 발생합니다.
이는 설정해놓은 태그 위로 새로운 draggable이라는 태그가 들어오면서 css구조가 깨지기 때문에 발생하는 문제입니다.(상위에 있는 draggable고유의 속성이 하위 항목의 설정들을 무시하고 잡아먹음)

해결방법은 다음과 같습니다.

SCSS/SASS를 사용할 경우 draggable에 클래스를 지정해두고 다시 한 번 전체적인 depth를 조정해봅니다. draggable에 할당한 class(지금같은 경우는 'drag-box')에 다음과 같은 속성을 추가해줍니다.

diplay : contents

이 속성으로 인해 draggable 고유의 속성은 무시되고 하위에 존재하는 태그들의 css설정값들은 그대로 반영되게 됩니다.

tbody {
            width: 1500px;
            .drag-box{
                display:contents;

                tr{
                    td {
                    border: 1px solid #d3d7df;
                    height: 41px; 
                    padding: 8px 10px 7px 10px;
                    text-align: center;
                    font-size: 100%;
                    font-size: 13px;

                    .delete-btn{
                        padding: 6 8 6 8;
                        background-color: #EF5C4A;
                        border-color: #EF5C4A;
                        color: #fff;
                        text-decoration: none;
                        }
                    }

                    .second-item{
                        &:hover{
                        background-color: #f2f4f7;
                        cursor: Pointer;
                        }
                    }
            	}
            }
profile
어려운 것은 없다, 다만 아직 익숙치않을뿐이다.

0개의 댓글