コンテンツにスキップ

$p.events.on_kamban_load

概要

カンバンを読み込んだとき、もしくはフィルタ等で表示する内容が変わったときに実行するメソッドの指定方法を説明します。

制限事項

  1. 意図しないタイミングで処理が実行されてしまう等の影響がある場合を除き、出力先は「全て」を選択してください。
  2. 複数のスクリプトを作成し各スクリプトの内容に本関数を記述すると、スクリプトが上書きされるため、最新のもの以外は動作しません。回避策は以下FAQを参照してください。

構文

JavaScript
$p.events.on_kamban_load= function () {
    //処理内容
}

使用例

以下のサンプルコードをプリザンターのスクリプトへ設定すると、カンバン画面を読み込んだときに画面下部にメッセージを表示します。

JavaScript
$p.events.on_kamban_load= function () {
    $p.setMessage('#Message', JSON.stringify({
        Css: 'alert-success',
        Text: 'カンバンを読み込みました。'
        })
    );
}

サンプルコード

Note

コード内の【 ... 】は適宜修正してください。

1. カンバンで、列ごとの件数の上限(WIP上限)を守らせる

タスク管理のカンバンで、「実施中」「レビュー」などの列ごとに、同時に抱えてよい件数の上限(WIP上限)を決めておきます。$p.events.on_kamban_loadで列ごとの件数を数え、上限に達した列の見出しを色で強調して「件数 / 上限」を表示します。さらに、上限に達した列へカードをドラッグしたときは、$p.events.before_send_UpdateByKambanでfalseを返して移動を取り消し、メッセージを表示します。

実務での使いどころ

カンバンで作業を管理していると、「実施中」に作業が積み上がり、どれも終わらない状態になりがちです。同時に進める件数に上限を設ける(WIP制限)と、新しい作業に手を付ける前に、今の作業を終わらせることに目が向きます。

このサンプルを使うと、上限に達した列が一目で分かり、上限を超える移動はカンバンの操作の時点で止まります。「実施中は5件まで」「レビュー待ちは3件まで」といった決まりを、画面の上で守らせることができます。上限の値を変えれば、問い合わせ対応の「対応中」の件数の管理などにも使えます。

事前準備

1:テーブル(期限付きテーブル)の状況に、以下の選択肢を設定します(期限付きテーブルの既定の選択肢のままで構いません)。

値 表示名
100 未着手
150 準備
200 実施中
300 レビュー
900 完了
910 保留

2:スクリプトを登録します。
 2-1:「テーブルの管理 > スクリプト」タブで「新規作成」を選択する。
 2-2:タイトル: 任意の名前(例:WIP上限)
 2-3:スクリプト: 下記サンプルコードを貼り付け、WIP_LIMITSに列(状況の値)ごとの上限を書く
 2-4:出力先:「カンバン」にチェック。一覧画面からビューの切り替えでカンバンにする使い方もする場合は「一覧」にもチェック
3:カンバンを開き、分類(横軸)を「状況」にします。縦軸は自由です。

JavaScript
/**
 * ================================================================
 * サンプル  カンバンで、列ごとの件数の上限(WIP上限)を守らせる
 * ================================================================
 *
 * 【概要】
 *   タスク管理のカンバンで、「実施中」「レビュー」などの列ごとに、
 *   同時に抱えてよい件数の上限を決めておきます。
 *     ・$p.events.on_kamban_load で列ごとの件数を数え、上限に達した列の
 *       見出しを色で強調して「件数 / 上限」を表示します。
 *     ・上限に達した列へカードをドラッグしたときは、
 *       $p.events.before_send_UpdateByKamban で false を返して移動を取り消し、
 *       メッセージを表示します。
 *   on_kamban_load は、カンバンを開いたときだけでなく、縦軸などの設定を
 *   変えたときや、カードを移動して描き直したときにも発火します。
 *   描き直すと強調は消えるため、発火のたびに付け直します。
 *
 * 【前提】
 *   カンバンの横軸(分類)は「状況」にします。縦軸は自由です
 *   (縦軸で行が分かれていても、列の件数は全部の行の合計で数えます)。
 *
 * 【スクリプトの出力先】
 *   「カンバン」にチェックしてください。一覧画面からビューの切り替えで
 *   カンバンにする使い方もする場合は「一覧」にもチェックしてください。
 *
 * 【注意】
 *   止められるのはカンバンでのドラッグだけです。編集画面・一覧の一括更新・
 *   API で状況を変えた場合は止められません。必ず守らせたい場合は、
 *   サーバースクリプトでの確認と組み合わせてください。
 * ================================================================
 */

