見出し画像

テーブル内に行番号を表示して任意の行をコピーする

やりたいこと

Kinotneアプリでテーブル内データの指定した行を複製するフォームとボタンを作成してみました。
これは、テーブル内の入力データを同じ内容で何度も入力する手間を少しでも省くために行コピー機能を実装したモノです。
コピーする行を指定するために、行の先頭に行Noも表示する様にしました。

デモ画面

デモ画面では、テーブル内のデータの行番号を指定して「行複製」ボタンをクリックすることで、指定した行と同じ内容の行を下に追加します。
レコードの新規登録と編集画面で動作します。
行追加ボタンで新しい行を挿入すると、行Noに初期値(又は空白)が設定されますが、レコード保存イベントで行Noを先頭行から振り直しています。

テーブルの行コピーのデモ画面

フォームの設定

フォームの設定では、オレンジ枠のフィールド名(フィールドコード)及びフィールド型を以下の様に設定して下さい。

  • 明細テーブル(明細テーブル):テーブル

  • 行No(行No):数値型・・・テーブルの行番号表示用、初期値1

  • 複製行No(複製行No):数値型、初期値1

  • スペース(要素ID=Button_Space

※上記フィールドの( )内のフィールドコード名は、Javascriptコードの「初期設定」のフィールドコード設定内容と合わせる必要があります。
※テーブル内のその他フィールド設定は自由(初期値の設定が推奨)です


サンプルのJavascriptコード

初期設定の/ フィールドコードを、kintoneフォームのフィールドコード名と合わせる様にして下さい。
BUTTON_TEXT: '行複製' は、ボタンの表示名です。
BUTTON_MARGIN_TOP: '35px' は、ボタンのトップマージンです。

【テーブル内フィールドの初期値について】
コードを簡略化するために、複製する行のフィールドが未定義(未入力)のチェック処理は省略していますので、テーブル内の任意のフィールドには、何かの初期値(数字なら0,日付ならレコード作成日、文字列ならガイド文字など)を設定しておいて下さい。
※初期値の設定が無いと、新規レコード登録画面や行追加の新しい行を対象にした複製処理でエラーが発生します。

/* テーブルの任意の行をコピーする */
(function() {
    "use strict";

    // 初期設定
    const CONFIG = {
        TABLE_FIELD: '明細テーブル',
        ROW_NUMBER_FIELD: '行No',
        DUPLICATE_ROW_FIELD: '複製行No',
        BUTTON_SPACE_ID: 'Button_Space',
        BUTTON_TEXT: '行複製',
        BUTTON_MARGIN_TOP: '35px'
    };

    function getTableData(record) {
        return record[CONFIG.TABLE_FIELD].value || [];
    }

    function setTableData(record, tableData) {
        record[CONFIG.TABLE_FIELD].value = tableData;
    }

    function renumberRows(tableData) {
        tableData.forEach((row, index) => {
            row.value[CONFIG.ROW_NUMBER_FIELD].value = index + 1;
        });
    }

    function duplicateRow(event) {
        const record = kintone.app.record.get().record;
        const copyRowNo = record[CONFIG.DUPLICATE_ROW_FIELD].value;
        let tableData = getTableData(record);

        if (copyRowNo < 1 || copyRowNo > tableData.length) {
            alert('指定された行番号が存在しません。有効な行番号を入力してください。');
            return;
        }

        //階層型オブジェクトの完全コピー処理(元データとの参照関係の切り離し)
        const newRow = JSON.parse(JSON.stringify(tableData[copyRowNo - 1]));
        tableData.push(newRow);

        renumberRows(tableData);
        setTableData(record, tableData);
        kintone.app.record.set({ record: record });
    }

    kintone.events.on(['app.record.create.show', 'app.record.edit.show'], function(event) {
        const space = kintone.app.record.getSpaceElement(CONFIG.BUTTON_SPACE_ID);

        if (space) {
            const button = document.createElement('button');
            button.textContent = CONFIG.BUTTON_TEXT;
            button.style.marginTop = CONFIG.BUTTON_MARGIN_TOP;
            button.addEventListener('click', duplicateRow);
            space.appendChild(button);
        }
    });

    kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], function(event) {
        const record = event.record;
        let tableData = getTableData(record);

        renumberRows(tableData);
        setTableData(record, tableData);
        return event;
    });
})();

カスタマイズした感想

本カスタマイズを実装した経緯は、出張経費申請アプリで、電車代の入力や宿泊ホテル名等の出張経費の明細入力が面倒なので、簡単にコピー出来る様にして欲しいという内容でした。
行項目のコピー&ペーストで良いのでは?と思いましたが、テーブル操作のカスタイマイズの練習になると思いましたので取り組んでみました。

今回工夫した点は、テーブル行の読み込み、書き戻し、行Noの番号振り直しを共通関数化したこと、テーブルの行データを配列に取り込んでから操作して最後に配列の内容をテーブルに書き戻す様にしていることです。
こうすることで処理の記述方法の柔軟性と保守のし易さが向上しています(と思っていますw)。

また、オブジェクトの階層構造まで含めた深いコピー(ディープコピー)を実行するために、JSON.parse(JSON.stringify(オブジェクト変数)) という処理方法を用いています。これは、階層化されたオブジェクトをJSON文字列化してから再オブジェクト化することで、完全に新しいメモリ位置にコピーし、元のオブジェクトとの参照関係を完全に切り離します。こうすることで、コピー元のオブジェクトに一切影響を与えずにテーブル行データの完全コピーを実現しています。
テーブル行のコピー処理には他の方法もありますが、コピー元に影響しないで完全コピーするには、この方法が一番簡潔でしたという結論※です。
※実は一番悩んだ部分です。もっと良い方法が有れば教えて欲しいです。

他のアプリでも、テーブルの任意の行を複製したいというニーズが有れば、簡単に応用できると思います。
テーブルの行操作のカスタマイズ手段は、奥が深いですね。

今回も最後まで読んで頂いて、ありがとうございました。

よろしければサポートお願いします! いただいたサポートは、note記事制作の活動費に使わせていただきます!