LoginSignup
5
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-09-30

※はじめからはこちら

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

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

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

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

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

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
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を更新するサービスを追加します。

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で受け取ることになります。

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


    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文を伸ばしていくことになるのでよくありません。ということでこんな感じに実装します。

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となっている部分に組み込むだけです。

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から呼び出せるようにします。

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のコネクションを取得します。そしてサーバに対してsendObjtype: update_card_orderを含んだメッセージを送信しています。これで、Consumer側のupdate_card_orderが呼び出されます。

ComponentにActionを組み込み

PipeLine.vueupdateCardOrderを組み込みます。

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に指定したwrappedCardListsetを呼び出しますので、その中でupdateCardOrderを呼び出してやれば新しい並び順を渡すことができます。

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

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

次回

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3