본문 바로가기

3.구현/HTML5&Javascript

[vue2] vue.draggable 사용하기와 v-treeview 적용 검토

들어가기

v-treeview에서 드래그앤드롭을 구현하기 위해 vue.draggable을 사용을 간단하게 검토해보았다.
일단 vue.draggable 사용법을 살펴보고 v-treeview을 적용을 해보았다. 결론을 먼저 말하면 v-treeview 적용 검토결과 제대로 적용되지 않았다.

작성자: ospace114@empal.com, http://ospace.tistory.com/

vue.draggable 사용하기

vue.draggable은 vue에서 손쉽게 드래그 앤 드롭 작업을 구현하기 위한 모듈이다.
먼저 헤더 내용을 작성하자.

<head>
<link
  href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"
  rel="stylesheet"
/>
<link
  href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css"
  rel="stylesheet"
/>
<link
  href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css"
  rel="stylesheet"
/>
<meta
  name="viewport"
  content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"
/>
<style>
  .item {
    padding: 10px;
    border: solid 1px;
    text-align: center;
  }
</style>
</head>

다음으로 vue.draggable로 화면에 표시할 간단한 구조를 만들어보자. 목록을 표시하고 목록간에 드래드 앤 드롭을 하는 예제이다.

<div id="app">
  <v-app>
    <v-main>
      <draggable v-model="myArray" group="people">
        <div class="item" v-for="(item,idx) in myArray" :key="idx">{{item}}</div>
      </draggable>
    </v-main>
  </v-app>
</div>

사용할 라이브러리을 포함시킨다. vue.js, vuetify, sortable, draggable이다.

<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

실제 동작시켜보자. 별다른게 없이 데이터만 입력해주면 된다. 나머지는 vue.draggable에서 알아서 해준다.

new Vue({
  el: "#app",
  vuetify: new Vuetify(),
  data() {
    return {
      myArray: ["aaa", "bbb", "ccc"],
    };
  },
});

전체 코드이다.

<html>
    <head>
        <title>test15</title>
        <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet" />
        <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet" />
        <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet" />
        <meta
            name="viewport"
            content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui" />
    </head>
    <body style="background: lightgray">
        <div id="app">
            <v-app>
                <v-main>
                    <draggable v-model="myArray" group="people">
                        <div v-for="(item,idx) in myArray" :key="idx">{{item}}</div>
                    </draggable>
                </v-main>
            </v-app>
        </div>

        <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
        <script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

        <script>
            new Vue({
                el: '#app',
                vuetify: new Vuetify(),
                data() {
                    return {
                        myArray: ['aaa', 'bbb', 'ccc'],
                    };
                },
            });
        </script>
    </body>
</html>

v-treeview 적용 검토

이제 최종 목적지인 v-treeview에 적용해보자. 이전 예제처럼 graggable 태그로 v-treeview을 감쌀수는 없다. 감싸는 영역에 한 개의 요소만 있기 때문이다. 즉, v-treeview의 각 항목을 찾아서 draggable을 적용할려고 한다.

이제는 드래그와 드롭 영역이 같이 있지 않고 별도로 있게된다. 아래 예제에 보이듯이 template에 반복되는 영역에 draggable이 있다. v-treeview에서 각 항목이 표시되는 곳인데 항목마다 draggable로 감싸고 있다. 아이템 개수만큼 생성된다.

<div id="app">
  <v-app>
    <v-main>
      <label>{{active}}</label>
      <v-treeview activatable open-all :active.sync="active" :items="items">
        <template v-slot:prepend="{ item, open }">
          <draggable v-model="items" group="node">
            <v-icon
              v-if="!!item.children"
              v-text="open ? 'mdi-folder-open' : 'mdi-folder'"
            />
            <button slot="footer">!!!</button>
          </draggable>
        </template>
      </v-treeview>
    </v-main>
  </v-app>
</div>

실행시켜보자.

