見出し画像

「すごくないkintoneカスタマイズ思考プロセス」〜テーブル行複製ボタンを作る【超初心者向け】

この記事は、kintone Advent Calendar 2022の19日目の記事です。

みなさんこんにちは。プロジェクト・アスノートの松田です。
サイボウズ公認 kintoneエバンジェリストでもあります。
今日は kintoneカスタマイズのお話です。

2022/10/26に開催された、imoniCampのLT大会でお話した内容をベースに、もっと持って帰りたい人や、もう少し勉強してみたい人のために残しておきます。
内容は超初心者向けに詳しめに書いていきます。

今回のお題

テーブルによる明細入力を行う必要がある、経費精算のアプリです。
以下の画像のような明細テーブルがあり、そこに交通費や宿泊費等の明細を日別に入力する必要があります。

明細の入力画面

カスタマイズの方針(普通に考えた場合)

それではさっそく、行コピー機能を考えていきます。
普通に kintoneカスタマイズを考える場合、きっと次のようになるのではないでしょうか。

  1. 処理を動かすタイミング
    行のコピーは編集画面で利用するため、カスタマイズを動かすイベントとしては、新規レコード作成画面と編集画面。一覧表示のインライン編集ではテーブルの編集は行わないので対象外。
    編集画面内のテーブルの各行に、行複製ボタンを設置し、これをクリックしたら複製処理を動かす。

  2. 行複製ボタンが押された時の処理
    ボタンはテーブルの各行に設置する必要があるため、押されたボタンがどの行のボタンなのかを判定する必要がある。
    そして、行を複製するために、そのテーブル行のデータを取得する必要がある。

  3. テーブルの更新データを作成する処理
    テーブルのデータを更新するためには、既存の行を含めた全行・全フィールドのデータが必要となるため、ボタンを押したタイミングのテーブルデータを取得し、それに複製行データを加えたデータ(オブジェクト)を作成する必要がある。

  4. テーブルデータの更新
    3で作成したテーブル更新データを、編集中の画面に反映させる。

と、こんなふうにカスタマイズの方針を考えるわけですが、ここでいくつか難しそうなポイントがあることがわかります。

難しそうなポイント

  1. テーブル内の各行に行複製ボタンを設置

  2. ボタンが押されたときに、どの行のボタンが押されたのかを判定

  3. ボタンが押された行のテーブルデータを取得

  4. 編集中のテーブルデータを更新(複製された行を挿入)

特に、1,2あたりが難しそうです。
特にテーブルに設置する場合、編集中に行追加する場合もあるので、それに対応させることも考慮する必要があります。

基本機能を活用したカスタマイズ方針

そこで、今回は kintoneの基本機能をうまく活用したカスタマイズ方法を考えてみましょう。
kintoneが持っている基本機能のフィールドをボタンとして活用するというアイデアです。

今回は、ボタンっぽく見えるチェックボックスを使ってみましょう。
テーブルの行の左端に、チェックボックスを設置し、選択肢は1つだけ設定します。

  1. 処理を動かすタイミング
    チェックボックスをボタンとして使うため、ボタンクリックの判定として、フィールドの値変更時イベントを使うことができます。

  2. 対象のテーブル行データを取得
    テーブル内フィールドの値変更時イベントでは、変更されたフィールドがある行のオブジェクトも取得することができます(event.changes.row)
    ボタンが押された判定も、event.changes.fieldを使うことで、効率よくできそうですね。

  3. 既存テーブルデータに複製行データを挿入
    値変更時イベントのeventオブジェクトは、そのイベントが発生時点のフィールドデータを持っています。
    元テーブルデータは event.record['Table'].valueとなるので、この配列に、先程の複製対象行データを挿入(push)することでいけそうです。

  4. 編集中の画面更新
    値変更時イベントでは、eventオブジェクトを書き換えて return することで編集中の画面を更新することができます。

どうでしょうか?
値変更時イベントを使うことで、全ての課題がかなりシンプルに考えることができるようになりました。

さっそくコーディング

まずはコーディングの全体像

/**
 *  テーブルの行コピー機能(チェックボックスをボタン化
 *  2022/10/22 Shotaro Matsuda 
**/

( () => {
    'use strict';
    const chkboxCode = '行複製';  // テーブル内のチェックボックスフィールドコードを指定(コピーボタン)
    const tableCode = 'Table';    // テーブルのフィールドコードを設定

    const events = [
        'app.record.create.change.' + chkboxCode,
        'app.record.edit.change.' + chkboxCode
    ];

    kintone.events.on(events, (event) => {
        // チェックボックスのチェックが外された場合は処理キャンセル(例外)
        if (event.changes.field.value.length === 0) {
            return event;
        }

        // コピー挿入するテーブル行のデータ作成
        const addRowValue = {
            id: null,
            valueevent.changes.row.value
        };

        // 指定された行のデータを既存のテーブル最下行に挿入
        event.record[tableCode]['value'].push(addRowValue);

        // チェックボックスのクリアー
        event.changes.field.value = [];

        return event;
    });

    // 詳細画面、印刷画面ではチェックボックス(コピーボタン)は非表示とする処理
    const showEvents = [
        'app.record.detail.show',
        'app.record.print.show'
    ];
    kintone.events.on(showEvents, (event) => {
        kintone.app.record.setFieldShown(chkboxCode, false);
        return event;
    });
})();

