Vue Grid Layoutで座席表を作ってみた

こんにちはWebCrewバッ苦エンドエンジニア1年目の久保田です。
今日は社内の座席方をいろいろあって作ることになり、その際に使ったVue Grid Layoutが面白かったので記事に残してみます。

# Vue Grid Layoutとは
まずVue Grid Layoutとはなんぞやって話ですが、とりあえずgridで組んだオブジェクトを自由にドラッグ&ドロップで動かしたらサイズを変えたりできるVueのライブラリです。便利ですね。
物は試しなので、とりあえずリンク先でいろいろ動かしてみてください。

https://jbaysolutions.github.io/vue-grid-layout/guide/01-basic.html

サイズを変えたりできるのはもちろんですが、staticにして一部だけ不可変に設定することもできます。

# 実装方法
使い方も簡単で、VueのCLI立ち上げて下のコードpackage.jsonに記載するだけです。
```Vue.js:package.json
    "vue-grid-layout": "^2.4.0"
```

これで各コンポーネントで使えるようになります。
今回は簡単な座席表っぽいものを作ってみます。
コンポーネントはなんでもいいですが、お試しなのでHelloWordl.vueにそのまま書いていきます。

基本的な書き方は以下の通りです。
```Vue.js:HelloWorld.vue
<template>
  <div class="hello">
    <grid-layout :layout.sync="layout"
                :col-num="24"
                :row-height="48"
                :is-draggable="draggable"
                :is-resizable="resizable"
                :vertical-compact="true"
                :use-css-transforms="true"
    >
        <grid-item v-for="item in layout"
                  :key="item"
                  :static="item.static"
                  :x="item.x"
                  :y="item.y"
                  :w="item.w"
                  :h="item.h"
                  :i="item.i"
        >
            <span class="text">{{itemTitle(item)}}</span>
        </grid-item>
    </grid-layout>
  </div>
</template>

<script>
import { GridLayout, GridItem } from "vue-grid-layout"


export default {
  components: {
      GridLayout,
      GridItem
  },
  data() {
      return {
          layout: [
              {"x":1,"y":1,"w":1,"h":1,"i":"0", static: false}
,
          draggable: true,
          resizable: true,
          index: 0
      }
  },
  methods: {
      itemTitle(item) {
          let result = item.i;
          if (item.static) {
              result += " - Static";
          }
          return result;
      }
  }
}
</script>

<style scoped>
html {
  margin: 0;
  padding: 0;
}
.hello {
  height: 100vh;
}

.vue-grid-layout {
  background: #eee;
  height: 100;
}

.vue-grid-item:not(.vue-grid-placeholder) {
  background: #ccc;
  border: 1px solid black;
}

.vue-grid-item .resizing {
  opacity: 0.9;
}

.vue-grid-item .static {
  background: #cce;
}

.vue-grid-item .text {
  font-size: 24px;
  text-align: center;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  height: 100%;
  width: 100%;
}

.vue-grid-item .no-drag {
  height: 100%;
  width: 100%;
}

.vue-grid-item .minMax {
  font-size: 12px;
}

.vue-grid-item .add {
  cursor: pointer;
}

.vue-draggable-handle {
  position: absolute;
  width: 20px;
  height: 20px;
  top: 0;
  left: 0;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
  background-position: bottom right;
  padding: 0 8px 8px 0;
  background-repeat: no-repeat;
  background-origin: content-box;
  box-sizing: border-box;
  cursor: pointer;
}
</style>
```

これだけでとりあえずオブジェクトを一つ作る事ができます。
dataの中身を増やしていくことでオブジェクトの数を増やす事が可能です。
便利な点としては、ハッシュでx,yの位置やw.hといったサイズを書くだけで簡単に実装できる点です。また、staticやdeaggableも個別に付け加えることで実装できるのでv-for内で完結します。
便利ですね。

主なプロパティについても記載しておきます。

|プロパティ|値|効果|
|---|---|---|
|x|int|x軸の位置|
|y|int|y軸の位置|
|w|int|横幅|
|h|int|縦幅|
|static|boolean|他のオブジェクトの変更も含めて動かせるかどうか|
|draggable|boolean|ドラッグできるか否か|
|resizable|boolean|サイズ変更できるか否か|

だいたいこんな感じです、めちゃくちゃ簡単です。

最後に簡単な座席表作って終わります。