// ===== 設定 =====
// 列(状況の値)ごとの件数の上限。ここに書いていない列には上限を設けない
const WIP_LIMITS = {
    200: 5, // 実施中
    300: 3, // レビュー
};
// 上限に達した列の見出しの色
const LIMIT_REACHED_COLOR = '#ffc7c7';

// ===== 処理 =====
// 列(状況の値)のカードの件数。縦軸の行をまたいで数える
function countCards(status) {
    return $(`#KambanBody td.kamban-container[data-x="${status}"] .kamban-item`).length;
}

// 列(状況の値)の見出しの th を探す。
// 見出しの並びは本体のセルの並びと同じなので、本体の1行目で
// その列のセルが何番目かを調べ、同じ位置の見出しを返す
function findHeader(status) {
    const $cells = $('#KambanBody tbody tr').first().children();
    const index = $cells.index($cells.filter(`td.kamban-container[data-x="${status}"]`));
    if (index < 0) {
        return $();
    }
    return $('#KambanBody thead tr').first().children().eq(index);
}

// カンバンを開いたとき、設定の変更やカードの移動で描き直すたびに呼ばれる
$p.events.on_kamban_load = function () {
    Object.keys(WIP_LIMITS).forEach(function (status) {
        const limit = WIP_LIMITS[status];
        const count = countCards(status);
        const $header = findHeader(status);
        if ($header.length === 0) {
            return;
        }
        $header.append($('<span>').text(` ${count} / ${limit}`));
        if (count >= limit) {
            $header.css('background-color', LIMIT_REACHED_COLOR);
        }
    });
};