1.全体構造

まずはお約束の即時関数を書きましょう。
devNet - 第1回 kintone JavaScript APIのイジりかた

そして、strictモードの宣言も忘れずに。

(() => {
    'use strict';
    // この中に処理を書く
});

ここまではお約束として、kintoneカスタマイズのコーディングをするときは自動的に書いてしまえるようになっておきましょう。

2.フィールドコードとイベント

利用するフィールドコードは最初に、変数としてまとめて宣言しておくことにしました。
フィールドコードを、処理中に都度書いても動作的には同じになりますが、処理中に何回も出てくる場合には、まとめて変数に設定しておくとわかりやすくなります。
さらに、別のアプリに適用したり、アプリ側を変更してコードを修正するときにも、まとまっている方がラクチンです。

const chkboxCode = '行複製';  // テーブル内のチェックボックスフィールドコードを指定(コピーボタン)
const tableCode = 'Table';    // テーブルのフィールドコードを設定

今回利用するイベントは、フィールド値変更時イベントです。
値変更時イベントは、新規レコード作成画面と編集画面で発生しますので、それらを events という変数に設定しておきます。

const events = [
    'app.record.create.change.' + chkboxCode,
    'app.record.edit.change.' + chkboxCode
];

ここまで設定してきたものを使って、フィールド値変更時イベントでの処理を作っていきましょう。

全体構造は次のようになります。

() => {
    'use strict';
    const chkboxCode = '行複製';  // テーブル内のチェックボックスフィールドコードを指定(コピーボタン)
    const tableCode = 'Table';    // テーブルのフィールドコードを設定

    const events = [
        'app.record.create.change.' + chkboxCode,
        'app.record.edit.change.' + chkboxCode
    ];

    kintone.events.on(events, (event) => {
        // ここに処理を書く
    });
})();

3.押されたボタンの判定と複製行データの取得

これは、フィールド個別に値変更時イベントが発生するので、このイベントが発火するということでボタンが押されたことは検知可能です。
次に、ボタンが押された行のデータ(オブジェクト)を取得する方法ですが、これも値変更時イベントでは、eventオブジェクトにありますね!
event.changes.row

テストアプリを作って、このオブジェクトの中身を確認してみると、

{
    "id""78114",
    "value": {
        "日付": {
            "type""DATE",
            "value""2022-10-12"
        },
        "文字列__1行__0": {
            "type""SINGLE_LINE_TEXT",
            "value""新幹線(東京→仙台)"
        },
        "行複製": {
            "type""CHECK_BOX",
            "value": [
                "▼"
            ]
        },
        "種別": {
            "type""DROP_DOWN",
            "value""交通費"
        },
        "金額": {
            "type""NUMBER",
            "value""10000"
        },
        "備考": {
            "type""SINGLE_LINE_TEXT"
        }
    }
}

となっていました。
event.changes.rowの中の、id には行のid、valueに行の中身のフィールドデータが入っていることがわかります。
このデータを使って、新規の行を追加するわけですが、新規の行ではまだidは設定されていないため、登録データのidは不要です。
なので、利用すべきデータは、event.changes.row.valueということがわかります。

4.更新用データの作成

テーブルの更新を行う場合、既存の行データも含めた全行のデータを使う必要があります(ココは間違いやすいので注意です。既存行が消えてしまうミスをやりがち)。
値変更時イベント発生時点の既存テーブル行データも、eventオブジェクトに入っています。
event.record['Table'].value の中身を確認すると、各行のデータが配列形式で格納されていることがわかります。

{
    "type""SUBTABLE",
    "value": [
        {
            "id""78114",
            "value": {
                "日付": {
                    "type""DATE",
                    "value""2022-10-12"
                },
                "文字列__1行__0": {
                    "type""SINGLE_LINE_TEXT",
                    "value""新幹線(東京→仙台)"
                },
                "行複製": {
                    "type""CHECK_BOX",
                    "value": [
                        "▼"
                    ]
                },
                "種別": {
                    "type""DROP_DOWN",
                    "value""交通費"
                },
                "金額": {
                    "type""NUMBER",
                    "value""10000"
                },
                "備考": {
                    "type""SINGLE_LINE_TEXT"
                }
            }
        },
        {
            "id""78119",
            "value": {
                "日付": {
                    "type""DATE",
                    "value""2022-10-13"
                },
                "文字列__1行__0": {
                    "type""SINGLE_LINE_TEXT",
                    "value""地下鉄"
                },
                "行複製": {
                    "type""CHECK_BOX",
                    "value": []
                },
                "種別": {
                    "type""DROP_DOWN",
                    "value""交通費"
                },
                "金額": {
                    "type""NUMBER",
                    "value""480"
                },
                "備考": {
                    "type""SINGLE_LINE_TEXT"
                }
            }
        }
    ]
}

