JavaScriptでカンバン機能を実現できるライブラリ「jKanban」の使い方 〜 その(3)

2021/11/12 · · 投稿者 Takashi Okutsu

画像前回のブログ記事では、カンバンのカラムやカードに色を付けたり画像を表示したりする装飾方法を紹介しました。

今回は、カードをクリックしたりドラッグ&ドロップしたときの動作を設定します。

デモンストレーション

今回もデモ用の 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) {} // カード追加ボタンがクリックされた時に実行

今回は、これらのうちの clickdropEl を使います。

アクション実行時の動作を設定

カードが左クリックされたときの動作

ここでは、カードが左クリックされたときに、青緑のバーにカードの名前(テキスト)が表示されるようにすることにします。

ここで使うのは 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;
}

カードをクリックすると、「カード『○○○』が左クリックされました。」というメッセージがカンバンの上の青緑バーに表示されます。

カード「CCC」を左クリックしたとき

カードがドラッグ&ドロップされたときの動作

ここでは、カードがドロップされたときに次の動作が行われるようにします。

  • 同じカラム内にドロップされたとき(=カラム内でカードの順序が変更されたとき)メッセージを表示する。
  • 異なるカラムにドロップされたとき(=カードが別のカラムに移動したとき)メッセージを表示する。
  • カードが「完了」カラムへ移動したときは、グレーアウトさせ、テキストに取り消し線を入れる。
  • カードが「完了」カラムから他のカラムへ移動したときは、グレーアウトと取り消し線を解除する。

ここで使うのは 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 は、sourcetarget の親要素(<div class="kanban-board"> 要素)に data-id= 属性として挿入されているので、parentNode で 1 つ上へ DOM ツリーを登って、それから dataset.id で id の値を取得します。

下図は、カード「AAA」をカラム「準備中」からカラム「実行中」へ移動させる場合の例です。

カラム間のカード移動とHTML要素

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

Page Top