JavaScriptでカンバン機能を実現できるライブラリ「jKanban」の使い方 〜 その(3)
前回のブログ記事では、カンバンのカラムやカードに色を付けたり画像を表示したりする装飾方法を紹介しました。
今回は、カードをクリックしたりドラッグ&ドロップしたときの動作を設定します。
デモンストレーション
今回もデモ用の HTML を用意しました。
https://github.com/bunatree/jkanban-examples/blob/main/example3.html
前回と同じような、赤、青、緑の 3 つのカラムと 5 枚のカード、そして、その上に明るい青緑のバーが表示されます。
こちら↓は CodePen に作成した動くデモです。 https://codepen.io/bunatree/pen/XWaYPwb
See the Pen jKanban Example 3 by bunatree (@bunatree) on CodePen.
jKanbanのコールバック
jKanban では、次のコールバックが用意されています。
click: function (el) {}, // カードが左クリックされた時に実行 context: function (el, event) {}, // カードが右クリックされた時に実行 dragEl: function (el, source) {}, // カードのドラッグが始まった時に実行 dragendEl: function (el) {}, // カードがドラッグが終わった時に実行 dropEl: function (el, target, source, sibling) {}, // カードがドロップされたときに実行 dragBoard: function (el, source) {}, // カラムのドラッグを開始した時に実行 dragendBoard: function (el) {}, // カラムのドラッグが終わった時に実行 buttonClick: function(el, boardId) {} // カード追加ボタンがクリックされた時に実行
今回は、これらのうちの click
と dropEl
を使います。
アクション実行時の動作を設定
カードが左クリックされたときの動作
ここでは、カードが左クリックされたときに、青緑のバーにカードの名前(テキスト)が表示されるようにすることにします。
ここで使うのは click
コールバックです。次のように、onKanbanItemClicked
関数が実行されるようにしました。
click: function (el) { onKanbanItemClicked(el); },
onKanbanItemClicked
関数は、次のとおりです。
function onKanbanItemClicked(el) { showMessage('カード「' + el.innerText + '」が左クリックされました。'); }
引数の el
は、クリックされたカードの HTML 要素です。そのプロパティである innerText
は、カードに表示されているテキストです。
そのテキストが渡された showMessage
関数は show-message
という id を持つ HTML 要素(青緑バー)にテキストを表示します。
function showMessage(msg) { document.getElementById('show-message').innerHTML = msg; }
カードをクリックすると、「カード『○○○』が左クリックされました。」というメッセージがカンバンの上の青緑バーに表示されます。
カードがドラッグ&ドロップされたときの動作
ここでは、カードがドロップされたときに次の動作が行われるようにします。
- 同じカラム内にドロップされたとき(=カラム内でカードの順序が変更されたとき)メッセージを表示する。
- 異なるカラムにドロップされたとき(=カードが別のカラムに移動したとき)メッセージを表示する。
- カードが「完了」カラムへ移動したときは、グレーアウトさせ、テキストに取り消し線を入れる。
- カードが「完了」カラムから他のカラムへ移動したときは、グレーアウトと取り消し線を解除する。
ここで使うのは dropEl
コールバックです。次のように、onKanbanItemDropped
関数が実行されるようにしました。
dropEl: function (el, target, source, sibling) { onKanbanItemDropped(el, target, source, sibling); },
こちらが関数 onKanbanItemDropped のコードです。
function onKanbanItemDropped(el, target, source, sibling) { // 移動元カラムのタイトル const sourceTitle = source.parentNode.querySelector('header').innerText; // 移動元カラムのID const sourceId = source.parentNode.dataset.id; // 移動先カラムのタイトル const targetTitle = target.parentNode.querySelector('header').innerText; // 移動元カラムのID const targetId = target.parentNode.dataset.id; // 同じカラム内の移動か、それとも異なるカラム間の移動かを判別 const sameColumn = (sourceId === targetId) ? true : false; // カラム内 or カラム間の移動によってメッセージを変える const alertMsg = (sameColumn) ? 'カード「' + el.innerText + '」が、カラム『' + sourceTitle + '』内で移動しました。': 'カード「' + el.innerText + '」が、カラム『' + sourceTitle + '』からカラム『' + targetTitle + '』へ移動しました。'; // メッセージを表示 showMessage(alertMsg); // 異なるカラム間の移動なおかつ移動先カラムが「完了」の場合 // カードのステータスを「done」にする。 // 異なるカラム間の移動なおかつ移動先カラムが「完了」以外の場合 // カードのステータスを「todo」にする。 if (!sameColumn && targetTitle === '完了') { setKanbanItemStatus(el, 'done'); } else if (!sameColumn && targetTitle !== '完了') { setKanbanItemStatus(el, 'todo'); } }
同じカラム内の移動か別のカラムへの移動かを判定
移動元と移動先のカラムが同じかどうかを比較・判定するため、次の箇所でカラムの id を取得しています。
// 移動元カラムのID const sourceId = source.parentNode.dataset.id; // 移動先カラムのID const targetId = target.parentNode.dataset.id;
ここで注意すべき点は、カードの移動元を表す source
と移動先を表す target
の引数はカラム本体の <div class="kanban-board">
要素ではなく、その子である <main class="kanban-drag">
要素だということです。
カラムの id は、source
や target
の親要素(<div class="kanban-board">
要素)に data-id=
属性として挿入されているので、parentNode
で 1 つ上へ DOM ツリーを登って、それから dataset.id
で id の値を取得します。
下図は、カード「AAA」をカラム「準備中」からカラム「実行中」へ移動させる場合の例です。
こうして取得した id を比較し、同じカラム内の移動なのか、異なるカラム間の移動なのかを判定しています。
// 同じカラム内の移動か、それとも異なるカラム間の移動かを判別 const sameColumn = (sourceId === targetId) ? true : false;
「完了」カラムへの移動、「完了」カラムからの移動
次の箇所で、カードが別のカラムへ移動したとき、移動先のカラムのタイトルが「完了」かどうかを調べ、setKanbanItemStatus
関数を呼び出しています。
if (!sameColumn && targetTitle === '完了') { setKanbanItemStatus(el, 'done'); } else if (!sameColumn && targetTitle !== '完了') { setKanbanItemStatus(el, 'todo'); }
カードが「完了」カラムへ移動したとき、引数 status
の値は「done」になり、カードの要素 el
から「todo」クラスが削除され、「done」クラスが追加されます。
カードが「完了」カラム以外へ移動したときは、引数 status の値は「todo」になり、カードの要素 el
から「done」クラスが削除され、「todo」クラスが追加されます。
function setKanbanItemStatus(el, status) { // ステータスが「done」の場合は // カードの「todo」クラスを「done」に変更する。 if (status === 'done') { el.classList.remove('todo'); el.classList.add('done'); // ステータスが「done」以外の場合は // カードの「done」クラスを「todo」に変更する。 } else { el.classList.remove('done'); el.classList.add('todo'); } }
つまり、カードがドロップされたカラムが「完了」かどうかによって、カードの HTML 要素である <div class="kanban-item">
に「todo」と「done」のどちらかのクラスが追加・削除されます。
なお、カードは次のように定義されており、class
プロパティを見てわかるように、「完了」以外のカラムに表示されているカードの HTML 要素には元々「todo」クラスが付いています。
{ "id": "item-id-1", "title": "AAA", "class": "todo" }, { "id": "item-id-2", "title": "BBB", "class": "todo,orange", }
カードが「完了」カラムに移動し、カードの HTML 要素の「todo」クラスが「done」クラスに変更されると、何が起こるのでしょうか?
それは CSS で次のように設定してます。
/* 完了したカードのテキストを灰色にして取り消し線を入れる */ .kanban-item.done { color: gray; text-decoration: line-through; background-color: whitesmoke; } /* 完了したカードの画像を75%モノクロにして薄く表示する */ .kanban-item.done img { filter: grayscale(75%); opacity: 0.5; }
下図は、カード「CCC」を「完了」カラムへ移動したときの例です。テキスト「CCC」の色がグレーになり、取り消し線が入り、画像がモノクロ化され、画像の色が薄くなっています。
このカードを別のカラムへ移動させると、カードのクラスが「done」から「todo」に戻り、これらの装飾が解除されて色が元に戻ります。
まとめ
以上、jKanban でカードをクリックしたり移動(ドラッグ&ドロップ)したりするときの動作の設定方法を紹介しました。
実際にアプリケーションを開発する場合は、データベースを用意し、カードの状況(どのカラムに所属しているのかやカラム内の順番など)が保存されるようにする必要があります。
TeamPage のカンバン機能では、
- カードがクリックされたら、クリックされた記事の詳細情報を表示するダイアログを表示
- カードがカラム内で移動したら、カラム内のカードの順序をデータベースに保存
- カードが別カラムへ移動したら、データベースに保存されている移動先カラムのカード一覧情報を更新
- 移動先カラムが「完了」で、ドロップされた記事の種類が「タスク」だったら、タスクのステータスを「done」に変更
…などの処理が行われるようにしました。
次の記事では、スイムレーンについて紹介します。jKanban を TeamPage に組み込んだ際に最も苦労したことのひとつです。ご期待ください。