動的にフィールドを生成する方法(ご質問)

下記の通り、ルックアップで取得したsectionとpositionをキーとして別アプリを参照し、そこから取得した該当レコード数分の項番(文字列1行)、チェック項目(文字列複数行)、結果(ラジオボタン)を自動生成することは可能でしょうか?
可能な場合、具体的な実現方法についてご教示いただけないでしょうか。

(要件)
・初期画面は以下の通りで、No.1とNo.3のチェック行(項番、チェック項目、結果)は予め固定で定義されている。
・No.2のチェック行は初期画面では非表示となっている。

・①の通り、ルックアップで報告者name(B)を選択し、課section(Purchasing)と職位position(Staff member)が確定する。
・②の通り、課section(Purchasing)と職位position(Staff member)をキーとして別アプリを参照し、該当する3レコード分のチェック項目行(項番、チェック項目、結果)を参照元アプリの画面に自動生成する。
・次に③の通り、ヒットした3レコード分のチェック項目を、自動生成されたチェック項目欄に転記する。

このような処理を実現するにはどうすべきかご教示頂けると助かります。

レコード内にデータとして残す必要があるならば、サブテーブルを使えば良いのではないでしょうか。

ルックアップから反映されるフィールドのchangeイベントでレコード取得→結果の数だけサブテーブルの行を作成(サブテーブルはkintone.app.record.setFieldShownを使って非表示状態にし、レコード取得の結果が1以上の場合は表示)という動作をさせれば近い形になるかと思います

mls-hashimoto
ご助言ありがとうございました。
テーブルを活用しトライし、別アプリからの検索結果を転記するところまで確認できました。
その上で、以下2点の改善を加えたく、さらにご助言頂けると助かります。
・初期画面ではテーブルを非表示にしている。

・ルックアップで検索した結果を基にテーブルに転記してみた。

改善1:テーブルのヘッダーを非表示にする方法(コード)をご教示願います。
改善2:テーブルの左から2列のフィールドを編集不可にする方法(コード)をご教示願います。
→53~58行目で試していますがうまく行きません。

現時点のコードは下記の通りとなっております。

(() => {
  'use strict';

  const apid = 216;
  const events = [
      'app.record.create.change.section','app.record.edit.change.section',
      'app.record.create.change.position','app.record.edit.change.position',
    ];

  kintone.events.on(events, (event) => {
  const record = event.record;
 
  // ルックアップでクリアするとテーブルを削除かつ非表示にする。
  if (!record.name.value){
    kintone.app.record.setFieldShown('table', false);
  }else{ 
  // ルックアップで値を選択するとsection/positiobに対応するチェック項目を取得する。
  if ( ! record.section.value || ! record.position.value ) {
      return event ;
  }
    const body = {app: apid, query: 'section_c = "' + record.section.value + '" and position_c = "' + record.position.value + '"',fields: ['checkitem_c']};
    kintone.api(kintone.api.url('/k/v1/records', true), 'GET', body
    ,(resp) => {
    if ( resp.records.length === 0 ) {
      let message = '選択したsection/position[' + record.section.value + '/' + record.position.value + '] がマスターに存在しません';
      alert( message ) ;
      return ;
    }
      // テーブルを表示する。
      kintone.app.record.setFieldShown('table', true);
      // テーブルに取得したチェック項目を転記する。
      const current = kintone.app.record.get().record;
      current.table.value = [];
      for (let i = 0; i < resp.records.length; i++){
         current.table.value.push({
           value: {
             'n_2': {
               value: `2-${i+1}`,
               type: 'SINGLE_LINE_TEXT',
             },
             'item_2': {
                value: resp.records[i].checkitem_c.value,
               type: 'SINGLE_LINE_TEXT',
             },
             'radio_2': {
               value: ["OK"],
                type: 'RADIO_BUTTON',
             }
           }
         });
      }    
      kintone.app.record.set({record: current});
      // テーブルフィールドを編集不可にする。
      for (let i = 0; i < record.table.value.length; i++) {
        console.log('[ReflectCheckItem]record.table.value.length');
        record.table.value[i].value.n_2.disabled = true;
        record.table.value[i].value.item_2.disabled = true;
      }
      // テーブルボタンを非表示にする。
      [].forEach.call(document.getElementsByClassName("subtable-operation-gaia"), function(button){
          button.style.display = 'none';
      });
    }
    // 更新失敗→エラー表示、ログ出力
   ,(error) => {
     console.log( '[ReflectCheckItem]body:' + JSON.stringify( body ) ) ;
     console.log( '[ReflectCheckItem]error:' + JSON.stringify( error ) ) ;
     let errorMessage = error.message || error ;
      alert( errorMessage ) ;
    });
  }
  return event;
  });
})();