今回の新規行はテーブルの一番下に挿入することにしますので、配列に要素を追加する「push」を使うことにします。

// コピー挿入するテーブル行のデータ作成
const addRowValue = {
    id: null,
    valueevent.changes.row.value
};

// 指定された行のデータを既存のテーブル最下行に挿入
event.record[tableCode]['value'].push(addRowValue);

新規に挿入する行データは、id: null, valueにはボタンが押された行のvalueオブジェクトを設定します。

既存のテーブルデータは event.record[tableCode].valueですから、そこにコピー行のデータをpushします。

5.テーブルの更新(行の複製反映)

ここまでの処理で、eventオブジェクトの書き換えが完了しています。
あとは、eventオブジェクトを return することで、フィールドの値を書き換えることができます。

( () => {
    'use strict';
    const chkboxCode = '行複製';  // テーブル内のチェックボックスフィールドコードを指定(コピーボタン)
    const tableCode = 'Table';    // テーブルのフィールドコードを設定

    const events = [
        'app.record.create.change.' + chkboxCode,
        'app.record.edit.change.' + chkboxCode
    ];

    kintone.events.on(events, (event) => {
        // コピー挿入するテーブル行のデータ作成
        const addRowValue = {
            id: null,
            valueevent.changes.row.value
        };

        // 指定された行のデータを既存のテーブル最下行に挿入
        event.record[tableCode]['value'].push(addRowValue);

        return event;
    });
})();

6.その他の処理

基本的な行コピーの処理はこれで完成しました。
テスト用のアプリで、ここまでの動きを確認して、間違いがないかをチェックしておきましょう。

ここからは、より実践的な処理を追加していきます。
チェックボックスをボタンとして活用していますので、押された後のチェックは、クリアーする処理を追加することで、よりボタンっぽい挙動にすることができます。

// チェックボックスのクリアー
event.changes.field.value = [];

return event; の前に、チェックボックスのクリアー処理を入れておきましょう。
対象となるチェックボックスは、値変更されたフィールドですから、event.changes.fieldで取得することができます。このvalueに[]を入れておきます。
チェックボックスは複数個の選択が可能なフィールドなので、valueは配列の構造となっています。今回は1つしか選択肢がありませんが、構造は配列であることに注意が必要です。

もう1つ。行の複製処理は、チェックボックスの値が変更されたときに動作します。一度入ったチェックはクリアーされるので、通常の画面操作においては、チェックが入った状態になることはありません。
しかし、ファイルから読み込まれたレコードやREST APIで作られたレコードでは、チェックボックスが入った状態となることも考えられるので、処理が動く条件としては、チェックボックスのチェックが入った時に限定しておくほうがいいと考えました。

処理としては、チェックが外された場合(値変更後のvalueが無い=配列の要素がゼロ)は、処理を行わずにreturnして抜けるようにしました。

// チェックボックスのチェックが外された場合は処理キャンセル(例外)
if (event.changes.field.value.length === 0) {
    return event;
}

これを、イベント発生後の処理の先頭に入れておきます。

他にも、自然な操作感を出すために考えておくことがあります。
この行複製操作は、新規レコード画面と編集画面で利用しますが、その他の画面(詳細画面、印刷画面)では、チェックボックスが見えていると不自然です。

// 詳細画面、印刷画面ではチェックボックス(コピーボタン)は非表示とする処理
const showEvents = [
    'app.record.detail.show',
    'app.record.print.show'
];
kintone.events.on(showEvents, (event) => {
    kintone.app.record.setFieldShown(chkboxCode, false);
    return event;
});

そこで、チェックボックスフィールドの非表示処理を入れておきます。
フィールドの表示/非表示

まとめ

はい。ということで、今回はテーブルの行を複製するというカスタマイズを、kintoneのフィールド:チェックボックスをボタン化するというアイデアによって、極力シンプルなカスタマイズとして作ることができました。

こうして振り返ってみると、カスタマイズの出来は、カスタマイズの方針を考えるところでほぼ決まっていることが分かります。

チェックボックスをボタン化したので、カッコいい自作のボタンを置くことはできませんでしたが、実際の業務では十分利用することができるアプリを作ることができました。
また、標準のイベントを利用していますし、DOM要素を使った非推奨のカスタマイズも行っていません。イイコトばっかりです!

今回の教訓

kintone SIGNPOSTの2-14「基本機能から考える」は、カスタマイズにも適用できる考え方だなと改めて思いました。


いただいたサポートは、今後とも有益な情報を提供する活動資金として活用させていただきます! 対価というよりも、応援のキモチでいただけたら嬉しいです。