new Vue({
  el: "#app",
  vuetify: new Vuetify(),
  data() {
    return {
      active: [],
      open: [],
      selectedItems: [],
      items: [
        {
          id: 1,
          name: "Applications :",
          children: [
            { id: 2, name: "Calendar : app" },
            { id: 3, name: "Chrome : app" },
            { id: 4, name: "Webstorm : app" },
          ],
        },
        {
          id: 5,
          name: "Documents :",
          children: [
            {
              id: 6,
              name: "vuetify :",
              children: [
                {
                  id: 7,
                  name: "src :",
                  children: [
                    { id: 8, name: "index : ts" },
                    { id: 9, name: "bootstrap : ts" },
                  ],
                },
              ],
            },
          ],
        },
        {
          id: 15,
          name: "Downloads :",
          children: [
            { id: 16, name: "October : pdf" },
            { id: 17, name: "November : pdf" },
            { id: 18, name: "Tutorial : html" },
          ],
        },
      ],
    };
  },
  computed: {
    selected() {
      if (!this.active.length) return undefined;
      return this.active[0];
    },
  },
});

아래 코드는 v-treeview에서 트리의 각항목에 마우스 호버링시 하이라이트 표시해준다.

let viewtree = document.querySelector(".v-treeview");
let target = null;
viewtree.addEventListener("mousemove", (ev) => {
  let found = ev.target;
  do {
    if (found.classList.contains("v-treeview-node__content"))
      break;
  } while ((found = found.parentNode));
  if (target === found) return;
  if (target) {
    target.style.backgroundColor = "";
  }

  if (found) {
    found.style.backgroundColor = "aliceblue";
  }
  target = found;
  this.pos = {
    clientX: ev.clientX,
    clientY: ev.clientY,
    layerX: ev.layerX,
    layerY: ev.layerY,
    movementX: ev.movementX,
    movementY: ev.movementY,
    offsetX: ev.offsetX,
    offsetY: ev.offsetY,
    pageX: ev.pageX,
    pageY: ev.pageY,
  };
});

추가로 v-treeview에서 드래그할 때에 드롭할 위치를 표시해주는 가이드용 태그이다.

<!--line-->
<span style="background-color: #0000ff; height: 2px; padding: 0; position: absolute; top: -1px; left: 2px; width: 100%;"/>
<!--circle-->
<span style="border: solid 2px #0000ff; border-radius: 100px; height: 8px; width: 8px; position: absolute; top: -4px; left: -6px; box-sizing: border-box;"/>

아래는 만들어진 v-treeview의 트리구조에서 각항목들을 추출하는 함쉬다.

function getTreeData() {
    let viewtree = document.querySelector(".v-treeview");
  if (!viewtree || !viewtree.children.length) return;
  let data = [];
  (function iterate(parent, node, depth) {
    if (node.classList.contains("v-treeview-node__root")) {
      parent.push(node.querySelector(".v-treeview-node__content"));
    } else if (node.classList.contains("v-treeview-node__children")) {
      let ret = [];
      Array.prototype.forEach.call(node.children, (each) => {
        iterate(ret, each, depth + 1);
      });
      parent.push(ret);
    } else {
      Array.prototype.forEach.call(node.children, (each) => {
        iterate(parent, each, depth + 1);
      });
    }
  })(data, viewtree, 0);

    return data;
}

검토 결론

결론적으로 말하면 v-treeview에 vue.draggable은 잘 작동하지 않는다.

v-treeview에 적용하면서 느낀점

  • v-treeview가 아닌 단일 컴포넌트나 v-for에 의해서 생성된 대상에 대해서는 잘 적용됨.
  • v-for가 아닌(render 사용) 경우 목록 및 nested 형식에 대해서 이상하게 적용됨.
  • 직접 드래그 가능한 대상을 선택하기 어려움(정해진 위치만 가능)
  • 드롭한 경우 화면 dom 객체가 직접 이동됨(데이터 변경은 없음)
    • draggable에 의해서 dom 이동/복제를 직접 처리
    • 추가로 데이터도 같이 처리되는데 제대로 이동되지 않음.
    • 한 요소가 여러 slot으로 구성된 경우 한 개 slot만 이동 가능. 상위 객체 지정 불가능.
  • 이벤트를 통해 직접 데이터 이동 처리하는 경우 데이터 이동도 잘되지만, 객체 표시할 때에 이상하게 표시되고 콘솔창에 경고 문구출력된다. 즉, 이동하는데 문제가 있다.

부족한 글이지만 여러분에서 도움이 되었으면 합니다. 즐프하세요. ospace.

반응형