44key さま

改善1:テーブルのヘッダーを非表示にする方法(コード)
改善2:テーブルの左から2列のフィールドを編集不可にする方法(コード)

以下のような形になりますが、この場合(プラスマイナスのボタン以外でのサブテーブル行数変更=サブテーブルのchangeイベントを起こせない場合)のフィールドの編集不可設定については

         current.table.value.push({
           value: {
             'n_2': {
               value: `2-${i+1}`,
               type: 'SINGLE_LINE_TEXT',
disabled: true
           },

この部分で設定できます。その上でedit.showで各行に編集不可の処理をする必要があります(下のコードに記載してあります)。

サブテーブルのヘッダーを非表示にすることは簡単ですが、サブテーブル内のフィールドは幅(width)を持たずヘッダーの幅に依存しているため、そのままヘッダーを非表示にするとフィールドが潰れるようです。以下で他のフィールドと同じような見た目にできます。

また、detail.showだとレコードを表示→サブテーブルの値を順次描画といった動作をするため、detail.showイベントでそのままサブテーブルのDOMにアクセスできず、描画完了を待つ処理が必要になります(ここではasync/awaitを使うとreturn eventせず描画処理自体が止まるのでthenで繋ぐ必要があります)。

 

(() => {
    'use strict';

    let subTable = {
      class: 'subtable-xxxxxxx', // サブテーブルに振られている番号
      fieldCode: '' // サブテーブルのフィールドコード
    };
  let disabledField = ''; // サブテーブル内の無効化するフィールド

    let trDrawWait = (record, fieldCode, className) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                let body = document.getElementsByClassName(className)[0].firstChild.nextSibling?.childNodes;

                if (record[fieldCode].value.length === body.length && body[0].children?.length) {
                    resolve();
                } else {
                    resolve(trDrawWait(record, fieldCode, className));
                }
            }, 100);
        });
    };

    // サブテーブルのヘッダー削除
    kintone.events.on([
        'app.record.detail.show', 'app.record.create.show', 'app.record.edit.show'
    ], (event) => {
        let record = event.record;
        let head = document.getElementsByClassName(subTable.class)[0].firstChild.firstChild;
        let widths = [...head.children].map((th) => th.style.width);

        if (event.type.match(/detail/)) {
            trDrawWait(record, subTable.fieldCode, subTable.class).then(() => {
                let rows = [...document.getElementsByClassName(subTable.class)[0].firstChild.nextSibling.children];

                rows.forEach((tr) => {
                    tr.classList.add('row-gaia');

                    let columns = [...tr.children];

                  widths.forEach((width, index) => {
                        columns[index].firstChild.style.width = width;
                        columns[index].firstChild.style.marginBottom = '12px';
                    });
                });

                head.style.display = 'none';
            });
        } else {
            let row = document.getElementsByClassName(subTable.class)[0].firstChild.nextSibling.firstChild;
            let columns = [...row.children];
    
            widths.forEach((width, index) => {
                columns[index].style.width = width;
            });
            head.style.display = 'none';
        }

        return event;
    });

    // サブテーブル内のフィールドを無効化(編集開始時とボタンによる行の増減時)
    kintone.events.on([
        'app.record.create.show', 'app.record.edit.show',
        `app.record.create.change.${subTable.fieldCode}`, `app.record.edit.change.${subTable.fieldCode}`
    ], (event) => {
        let record = event.record;

        record[subTable.fieldCode].value.forEach((row) => {
            row.value[disabledField].disabled = true;
        });

        return event;
    });
})();

