kintone UI Component のDialogコンポーネントを使って入力補助を作成してみた！

# はじめに
この記事は「[kintone Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kintone)」の22日目の記事です！

サイボウズの開発チームが提供してくれている超絶イケてる「**[kintone Rest API Client](https://github.com/kintone/js-sdk/tree/master/packages/rest-api-client)**」「**[kintone UI Compoment](https://kintone-ui-component.netlify.app/ja/)**」を使ったことありますか？？

弊社は、最近の主流である **"ローコード開発""ノーコード開発"** に抗うよう(笑)に、ガッツリとコードを書くような開発をしておりますので、この2つのライブラリにはメチャクチャお世話になっています！！

kintoneでJavaScriptを使ったカスタマイズをする人で、上記2つのライブラリを試した事のない方は、この記事を参考にぜひチャレンジしてみてください！！

# まずは完成イメージ
![2022-12-21_23h24_31.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319512/19c7998c-41f3-2139-ba1b-327a90446b0c.gif)

コレとコレとコレを選んで、エイってすると一気にバババッて入ってほしいなぁって思ったことあるでしょ？ね？ありますよね？？
(擬音語多いな…)

kintoneのルックアップって、他のアプリの情報を取得してきたりする非常に重要かつ強力な機能だけども、何行分もポチポチするのは結構めんどくさいのよね。

そんなご不便をバシッと解決するカスタマイズをやってみました！！


# アプリの構成
### 書籍マスタ
| フィールドコード | 特記事項 |
|---|---|
| 書籍コード | 必須、重複禁止 |
| 書籍名 | 必須 |
| 著者 |  |
| 貸出状況 |  |

* **閲覧権限あり**のAPIトークンを生成しておきましょう



### 貸出記録
| フィールドコード | 特記事項 |
|---|---|
| 貸出日 | 必須、登録日を自動セット |
| 返却日 |  |
| 利用者名 | 必須 |
| sp_show_book_list | スペースフィールド |
| 書籍コード | 書籍マスタをルックアップ |
| 書籍名 | 書籍マスタからルックアップ後にコピー |
* 「JavaScript / CSSでカスタマイズ」に下記5つを読み込む
    * jQuery
        * **[Cybozu CDN](https://developer.cybozu.io/hc/ja/articles/202960194-Cybozu-CDN)** から最新のURLをセットしましょう。
    * kintone-rest-api-client
        * **[Cybozu CDN](https://developer.cybozu.io/hc/ja/articles/202960194-Cybozu-CDN)** から最新のURLをセットしましょう。
    * kintone UI Component
        * **[Github](https://github.com/kintone-labs/kintone-ui-component/releases)** から最新のファイルを取得してセットしましょう。
    * カスタマイズ用のJavaScriptファイル
        * book_record.js
    * カスタマイズ用のCSSファイル
        * book_record.p.css


# コードの説明
### 登録画面と編集画面に「書籍リスト」ボタンを設置
```javascript
kintone.events.on([
  'app.record.create.show', 'app.record.edit.show'
], async event => {
  const record = event.record;

  const sp_show_book_list = kintone.app.record.getSpaceElement('sp_show_book_list');
  const btn_show_book_list = new Kuc.Button({
    type: 'submit',
    text: '書籍リスト表示',
    id: 'btn_show_book_list',
    className: 'btn_show_book_list',
  });
  btn_show_book_list.addEventListener("click", async () => {

  });
  sp_show_book_list.append(btn_show_book_list);

  return event;
});
```


### 書籍マスタのデータを取得
```javascript
// ------------------------------------
// データ取得
// ------------------------------------
const bookRecords = await(new KintoneRestAPIClient({
  auth: { apiToken: 'xxxxxxxxxxxxxxxxxxxxxxx' }
})).record.getAllRecords({
  app: 999,
  condition: '貸出状況 in ("未貸出")'
}).then(resp => {
  console.log(resp);
  return resp;
}).catch(error => {
  console.log(error);
  return Promise.reject(new Error('GET : 書籍マスタ / ' + error.message));
});
console.log(bookRecords);
```

### まずはシンプルなDialog表示

ファーストステップとして、kintone UI ComponentのDialogコンポーネントを使って、シンプルなダイアログを表示してみます。

![2022-12-22_00h44_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319512/a9824857-cdcc-f5bb-c5bc-f2f883a150ed.png)

```javascript
// ------------------------------------
// Dialog > Contents 用
// ------------------------------------
const divContents = $('<div>').attr({ id: 'divContents' });

// ------------------------------------
// Dialog > Footer 用
// ------------------------------------
const divFooter = $('<div>').attr({ id: 'divFooter' });

const btnOk = new Kuc.Button({
  type: 'submit',
  text: 'OK',
  id: 'btnOk',
  className: 'btnOk'
});
btnOk.addEventListener('click', event => {
  console.log('btnOk');
  console.log(event);
  dialog.close();
});
divFooter.append(btnOk);

divFooter.append($('<span>').text('　'));

const btnCancel = new Kuc.Button({
  type: 'submit',
  text: 'Cancel',
  id: 'btnCancel',
  className: 'btnCancel'
});
btnCancel.addEventListener('click', event => {
  console.log('btnCancel');
  console.log(event);
  dialog.close();
});
divFooter.append(btnCancel);

const dialog = new Kuc.Dialog({
  title: '書籍リスト',
  content: divContents.get(0),
  footer: divFooter.get(0),
  icon: ''
});
dialog.addEventListener('close', event => {
  console.log(event);
});
dialog.open();
```

pure JavaScriptで書いてもいいのですが、jQueryの方が書きやすかったので、jQueryで書いています。Vue等を使いたかったら適宜読み替えていただければと。

ただ、jQuery使う中でハマったのは、
```javascript
content: divContents.get(0),
footer: divFooter.get(0),
```
の部分。
HTML文字列にするときの処理の書き方が分からなかった…。
(アドバイスをくれた、[ミケちゃん](https://twitter.com/tomo63ko)サンクス！！)


### Dialogのcontent部分に書籍マスタの情報をtableとして表示

```javascript
// ------------------------------------
// Dialog > Contents 用
// ------------------------------------
const divContents = $('<div>').attr({ id: 'divContents' });

const elmTable = $('<table>').addClass('tblContents');
const elmHeader = $('<thead>');
const elmBody = $('<tbody>');

// Table Header
elmHeader.append($('<tr>'));
elmHeader.append($('<td>').addClass('th_chk').text('　'));
elmHeader.append($('<td>').addClass('th_code').text('書籍コード'));
elmHeader.append($('<td>').addClass('th_name').text('書籍名'));
elmHeader.append($('<td>').addClass('th_author').text('著者'));

// Table Body
bookRecords.forEach(v => {
  const elmTr = $('<tr>');

  elmTr.append($('<td>').addClass('td_chk').append($('<input type="checkbox">').attr({ name: 'target', id: 'target_' + v.$id.value, value: v.書籍コード.value })));
  elmTr.append($('<td>').addClass('td_code').text(v.書籍コード.value));
  elmTr.append($('<td>').addClass('td_name').text(v.書籍名.value));
  elmTr.append($('<td>').addClass('td_author').text(v.著者.value));

  elmBody.append(elmTr);
});
divContents.append(elmTable.append(elmHeader).append(elmBody));
```
この辺りは、書いてあるそのままではあるのだけども、ちょっと工夫したのは1列目にチェックボックスの列を表示していて、そのvalueに「書籍コード」をセットしている所。
こうすることで、この値をルックアップにセットしやすくなって、自動ルックアップに繋げることができます。


### OKボタンでDialogを閉じた時の処理
OKボタン押下でDialogを閉じた時に、チェックをつけているものをテーブルに追加して、自動ルックアップを実行する
```javascript
const btnOk = new Kuc.Button({
  type: 'submit',
  text: 'OK',
  id: 'btnOk',
  className: 'btnOk'
});
btnOk.addEventListener('click', event => {
  console.log('btnOk');
  console.log(event);

  const targets = [];

  // すべてのチェック済みvalue値を取得する
  $('input[name="target"]:checked').each(function () {
    targets.push($(this).val());
  });
  console.log(targets);

  // 値のセット
  const objRecord = kintone.app.record.get();
  const setTableValue = targets.map(v => {
    return {
      value: {
        書籍コード: { type: 'SINGLE_LINE_TEXT', value: v },
        書籍名: { type: 'SINGLE_LINE_TEXT', value: '' }
      }
    }
  });
  console.log(setTableValue);

  objRecord.record.貸出リスト.value = setTableValue;

  // ルックアップ自動取得
  objRecord.record.貸出リスト.value.forEach(v => v.value.書籍コード.lookup = 'UPDATE');

  kintone.app.record.set(objRecord);

  dialog.close();
});
divFooter.append(btnOk);
```

### ファイル全体はこちらから見てください。
<details><summary>book_record.js</summary><div>

```javascript
//=============================================================================
//【ファイル名】
//    book_record.js
//【アプリ名】
//    書籍貸出記録
//-----------------------------------------------------------------------------
//  Copyright (c) 2022 AISIC.Inc
//=============================================================================

(() => {
  'use strict';

  kintone.events.on([
    'app.record.create.show', 'app.record.edit.show'
  ], async event => {
    const record = event.record;

    const sp_show_book_list = kintone.app.record.getSpaceElement('sp_show_book_list');
    const btn_show_book_list = new Kuc.Button({
      type: 'submit',
      text: '書籍リスト表示',
      id: 'btn_show_book_list',
      className: 'btn_show_book_list',
    });
    btn_show_book_list.addEventListener("click", async () => {
      try {

        // ------------------------------------
        // データ取得
        // ------------------------------------
        const bookRecords = await (new KintoneRestAPIClient({
          auth: { apiToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' }
        })).record.getAllRecords({
          app: 999,
          condition: '貸出状況 in ("未貸出")'
        }).then(resp => {
          console.log(resp);
          return resp;
        }).catch(error => {
          console.log(error);
          return Promise.reject(new Error('GET : 書籍マスタ / ' + error.message));
        });
        console.log(bookRecords);

        // ------------------------------------
        // Dialog > Contents 用
        // ------------------------------------
        const divContents = $('<div>').attr({ id: 'divContents' });

        const elmTable = $('<table>').addClass('tblContents');
        const elmHeader = $('<thead>');
        const elmBody = $('<tbody>');

        // Table Header
        elmHeader.append($('<tr>'));
        elmHeader.append($('<td>').addClass('th_chk').text('　'));
        elmHeader.append($('<td>').addClass('th_code').text('書籍コード'));
        elmHeader.append($('<td>').addClass('th_name').text('書籍名'));
        elmHeader.append($('<td>').addClass('th_author').text('著者'));

        // Table Body
        bookRecords.forEach(v => {
          const elmTr = $('<tr>');

          elmTr.append($('<td>').addClass('td_chk').append($('<input type="checkbox">').attr({ name: 'target', id: 'target_' + v.$id.value, value: v.書籍コード.value })));
          elmTr.append($('<td>').addClass('td_code').text(v.書籍コード.value));
          elmTr.append($('<td>').addClass('td_name').text(v.書籍名.value));
          elmTr.append($('<td>').addClass('td_author').text(v.著者.value));

          elmBody.append(elmTr);
        });
        divContents.append(elmTable.append(elmHeader).append(elmBody));

        // ------------------------------------
        // Dialog > Footer 用
        // ------------------------------------
        const divFooter = $('<div>').attr({ id: 'divFooter' });

        const btnOk = new Kuc.Button({
          type: 'submit',
          text: 'OK',
          id: 'btnOk',
          className: 'btnOk'
        });
        btnOk.addEventListener('click', event => {
          console.log('btnOk');
          console.log(event);

          const targets = [];

          // すべてのチェック済みvalue値を取得する
          $('input[name="target"]:checked').each(function () {
            targets.push($(this).val());
          });
          console.log(targets);

          // 値のセット
          const objRecord = kintone.app.record.get();
          const setTableValue = targets.map(v => {
            return {
              value: {
                書籍コード: { type: 'SINGLE_LINE_TEXT', value: v },
                書籍名: { type: 'SINGLE_LINE_TEXT', value: '' }
              }
            }
          });
          console.log(setTableValue);

          objRecord.record.貸出リスト.value = setTableValue;

          // ルックアップ自動取得
          objRecord.record.貸出リスト.value.forEach(v => v.value.書籍コード.lookup = 'UPDATE');

          kintone.app.record.set(objRecord);

          dialog.close();
        });
        divFooter.append(btnOk);

        divFooter.append($('<span>').text('　'));

        const btnCancel = new Kuc.Button({
          type: 'submit',
          text: 'Cancel',
          id: 'btnCancel',
          className: 'btnCancel'
        });
        btnCancel.addEventListener('click', event => {
          console.log('btnCancel');
          console.log(event);
          dialog.close();
        });
        divFooter.append(btnCancel);

        const dialog = new Kuc.Dialog({
          title: '書籍リスト',
          content: divContents.get(0),
          footer: divFooter.get(0),
          icon: ''
        });
        dialog.addEventListener('close', event => {
          console.log(event);
        });
        dialog.open();

      } catch (error) {
        console.log(error);
      };
    });
    sp_show_book_list.append(btn_show_book_list);

    return event;
  });

})();
```
</div></details>

<details><summary>book_record.p.css</summary><div>

```css
.tblContents {
  width: 800px;
}


.th_chk {
  border: solid 1px;
  width: 50px;
  background-color: #7fffd4;
}

.th_code {
  font-weight: bold;
  text-align: center;
  border: solid 1px;
  width: 150px;
  background-color: #7fffd4;
}

.th_name {
  font-weight: bold;
  text-align: center;
  border: solid 1px;
  width: 500px;
  background-color: #7fffd4;
}

.th_author {
  font-weight: bold;
  text-align: center;
  border: solid 1px;
  width: 150px;
  background-color: #7fffd4;
}



.td_chk {
  text-align: center;
  border: solid 1px;
  width: 50px;
}

.td_code {
  border: solid 1px;
  width: 150px;
}

.td_name {
  border: solid 1px;
  width: 500px;
}

.td_author {
  border: solid 1px;
  width: 150px;
}
```
</div></details>



# ダウンロードはこちら
今回のカスタマイズにかかる一式は **[コチラ](https://github.com/AISIC-DL/advent_calendar_2022)** にアップしています。
kintoneアプリのアプリテンプレートも置いておりますので、必要に応じてダウンロードしてお使いくださいませ。
**※各自の責任の上でご使用ください。何らかの不具合が発生したとしても、当方は責任を取れませんので、よろしくお願いします。**


# さいごに
今回ご紹介したカスタマイズは、かなり実践的なカスタマイズと言えるかなぁと思います。
とはいえ、実開発においては、もっと色々と考慮しないといけない部分が沢山ありますので、**まるっとコピペはご遠慮くださいね。**

「**[kintone Rest API Client](https://github.com/kintone/js-sdk/tree/master/packages/rest-api-client)**」「**[kintone UI Component](https://kintone-ui-component.netlify.app/ja/)**」を積極的に活用いただいて、kintoneのカスタマイズにチャレンジしてみてください！

近々、kintone UI Component のNewリリースもあるようで、**『テーブル』** やら **『添付ファイル』** のコンポーネントが追加になるとの噂がっ！！
ますます目が離せませんな！！