GoとVue.jsでTrello風Webアプリケーションを作成してみた

## 作ったもの

最近、プログラミングから少し離れていたので、思い出すこともかねてTrello風のWebアプリケーションを作ってみた。

![vue-trello - Google Chrome 2020_03_17 3_05_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279498/b545e34f-a1da-e8aa-97e0-ce7653247eb2.png)

▼デモ（ https://x-color.github.io/vue-trello ）
![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/279498/7f07b984-ce2b-8db6-c8d4-c11ff41d7c34.gif)

▼完成品のリポジトリ
<a href="https://github.com/x-color/vue-trello"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/x-color/vue-trello.png" width="460px"></a>

## 使用技術

### フロントエンド

フロントエンドはSPAとなっており、Vue.jsで実装している。

- [Vue.js](https://github.com/vuejs/vue): JavaScriptフレームワーク
- [Vuex](https://github.com/vuejs/vuex): Vue.js用状態管理ライブラリ
- [Vue Router](https://github.com/vuejs/vue-router): SPA構築用のルーター
- [Vuetify](https://github.com/vuetifyjs/vuetify): Vue.jsのマテリアルデザインコンポーネントフレームワーク
- [Vue.Draggable](https://github.com/SortableJS/Vue.Draggable): ドラッグアンドドロップ処理用ライブラリ

### バックエンド

バックエンドはAPIサーバーとなっており、Go言語で実装している。

- [Go言語](https://github.com/golang/go)
- [GORM](https://github.com/jinzhu/gorm): Go言語用ORMライブラリ
- [Echo](https://github.com/labstack/echo): Go言語用Webフレームワーク
- [jwt-go](https://github.com/dgrijalva/jwt-go): Go言語用JWTを扱うライブラリ

## 実装内容（カードを動かす処理）

今回のアプリケーションで実装したカードを動かす処理の概要を以下で紹介していく。

### 基本的な実装

今回はカードを動かす処理に、Vue.Draggableを用いているため、`draggable` タグで動かしたいものを囲むだけで実装可能。
以下の例は、`fruits` 配列をドラッグアンドドロップで自由に並び替える処理。

```vue

<template>
    <draggable v-model="fruits">
       <!-- ここの要素がドラッグアンドドロップ可能になる -->
       <div v-for="(v, i) in fruits" :key="i">{{ v }}</div>
    </draggable>
</template>

<script>
import draggable from 'vuedraggable'

export default {
	components: {
		draggable,
	},
	data() {
		return {
			fruits: [
				"apple",
				"banana",
				"cherry"
			],
		}
	}
}
</script>
```

### Vuexで管理しているデータを並び替える

今回の場合は、カードのデータや並び順をVuex内で管理しているため、Vuex内のデータを並び替える必要がある。
[公式のREADME](https://github.com/SortableJS/Vue.Draggable/blob/master/README.md)に記載されている通り、`computed`内からVuexの`state`を呼び出し、Setterを用いて更新することで対応可能。
シンプルな配列を並び替えたいときは、以下のようにするだけで並び変え可能。

```vue

<template>
    <draggable v-model="list">
       <div v-for="(v, i) in list" :key="i">{{ v }}</div>
    </draggable>
</template>

<script>
import draggable from 'vuedraggable'

export default {
	components: {
		draggable,
	},
    computed: {
        list: {
            get() {
                return this.$store.state.list
            },
            set(value) {
                this.$store.commit('updateList', value)
            },
        },
    },
}
</script>
```

今回作成したアプリでは、カードを並び替えた際に一部データの更新を行う必要があったので、以下のようにメソッドを呼び出し、データの更新処理を行ったあとにデータの移動を反映させる形にした。

```js
computed: {
    lists: {
        get() {
            return this.getListsByBoardId(this.id); // list の配列を取得
        },
        set(value) {
            this.moveList(value); // list が移動した際に行う処理を実施
        },
    },
}
```

実際には、移動したデータを追跡し、順番を保持している変数値の更新とAPIサーバーとの通信などを実施している。

### 動かせるものを指定する

`<draggable>` で囲った要素は基本的にすべて、ドラッグ可能となってしまう。そのため、動かせないものを一緒にタグで囲わなければならない場合、動かしたいものを指定する必要がある。例えば今回の場合は、カードを追加するための「＋」ボタンのカードを動かしたくなかった。

以下は失敗例のサンプル。`<draggable>`の中に動かしたいカードと動かしたくない「＋」ボタンが入ってしまっている。そのため、このままだとボタンがドラッグ可能となってしまう。

```html
<draggable v-model="lists">
    <v-col v-for="(list, i) in lists" :key="i" cols="auto">
        <card-list :id="list.id" />
    </v-col>

    <!-- カード追加ボタン -->
    <v-col cols="auto">
        <v-btn>
            <v-icon>mdi-plus</v-icon>
        </v-btn>
    </v-col>
</draggable>
```

これを改善したのが以下のサンプル。Vue.Draggableでは、`draggable` 属性を用いて、動かしたいものと動かしたくないものを対象のclass属性で判別することが可能。

```html
<!-- dragable属性(draggable=".item")を付与 -->
<draggable v-model="lists" draggable=".item">
    <!-- ドラッグ可能にするためにclass属性(class="item")を付与 -->
    <v-col v-for="(list, i) in lists" :key="i" cols="auto" class="item">
        <card-list :id="list.id" />
    </v-col>

    <!-- itemクラスが付与されていないためドラッグ不可 -->
	<v-col cols="auto">
        <v-btn>
            <v-icon>mdi-plus</v-icon>
        </v-btn>
    </v-col>
</draggable>
```

上記の例では、`draggable=".item"`を用いて、`item`クラスを付与されているもののみ移動可能としている。
これにより、「＋」ボタンカードを除いたカードのみ移動可能とすることができる。

### ドラッグ可能な箇所を指定する

先ほどのサンプルを再掲。
以下のコードだと、実はカード外部でドラッグ可能となってしまう。
ドラッグ対象が `<v-col>` となっているので、実際ドラッグしたい `<card-list>` 外部でもドラッグが可能となってしまい、UI的にカードではない部分でドラッグできてしまう。

```html
<draggable v-model="lists" draggable=".item">
    <!-- ドラッグ対象は以下の要素となってしまう -->
    <v-col v-for="(list, i) in lists" :key="i" cols="auto" class="item">
		<!-- ドラッグしたいカード -->
        <card-list :id="list.id" />
    </v-col>

	<v-col cols="auto">
        <v-btn>
            <v-icon>mdi-plus</v-icon>
        </v-btn>
    </v-col>
</draggable>
```

改善した結果が以下のサンプルとなる。 `handle` 属性を用いて、ドラッグ判定を出す部分をclass属性で指定することが可能。

```html
<!-- handle属性(handle=".handle")を付与 -->
<draggable v-model="lists" draggable=".item" handle=".handle">
    <v-col v-for="(list, i) in lists" :key="i" cols="auto" class="item">
        <!-- class属性(class="handle")を付与 -->
        <card-list :id="list.id" class="handle" />
    </v-col>

    <v-col cols="auto">
        <v-btn>
            <v-icon>mdi-plus</v-icon>
        </v-btn>
    </v-col>
</draggable>
```

上記の例では、`handle=".handle"`を用いて、`handle` クラスを付与されているものがドラッグ可能と判定している。これにより、`<card-list>` 内（カード）をドラッグした場合のみドラッグ可能とすることができる。

### スムーズなドラッグアニメーションにする

デフォルトの移動時のアニメーションだと、動かしたというよりも瞬間移動した感じが出てしまう。そのため今回は、アニメーションを変更し、スムーズに動かした感じを出すこととした。

```html
<!-- animation属性(:animation="300")を付与 -->
<draggable v-model="lists" draggable=".item" handle=".handle" :animation="300">
    <v-col v-for="(list, i) in lists" :key="i" cols="auto" class="item">
        <card-list :id="list.id" class="handle" />
    </v-col>

    <v-col cols="auto">
        <v-btn>
            <v-icon>mdi-plus</v-icon>
        </v-btn>
    </v-col>
</draggable>
```

`animation`属性を用いてアニメーションの時間を変更している。今回は時間を多めにとることにより、動いている感じを出している。アニメーションも属性を一つ追加すればよいだけなのでとても簡単にできる。

## 最後に

久々のプログラミングだったので、細かなところなどを結構忘れていて、実装に時間がかかってしまった。

また、初めてドラッグアンドドロップを実装したが、Vue.Draggableを用いることで簡単に実装することができた。ドラッグアンドドロップを実装したい場合はとてもおすすめ。