(追記)cssを忘れていました。プラスマイナスのボタン非表示は投稿されたコードだと全てのサブテーブルに反映されるため、特定のサブテーブルのみである場合は以下のようになります(cssの方が軽くなるかと思います)。

.subtable-xxxxxxx td{
  border: none;
}

.subtable-xxxxxxx .subtable-operation-gaia {
  display: none;
}

mls-hashimoto
ご助言頂いた3つのコードを実装させていただくことで改善1と改善2を実現することができました。
さらに改善3として、以前ご助言頂いた、他のフィールド縦幅を縦幅の一番大きい文字列(複数行)フィールドの縦幅に合わせる実装をサブテーブルのフィールドにも適用したいと思います。

固定領域(上図ではNo.1とNo.3)に対しては、以前ご助言頂いた内容を適用した下記コード内容で幅合わせができていますが、さらにサブフィードにも適用するには、どのようにコードを追加すべきかご助言いただけないでしょうか。

(() => {
    'use strict';

    let fieldElement = ['value-5542778', 'value-5542779'];

    kintone.events.on([
        'app.record.detail.show', 'app.record.create.show', 'app.record.edit.show', 'app.record.print.show'
    ], (event) => {
        let isDetail = event.type.match(/detail/);
        let isPrint = event.type.match(/print/);

        for (let i = 0; i < fieldElement.length; i++) {
            let textarea = document.getElementsByClassName(fieldElement[i])[0];
            let height = (isDetail || isPrint) ? textarea.clientHeight : textarea.firstChild.firstChild.clientHeight;
            let row = textarea.parentNode.parentNode;

            [...row.children].forEach((field) => {
                if (!field.getElementsByClassName('control-value-gaia').length) return;

                [...field.getElementsByClassName('control-value-gaia')[0].children].forEach((el) => {
                    if (el.parentNode.parentNode.className.match('control-multiple_line_text-field-gaia')) return;

                    if (el.className === 'input-text-outer-cybozu') {
                        el.firstChild.style.display = 'table-cell';
                        el.firstChild.style.verticalAlign = 'middle';
                        el.firstChild.style.height = `${height}px`;
                    } else if (el.className === 'userselect-cybozu') {
                        el.parentNode.style.display = 'table-cell';
                        el.parentNode.style.verticalAlign = 'middle';
                        el.parentNode.style.height = `${height}px`;
                    } else if (el.className === 'input-file-cybozu') {
                        el.style.display = 'table-cell';
                        el.style.verticalAlign = 'middle';
                        el.style.height = `${height - 29}px`;
                    } else {
                        el.style.display = 'table-cell';
                        el.style.verticalAlign = 'middle';
                        el.style.height = `${(isDetail || isPrint) ? (height - 8): (height - 6)}px`;
                    }
                });
            });
        };

        return event;
    });
})();

44key さま

以下のようになります(通常のフィールドとはDOMツリーの階層が違うので、別のスクリプトとして実装した方が良いでしょう)。

