DjangoとVueでカンバンアプリケーションを作る(10)

※はじめからは[こちら](https://qiita.com/denzow/items/046f3c8b9bd8d3378eb4#_reference-7b3e4a58510793f8a9c7)

前回までで、ボードのデータを表示することはできたので今回はカードの並び替えを実装していきます。

### 並び替え更新の処理をConsumerに追加

Consumerに、あるPipeLine内のCardIdのリストを受け取り、それで各CardのOrderを更新する処理を追加していきます。まずは、そのようなサービスを定義して行きます。

#### PipeLine, Cardモデルにメソッド追加

PipeLineやCardがID指定で取得できるほうが楽なので、そのようなクラスメソッドを定義します。

```diff:application/modules/kanban/models/pipe_line.py
@@ -15,3 +15,10 @@ class PipeLine(models.Model):
     @classmethod
     def get_list_by_board(cls, board):
         return list(cls.objects.filter(board=board).order_by('order'))
+
+    @classmethod
+    def get_by_id(cls, pipe_line_id):
+        try:
+            return cls.objects.get(id=pipe_line_id)
+        except cls.DoesNotExist:
+            return None
```

```diff:application/modules/kanban/models/card.py
@@ -16,3 +16,10 @@ class Card(models.Model):
     @classmethod
     def get_list_by_pipe_line(cls, pipe_line):
         return list(cls.objects.filter(pipe_line=pipe_line).order_by('order'))
+
+    @classmethod
+    def get_by_id(cls, card_id):
+        try:
+            return cls.objects.get(id=card_id)
+        except cls.DoesNotExist:
+            return None
```

#### update_card_orderの実装

PipeLineのIDと新しい並び順としてのCardIdのListを受け取り、その順に`card.order`を更新するサービスを追加します。

```diff:application/modules/kanban/service.py
@@ -49,3 +49,17 @@ def get_board_data_by_board_id(board_id):
         board_data['pipe_line_list'].append(pipe_line_data)

     return board_data
+
+
+def update_card_order(pipe_line_id, card_id_list):
+    """
+    :param int pipe_line_id:
+    :param list card_id_list:
+    :return:
+    """
+    pipe_line = PipeLine.get_by_id(pipe_line_id)
+    for i, card_id in enumerate(card_id_list):
+        card = Card.get_by_id(card_id)
+        card.order = i
+        card.pipe_line = pipe_line
+        card.save()
```

これを呼び出せば、指定したPipeLine内のカードをまるっと更新できます。

#### Consumerの分岐処理を実装

Consumerがクライアントからメッセージを受け取る場合、`JsonWebsocketConsumer`を継承しているクラスでは`receive_json`というメソッドが呼び出されます。今後はクライアントから様々な種別のメッセージが届き、それごとに処理を実装していきますが、いずれも`receive_json`で受け取ることになります。

これを何も考えずに実装すると

```python

    def receive_json(self, content, **kwargs):
        action_type = content['type']
        if action_type == 'update_card_order':
            self.update_card_order()
        elif action_type == 'add_card':
            self.add_card()
```

こんな感じになります。しかし、これでは処理が増えるたびにif文を伸ばしていくことになるのでよくありません。ということでこんな感じに実装します。

```diff:application/views/ws/kanban_consumer.py
@@ -13,6 +13,9 @@ class KanbanConsumer(JsonWebsocketConsumer):
         self.user = None
         self.board_id = None
         self.namespace = 'board'
+        self.action_map = {
+            'update_card_order': self.update_card_order
+        }

     def connect(self):
         # 認証チェック
@@ -35,3 +38,17 @@ class KanbanConsumer(JsonWebsocketConsumer):
             'namespace': self.namespace,
         })

+    def update_card_order(self, content):
+        pass
+
+    def receive_json(self, content, **kwargs):
+        """
+        Typeに応じた処理を呼び出して実行する
+        :param dict content:
+        :param kwargs:
+        :return:
+        """
+        action = self.action_map.get(content['type'])
+        if not action:
+            raise Exception('{} is not a valid action_type'.format(content['type']))
+        action(content)
```

`self.action_map`にどのメッセージが来たらどのメソッドを呼び出すかの対応を定義しておき、`receive_json`ではメッセージ内の`type`に該当するものを直接呼び出します。また未定義の`type`を受け取った場合は例外を送出させます。これであれば、新しいメッセージを追加する際も`action_map`に追加するだけで済みます。

#### update_card_orderの組み込み

先程サービスとして作成した`update_card_order`をConsumerに追加します。といっても`action_map`には先程追加したので`pass`となっている部分に組み込むだけです。

```diff:application/views/ws/kanban_consumer.py
@@ -39,7 +39,19 @@ class KanbanConsumer(JsonWebsocketConsumer):
         })

     def update_card_order(self, content):
-        pass
+        """
+        ボード内のカードの並び順を更新する
+        {
+            'type': 'update_card_order',
+            'pipeLineId': 1,
+            'cardIdList': [3, 1]
+        }
+        :return:
+        """
+        pipe_line_id = content['pipeLineId']
+        card_id_list = content['cardIdList']
+        kanban_sv.update_card_order(pipe_line_id, card_id_list)
+        self.send_board_data()
```

これで一旦サーバ側の実装は終わりです。

### 並び替えの処理をフロント側に実装

#### Store.actionに呼び出しを追加

今しがたConsumerに追加した`update_card_order`をStoreから呼び出せるようにします。

```diff:application/vuejs/src/store/pages/board.js
@@ -21,12 +21,26 @@ const getters = {
 };

 const actions = {
+  updateCardOrder({ commit, getters }, { pipeLineId, cardList }) {
+    console.log(pipeLineId, cardList);
+    const socket = getters.getSocket;
+    socket.sendObj({
+      type: 'update_card_order',
+      pipeLineId,
+      cardIdList: cardList.map(x => x.cardId),
+    });
+  },
 };

```

このActionは更新対象の`pipeLineId`と新しい並び順になったカードの一覧を`cardList`として受け取ります。
そして`getters.getSocket`でrootStoreに格納されているWebSocketのコネクションを取得します。そしてサーバに対して`sendObj`で`type: update_card_order`を含んだメッセージを送信しています。これで、Consumer側の`update_card_order`が呼び出されます。


#### ComponentにActionを組み込み

`PipeLine.vue`に`updateCardOrder`を組み込みます。

```diff:application/vuejs/src/pages/Board/components/BoardArea/PipeLine.vue
@@ -27,9 +27,12 @@
 </template>

 <script>
+import { createNamespacedHelpers } from 'vuex';
 import Draggable from 'vuedraggable';
 import Card from './Card.vue';

+const { mapActions, mapGetters } = createNamespacedHelpers('board');
+

 export default {
   name: 'PipeLine',
@@ -59,6 +62,10 @@ export default {
       },
       set(value) {
         console.log(value);
+        this.updateCardOrder({
+          pipeLineId: this.pipeLine.pipeLineId,
+          cardList: value,
+        });
       },
     },
     pipeLineName() {
@@ -72,6 +79,12 @@ export default {
     delPipeLineAction() {
       window.confirm(`DELETE [${this.pipeLineName}] ? Are you sure?`);
     },
+    ...mapActions([
+      'updateCardOrder',
+    ]),
+    ...mapGetters([
+      'getBoardId',
+    ]),
   },
 };
```

VueDraggableはD&D終了時に、v-modelに指定した`wrappedCardList`の`set`を呼び出しますので、その中で`updateCardOrder`を呼び出してやれば新しい並び順を渡すことができます。

これで、カードの並び替え自体はできるようになりました。

![](https://i.gyazo.com/5258e981bb4258bc37ebd82f7b28acee.gif)

PipeLine内でも、PipeLineをまたがってもカードを移動できています。もちろんF5してもその並び順は保持されています。しかし、一瞬カードが前の位置に戻ってしまうチラ付きが出ています。これは次回解決していきます。

### 次回

https://qiita.com/denzow/items/d358e001fc38c65e1740