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 に組み込んだ際に最も苦労したことのひとつです。ご期待ください。