```:Vue.js:HelloWorld.vue
<template>
  <div class="hello">
    <grid-layout :layout.sync="layout"
                :col-num="24"
                :row-height="48"
                :is-draggable="draggable"
                :is-resizable="resizable"
                :vertical-compact="true"
                :use-css-transforms="true"
    >
        <grid-item v-for="item in layout"
                  :key="item"
                  :static="item.static"
                  :x="item.x"
                  :y="item.y"
                  :w="item.w"
                  :h="item.h"
                  :i="item.i"
        >
            <span class="text">{{itemTitle(item)}}</span>
        </grid-item>
    </grid-layout>
  </div>
</template>

<script>
import { GridLayout, GridItem } from "vue-grid-layout"


export default {
  components: {
      GridLayout,
      GridItem
  },
  data() {
      return {
          layout: [
              {"x":1,"y":1,"w":1,"h":1,"i":"0", static: false},
              {"x":8,"y":1,"w":1,"h":1,"i":"1", static: false},
              {"x":12,"y":1,"w":1,"h":1,"i":"2", static: false},
              {"x":16,"y":1,"w":1,"h":1,"i":"3", static: false},
              {"x":19,"y":1,"w":1,"h":1,"i":"4", static: false},
              {"x":20,"y":1,"w":1,"h":1,"i":"5", static: false},
              {"x":19,"y":1,"w":1,"h":1,"i":"6", static: false},
              {"x":20,"y":1,"w":1,"h":1,"i":"7", static: false},
              {"x":1,"y":2,"w":23,"h":1,"i":"99", static: true},
              {"x":1.5,"y":4,"w":1,"h":1,"i":"8", static: false},
              {"x":1,"y":5,"w":1,"h":1,"i":"9", static: false},
              {"x":1,"y":6,"w":1,"h":1,"i":"10", static: false},
              {"x":1,"y":7,"w":1,"h":1,"i":"11", static: false},
              {"x":1,"y":7,"w":1,"h":1,"i":"12", static: false},
              {"x":2,"y":5,"w":1,"h":1,"i":"13", static: false},
              {"x":2,"y":6,"w":1,"h":1,"i":"14", static: false},
              {"x":2,"y":7,"w":1,"h":1,"i":"15", static: false},
              {"x":2,"y":7,"w":1,"h":1,"i":"16", static: false},
              {"x":4,"y":4,"w":1,"h":1,"i":"17", static: false},
              {"x":4,"y":4,"w":1,"h":1,"i":"18", static: false},
              {"x":4,"y":5,"w":1,"h":1,"i":"19", static: false},
              {"x":4,"y":6,"w":1,"h":1,"i":"20", static: false},
              {"x":4,"y":7,"w":1,"h":1,"i":"21", static: false},
              {"x":5,"y":7,"w":1,"h":1,"i":"22", static: false},
              {"x":5,"y":5,"w":1,"h":1,"i":"23", static: false},
              {"x":5,"y":6,"w":1,"h":1,"i":"24", static: false},
              {"x":5,"y":7,"w":1,"h":1,"i":"25", static: false},
              {"x":5,"y":7,"w":1,"h":1,"i":"26", static: false},
              {"x":7,"y":3,"w":1,"h":1,"i":"27", static: false},
              {"x":7,"y":4,"w":1,"h":1,"i":"28", static: false},
              {"x":7,"y":5,"w":1,"h":1,"i":"29", static: false},
              {"x":7,"y":6,"w":1,"h":1,"i":"30", static: false},
              {"x":7,"y":7,"w":1,"h":1,"i":"31", static: false},
              {"x":8,"y":3,"w":1,"h":1,"i":"32", static: false},
              {"x":8,"y":4,"w":1,"h":1,"i":"33", static: false},
              {"x":8,"y":5,"w":1,"h":1,"i":"34", static: false},
              {"x":8,"y":6,"w":1,"h":1,"i":"35", static: false},
              {"x":8,"y":7,"w":1,"h":1,"i":"36", static: false},
              {"x":10,"y":3,"w":1,"h":1,"i":"37", static: false},
              {"x":10,"y":4,"w":1,"h":1,"i":"38", static: false},
              {"x":10,"y":5,"w":1,"h":1,"i":"39", static: false},
              {"x":10,"y":6,"w":1,"h":1,"i":"40", static: false},
              {"x":10,"y":7,"w":1,"h":1,"i":"41", static: false},
              {"x":11,"y":3,"w":1,"h":1,"i":"42", static: false},
              {"x":11,"y":4,"w":1,"h":1,"i":"43", static: false},
              {"x":11,"y":5,"w":1,"h":1,"i":"44", static: false},
              {"x":11,"y":6,"w":1,"h":1,"i":"45", static: false},
              {"x":11,"y":7,"w":1,"h":1,"i":"46", static: false},
              {"x":13,"y":3,"w":1,"h":1,"i":"47", static: false},
              {"x":13,"y":4,"w":1,"h":1,"i":"48", static: false},
              {"x":13,"y":5,"w":1,"h":1,"i":"49", static: false},
              {"x":13,"y":6,"w":1,"h":1,"i":"50", static: false},
              {"x":13,"y":7,"w":1,"h":1,"i":"51", static: false},
              {"x":14,"y":3,"w":1,"h":1,"i":"52", static: false},
              {"x":14,"y":4,"w":1,"h":1,"i":"53", static: false},
              {"x":14,"y":5,"w":1,"h":1,"i":"54", static: false},
              {"x":14,"y":6,"w":1,"h":1,"i":"55", static: false},
              {"x":14,"y":7,"w":1,"h":1,"i":"56", static: false},
              {"x":16,"y":3,"w":1,"h":1,"i":"57", static: false},
              {"x":16,"y":4,"w":1,"h":1,"i":"58", static: false},
              {"x":16,"y":5,"w":1,"h":1,"i":"59", static: false},
              {"x":16,"y":6,"w":1,"h":1,"i":"60", static: false},
              {"x":16,"y":7,"w":1,"h":1,"i":"61", static: false},
              {"x":17,"y":3,"w":1,"h":1,"i":"62", static: false},
              {"x":17,"y":4,"w":1,"h":1,"i":"63", static: false},
              {"x":17,"y":5,"w":1,"h":1,"i":"64", static: false},
              {"x":17,"y":6,"w":1,"h":1,"i":"65", static: false},
              {"x":17,"y":7,"w":1,"h":1,"i":"66", static: false},
              {"x":19,"y":3,"w":1,"h":1,"i":"67", static: false},
              {"x":19,"y":4,"w":1,"h":1,"i":"68", static: false},
              {"x":19,"y":5,"w":1,"h":1,"i":"69", static: false},
              {"x":19,"y":6,"w":1,"h":1,"i":"70", static: false},
              {"x":19,"y":7,"w":1,"h":1,"i":"71", static: false},
              {"x":20,"y":3,"w":1,"h":1,"i":"72", static: false},
              {"x":20,"y":4,"w":1,"h":1,"i":"73", static: false},
              {"x":20,"y":5,"w":1,"h":1,"i":"74", static: false},
              {"x":20,"y":6,"w":1,"h":1,"i":"75", static: false},
              {"x":20,"y":7,"w":1,"h":1,"i":"76", static: false},
              {"x":22,"y":3,"w":1,"h":1,"i":"77", static: false},
              {"x":22,"y":4,"w":1,"h":1,"i":"78", static: false},
              {"x":22,"y":5,"w":1,"h":1,"i":"79", static: false},
              {"x":22,"y":6,"w":1,"h":1,"i":"80", static: false},
              {"x":22,"y":7,"w":1,"h":1,"i":"81", static: false},
              {"x":23,"y":3,"w":1,"h":1,"i":"82", static: false},
              {"x":23,"y":4,"w":1,"h":1,"i":"83", static: false},
              {"x":23,"y":5,"w":1,"h":1,"i":"84", static: false},
              {"x":23,"y":6,"w":1,"h":1,"i":"85", static: false},
              {"x":23,"y":7,"w":1,"h":1,"i":"86", static: false},
              {"x":1,"y":8,"w":23,"h":1,"i":"100", 

          ],
          draggable: true,
          resizable: true,
          index: 0
      }
  },
  methods: {
      itemTitle(item) {
          let result = item.i;
          if (item.static) {
              result += " - Static";
          }
          return result;
      }
  }
}
</script>

<style scoped>
html {
  margin: 0;
  padding: 0;
}
.hello {
  height: 100vh;
}

.vue-grid-layout {
  background: #eee;
  height: 100;
}

.vue-grid-item:not(.vue-grid-placeholder) {
  background: #ccc;
  border: 1px solid black;
}

.vue-grid-item .resizing {
  opacity: 0.9;
}

.vue-grid-item .static {
  background: #cce;
}

.vue-grid-item .text {
  font-size: 24px;
  text-align: center;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  height: 100%;
  width: 100%;
}

.vue-grid-item .no-drag {
  height: 100%;
  width: 100%;
}

.vue-grid-item .minMax {
  font-size: 12px;
}

.vue-grid-item .add {
  cursor: pointer;
}

.vue-draggable-handle {
  position: absolute;
  width: 20px;
  height: 20px;
  top: 0;
  left: 0;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
  background-position: bottom right;
  padding: 0 8px 8px 0;
  background-repeat: no-repeat;
  background-origin: content-box;
  box-sizing: border-box;
  cursor: pointer;
}


</style>
```

以上です、Vueには他にもドラッグ&ドロップできるライブラリいくつかあるみたいなので試してみたいと思います。
ありがとうございました。