// カードをドラッグして別のセルに落としたとき、保存の送信の前に呼ばれる。
// false を返すと送信されず、カードは元の列に残る
$p.events.before_send_UpdateByKamban = function (args) {
    const data = args.data || {};
    // 移動先の状況は「Issues_Status」(記録テーブルなら「Results_Status」)で届く
    const statusKey = Object.keys(data).find(function (key) {
        return /_Status$/.test(key);
    });
    if (!statusKey) {
        return true;
    }
    const to = String(data[statusKey]);
    const limit = WIP_LIMITS[to];
    if (limit === undefined) {
        // 上限を決めていない列への移動は止めない
        return true;
    }
    // 移動元の列は、移動したカード(KambanId)が今どの列にあるかで調べる
    const from = String(
        $(`#KambanBody .kamban-item[data-id="${data.KambanId}"]`)
            .closest('td.kamban-container')
            .attr('data-x')
    );
    if (from === to) {
        // 同じ列の中での移動(縦軸だけの変更)は件数が変わらないので止めない
        return true;
    }
    if (countCards(to) >= limit) {
        const name = findHeader(to).text().replace(/\(.*$/, '').trim();
        $p.setMessage(
            '#Message',
            JSON.stringify({
                Css: 'alert-error',
                Text: `${name}は上限(${limit}件)に達しています。`,
            })
        );
        return false;
    }
    return true;
};

注意点

  • 列の件数は、縦軸で行が分かれていても、全部の行の合計で数えます。
  • 同じ列の中での移動(縦軸のみを変える移動)や、上限を決めていない列への移動は止めません。
  • 移動先の状況は、args.dataの「Issues_Status」(記録テーブルでは「Results_Status」)で受け取ります。このサンプルは末尾が「_Status」の項目を探すので、どちらのテーブルでも動きます。
  • 止められるのはカンバンでのドラッグのみです。編集画面・一覧の一括更新・APIで状況を変えた場合は止められません。必ず守らせたい場合は、サーバースクリプトでの確認と組み合わせてください。
2. カンバンのカードに、コメントの件数とコメントの内容を表示する

カンバンを開いたとき、表示中のカードのコメントをまとめて取得し、コメントのあるカードに「💬 2」のように件数を付けます。カードにマウスを乗せると、コメントを新しい順に表示します。

実務での使いどころ

カンバンで作業の流れを見ていると、「このカードはなぜ止まっているのか」「最近どんなやり取りがあったか」を知りたいことがよくあります。標準のカンバンではカードに件名しか出ないため、そのたびにカードを開く必要があります。このサンプルを使うと、コメントのあるカードがすぐ分かり、マウスを乗せれば最新のコメントを確かめられます。

事前準備

1:スクリプトを登録します。
 1-1:「テーブルの管理 > スクリプト」タブで「新規作成」を選択する。
 1-2:タイトル: 任意の名前(例:カンバンのコメント表示)
 1-3:スクリプト: 下記サンプルコードを貼り付け
 1-4:出力先:「カンバン」にチェック。一覧画面からビューの切り替えでカンバンにする使い方もする場合は「一覧」にもチェック

JavaScript
/**
 * ================================================================
 * サンプル  カンバンのカードに、コメントの件数とコメントの内容を表示する
 * ================================================================
 *
 * 【概要】
 *   カンバンを開いたとき、$p.events.on_kamban_load で表示中のカードの
 *   コメントを $p.apiGet でまとめて取得し、コメントのあるカードに
 *   「💬 2」のように件数を付けます。カードにマウスを乗せると、
 *   コメントを新しい順に表示します。カードを開かなくても、
 *   止まっている理由や最近のやり取りを確かめられます。
 *   on_kamban_load は、カンバンを開いたときだけでなく、設定の変更や
 *   カードの移動で描き直すたびに発火します。描き直すと件数の表示は
 *   消えるため、発火のたびに取得し直して付け直します。
 *
 * 【取得のしかた】
 *   カードごとに API を呼ぶと、カードが多いときに通信が増えるため、
 *   表示中のカードのID(data-id)をまとめて渡して1回で取得します
 *   (API が一度に返すのは200件までなので、200件ずつに分けて取得します)。
 *   コメントは「Comments」に、新しい順の JSON 形式の文字列で入っています。
 *
 * 【スクリプトの出力先】
 *   「カンバン」にチェックしてください。一覧画面からビューの切り替えで
 *   カンバンにする使い方もする場合は「一覧」にもチェックしてください。
 *
 * 【ほかの on_kamban_load と一緒に使う場合】
 *   $p.events.on_kamban_load は1つしか登録できません。WIP上限のサンプルなど、
 *   ほかにも on_kamban_load を使うスクリプトがある場合は、1つの関数の中で
 *   両方の処理を呼ぶようにまとめてください。
 * ================================================================
 */

// ===== 設定 =====
// マウスを乗せたときに表示するコメントの件数(新しいものから)
const MAX_COMMENTS = 3;
// コメント1件あたりの最大文字数(超えた分は「…」にする)
const MAX_LENGTH = 200;

// ===== 処理 =====
// レコードID → コメントの配列(新しい順)。描き直すたびに作り直す
let commentsById = {};

// コメント(JSON 形式の文字列)を配列にする
function parseComments(text) {
    try {
        return text ? JSON.parse(text) : [];
    } catch (e) {
        return [];
    }
}

// 「2026-10-07T14:54:33.91+09:00」を「2026/10/07 14:54」にする
function formatTime(text) {
    const date = new Date(text);
    if (isNaN(date)) {
        return '';
    }
    const pad = (n) => String(n).padStart(2, '0');
    return (
        `${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())} ` +
        `${pad(date.getHours())}:${pad(date.getMinutes())}`
    );
}

// 表示中のカードのコメントを、IDを200件ずつに分けてまとめて取得する
function loadComments(ids) {
    // レコードIDの項目は、期限付きテーブルでは IssueId、記録テーブルでは ResultId
    const idColumn = $('#ReferenceType').val() === 'Issues' ? 'IssueId' : 'ResultId';
    for (let i = 0; i < ids.length; i += 200) {
        const chunk = ids.slice(i, i + 200);
        $p.apiGet({
            id: $p.siteId(),
            data: {
                View: {
                    ApiDataType: 'KeyValues',
                    ApiColumnKeyDisplayType: 'ColumnName',
                    GridColumns: [idColumn, 'Comments'],
                    // IDの配列で絞り込む
                    ColumnFilterHash: { [idColumn]: JSON.stringify(chunk) },
                },
            },
            done: function (data) {
                (data.Response.Data || []).forEach(function (record) {
                    const comments = parseComments(record.Comments);
                    if (comments.length === 0) {
                        return;
                    }
                    const id = String(record[idColumn]);
                    commentsById[id] = comments;
                    // コメントのあるカードに件数を付ける
                    $(`#KambanBody .kamban-item[data-id="${id}"]`).append(
                        $('<span>')
                            .addClass('kamban-comment-count')
                            .css({ marginLeft: '6px', fontSize: '0.85em', color: '#666', whiteSpace: 'nowrap' })
                            .text(`💬 ${comments.length}`)
                    );
                });
            },
            fail: function (data) {
                // 取得に失敗してもカンバンの表示は止めず、コンソールに記録するだけにする
                console.log('コメントの取得に失敗しました', data);
            },
        });
    }
}

// カンバンを開いたとき、設定の変更やカードの移動で描き直すたびに呼ばれる
$p.events.on_kamban_load = function () {
    commentsById = {};
    const ids = $('#KambanBody .kamban-item')
        .map(function () {
            return String($(this).attr('data-id'));
        })
        .get();
    if (ids.length > 0) {
        loadComments(ids);
    }
};

// ===== マウスを乗せたときの表示 =====
// カードは描き直すたびに作り直されるため、document に1回だけ登録しておく
function showTooltip(event, comments) {
    let $tooltip = $('#KambanCommentTooltip');
    if ($tooltip.length === 0) {
        $tooltip = $('<div id="KambanCommentTooltip">')
            .css({
                position: 'absolute',
                zIndex: 1000,
                maxWidth: '360px',
                padding: '8px 10px',
                background: '#fff',
                border: '1px solid #ccc',
                borderRadius: '4px',
                boxShadow: '0 2px 6px rgba(0, 0, 0, 0.2)',
                fontSize: '0.9em',
                whiteSpace: 'pre-wrap',
            })
            .appendTo('body');
    }
    $tooltip.empty();
    comments.slice(0, MAX_COMMENTS).forEach(function (comment) {
        const body = String(comment.Body || '');
        // コメントは利用者が書いた文字なので、HTML として解釈させず text() で入れる
        $('<div>')
            .css({ marginBottom: '6px' })
            .append($('<div>').css({ color: '#888', fontSize: '0.85em' }).text(formatTime(comment.CreatedTime)))
            .append($('<div>').text(body.length > MAX_LENGTH ? body.slice(0, MAX_LENGTH) + '…' : body))
            .appendTo($tooltip);
    });
    if (comments.length > MAX_COMMENTS) {
        $('<div>').css({ color: '#888' }).text(`ほか ${comments.length - MAX_COMMENTS} 件`).appendTo($tooltip);
    }
    $tooltip.css({ left: event.pageX + 12, top: event.pageY + 12 }).show();
}

$(document).on('mouseenter', '#KambanBody .kamban-item', function (event) {
    const comments = commentsById[String($(this).attr('data-id'))];
    if (comments) {
        showTooltip(event, comments);
    }
});
$(document).on('mouseleave mousedown', '#KambanBody .kamban-item', function () {
    // カードから離れたとき、ドラッグを始めたときは消す
    $('#KambanCommentTooltip').hide();
});

注意点

  • コメントは、表示中のカードのIDをまとめて渡して取得します(APIが一度に返すのは200件までのため、200件ずつに分けます)。カードごとに取得するより通信が少なくて済みます。
  • 表示するのは新しい順に3件まで、1件200文字までです。MAX_COMMENTS・MAX_LENGTHで変えられます。
  • コメントは利用者が書いた文字のため、HTMLとして解釈させずに表示しています。
  • $p.events.on_kamban_loadは1つしか登録できません。サンプル1と一緒に使う場合は、1つの関数の中で両方の処理を呼ぶようにまとめてください。