(() => {
    'use strict';

    let subTable = {
        class: 'subtable-xxxxxxx',
        fieldCode: 'テーブル'
    };
    let fieldElement = ['value-xxxxxxx'];

    let trDrawWait = (record, fieldCode, className) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                let body = document.getElementsByClassName(className)[0].firstChild.nextSibling?.childNodes;

                if (record[fieldCode].value.length === body.length && body[0].children?.length) {
                    resolve();
                } else {
                    resolve(trDrawWait(record, fieldCode, className));
                }
            }, 100);
        });
    };

    let fieldHeightChange = (isDetail) => {
        for (let i = 0; i < fieldElement.length; i++) {
            [...document.getElementsByClassName(fieldElement[i])].forEach((textarea) => {
                let height = (isDetail) ? textarea.clientHeight : textarea.firstChild.firstChild.clientHeight;
                let row = textarea.parentNode.parentNode.parentNode;
    
                [...row.children].forEach((td) => {
                    if (!td.getElementsByClassName('control-value-gaia').length) return;
    
                    let el = td.getElementsByClassName('control-value-gaia')[0].firstChild;

                    if (el.parentNode.parentNode.className.match('control-multiple_line_text-field-gaia')) return;

                    if (el.className === 'input-text-outer-cybozu') {
                        el.firstChild.style.display = 'table-cell';
                        el.firstChild.style.verticalAlign = 'middle';
                        el.firstChild.style.height = `${height}px`;
                    } else if (el.className === 'userselect-cybozu') {
                        el.parentNode.style.display = 'table-cell';
                        el.parentNode.style.verticalAlign = 'middle';
                        el.parentNode.style.height = `${height}px`;
                    } else if (el.className === 'input-file-cybozu') {
                        el.style.display = 'table-cell';
                        el.style.verticalAlign = 'middle';
                        el.style.height = `${height - 29}px`;
                    } else {
                        el.style.display = 'table-cell';
                        el.style.verticalAlign = 'middle';
                        el.style.height = `${(isDetail) ? (height - 8): (height - 6)}px`;
                    }
                });
            });
        };
    }

    kintone.events.on([
        'app.record.detail.show', 'app.record.create.show', 'app.record.edit.show', 'app.record.print.show'
    ], (event) => {
        let record = event.record;
        let isDetail = event.type.match(/detail/);
        let isPrint = event.type.match(/print/);

        if (isDetail || isPrint) {
            trDrawWait(record, subTable.fieldCode, subTable.class).then(() => {
                fieldHeightChange(true);
            });
        } else {
            fieldHeightChange(false);
        }

        return event;
    });
})();

ここまでやっておいてなんですが、今までのカスタマイズ含め、これらはkintoneのクラス名を使用したDOM操作になるので、今後のアップデート等で動作しなくなる可能性があります。当然、DOMの解析ができれば何も問題はないのですが、あまり推奨されている方法ではないことをあらかじめご了承下さい。

mls-hashimoto様
ご助言及びコードをご提示頂きありがとうございました。
下記の通り、サブテーブルと中央の文字列(複数行)の値を確認しコードに反映致しました。
<サブテーブルおよび文字列(複数行)の値>

<コードに反映>

(() => {
    'use strict';

    let subTable = {
        class: 'subtable-5542773',
        fieldCode: 'table'
    };
  let fieldElement = ['value-5542777'];    

動作を確認した結果、下記の通り、幅合わせができませんでした。

誠に申し訳ありませんが、ご確認いだけまでんしょうか。

44key さま

こちらはsetFieldShownで非表示から表示状態へ変えた時の挙動でしょうか?初めから表示状態であったとしても同じようになりますか?恐らくclientHeight(現在の表示域の高さ)が原因ではないかと思います。style.heightの参照に変える等でも良いかもしれません。

mls-hashimoto
ご報告した挙動に関係する流れは以下の通りとなります。
1)別アプリ(当該投稿でご助言頂いたテーブルヘッダーを非表示にするjsコード)に、kintone.app.record.setFieldShown(‘table’, false)を追加し、レコード追加画面の表示時、 サブテーブルを非表示
対象イベント:app.record.detail.show’, ‘app.record.create.show’, ‘app.record.edit.show’。
2)別アプリ(当該投稿で最初に例示したjsコード)で、kintone.app.record.setFieldShown(‘table’, true)により サブテーブルを表示 後、ルックアップで取得したデータをサブテーブルに転記。
対象イベント:app.record.create.change.section’, ‘app.record.edit.change.section’, ‘app.record.create.change.position’, ‘app.record.edit.change.position’。
3)今回アプリ(サブテーブル各行のフィールド縦幅を中央の文字列(複数行)に合わせるためのjsコード)が実行されたが、縦幅合わせができなかった。
対象イベント→app.record.detail.show’, ‘app.record.create.show’, ‘app.record.edit.show’, 'app.record.print.show

因みに、1)のアプリから、kintone.app.record.setFieldShown(‘table’, falseをコメントアウトして試すと以下の結果となりました。
<レコード追加画面表示時>

<ルックアップ検索結果をサブテーブルに転記時>

以上、取り急ぎ、ご報告申し上げます。ご確認頂きますようお願い申し上げます。

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。