【kintone】アプリ同士のデータを連結してCSV出力する

##はじめに
kintoneのアプリ同士のデータを連結してCSV出力する方法について紹介します！

関連レコードを用いてCSV出力する方法はcybozu developer networkにて紹介されています。こちらのコードを基に自分でカスタマイズを行いました。
[関連レコードのデータをCSV出力する方法
](https://developer.cybozu.io/hc/ja/articles/203605844-%E9%96%A2%E9%80%A3%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92CSV%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)

今回は関連レコードを用いず、別アプリのテーブルにて複数登録されているデータとの連結方法について紹介を行っていきます。

##アプリの構成
### サンプル１アプリ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/f641fdcb-5ee6-6d32-87ca-924fee197074.png)
サンプルアプリ１の申請NOを基にサンプル２アプリとデータの紐づけを行います。

### サンプル２アプリ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/c0adb2a3-6281-83b7-f0b7-03deb7f12468.png)
サンプル１アプリの申請NOをテーブルにて複数登録できるようにしています。
テーブルのデータを取得しやすいようにカンマ区切りで管理用の項目に値を保持します。

カンマ区切りの保存方法はこちらです:information_desk_person_tone1:

```javascript
  kintone.events.on([
    'app.record.create.submit',
    'app.record.edit.submit'
  ], function (event) {
    let record = event.record;
    // サンプル１の申請NOをカンマ区切りでつなげて保存するように設定する
    const tableLength = record.テーブル.value.length;
    let sample1ApplyNo = '';
    for (let i = 0; i < tableLength; i++) {
      // カンマで連結
      if (i > 0) {
        sample1ApplyNo += ',';
      }
      sample1ApplyNo
        += record.テーブル.value[i].value.サンプル１の申請NO.value;
    }
    record.サンプル１の申請NO_管理用.value = sample1ApplyNo;
    return event;
  });
```

##画面イメージ（サンプル１アプリ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/28cdceb1-f1bf-5adb-8c0a-d158f49f84c2.png)
一覧画面にCSV出力ボタンを表示させます。色は左のプルダウンの部分と統一させました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/f360486b-b37b-5b38-f909-3715790f168f.png)
ボタンの上にカーソルを乗せた際には色を変化させます。

上記のボタンのCSSはこちらです:information_desk_person_tone1:

```css
#btn-export-csv {
    background: #3498db;
    color: #ffff;
    padding: 2px 10px;
    width: 140px;
    border-radius: 3px;
    border: 4px solid #3498db;
    height: 40px;
    border-bottom: 1px solid rgba(0,0,0, .25);
    box-shadow: 0 1px 3px rgb(0 0 0 / 50%);
}

#btn-export-csv:hover {
    background: #1d6fa5;
    border: 4px solid #1d6fa5;
}
```

## 連結するコード
こちらコードはサンプル１アプリに登録します。

```javascript
(function () {
  'use strict';
  // 紐づけるサンプル２アプリのアプリID
  const appId = 1;

  /*
  * 一覧画面表示イベント
  */
  kintone.events.on('app.record.index.show', function (event) {
      const records = event.records;
      // ボタンの有無をチェック
      if (document.getElementById('btn-export-csv')) {
          return;
      }
      
      // ボタンを作成
      const spaceEl = kintone.app.getHeaderMenuSpaceElement();
      const cSVButtonEl = document.createElement('button');
      cSVButtonEl.textContent = 'CSV出力';
      cSVButtonEl.id = 'btn-export-csv';
      spaceEl.appendChild(cSVButtonEl);

      // ボタン押下時のイベント
      cSVButtonEl.addEventListener('click', function () {
          try {
              makeLinkedCsv(appId, records).then(function (resp) {
                  downloadFile(resp);
              });
          } catch (error) {
              alert('CSV出力でエラーが発生しました  - error: ' + e.message);
              return false;
          }
      });
  });

  /*
  * CSVデータの作成
  */
  function makeLinkedCsv(appId, sample1Records) {
      const header = [
          'サンプル１の申請NO',
          '担当者',
          '件名',
          'サンプル２の申請NO',
          '担当者',
          '紐づくサンプル１の申請NO',
          '件名'
      ].join(',');
      let csvData = [header];

      return new kintone.Promise(function (resolve, reject) {
          linkData().then(function (resp) {
              csvData = csvData.concat(resp);
              resolve(csvData);
          });
      });

      /*
      * 同じサンプル１アプリの申請NOを持つサンプル２アプリのレコードを連結
      */
      function linkData(index, data) {
          // CSVデータに追加するサンプル１の情報
          const sample1RowIndex = index || 0;
          const sample1Record = sample1Records[sample1RowIndex];
          // 生成済みのCSVデータ
          const detailRows = data || [];
          // サンプル１の申請NO
          const sample1ApplyNo = sample1Record.申請NO.value;
          // サンプル１の申請NOを基に該当関連レコードを部分一致検索で取得
          const query = 'サンプル１の申請NO_管理用 like "' + sample1ApplyNo + '"';
          const params = {
              app: appId,
              query: query
          };
          return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params)
              .then(function (resp) {
                  let row = [];
                  // サンプル１のレコード情報
                  const sample1Cols = [
                    sample1ApplyNo,
                      sample1Record.担当者.value,
                      sample1Record.件名.value.replace(',', '，')
                  ];

                  // 空のサンプル２のレコード情報
                  const sample2Cols = [
                      '',
                      '',
                      '',
                  ];

                  // 紐づくレコード情報
                  const relatedRecords = resp.records;
                  // サンプル１の申請NOに紐づくレコ―ドが存在する場合
                  let targetSample2Count = 0;
                  if (relatedRecords.length > 0) {
                      for (let i = 0; i < relatedRecords.length; i++) {
                          // サンプル２情報のサンプル１の申請NOリストを配列に変換
                          let sample1ApplyNoList =
                              relatedRecords[i].サンプル１の申請NO_管理用.value.split(',');
                          if (sample1ApplyNoList.includes(sample1ApplyNo)) {
                              // サンプル２情報のサンプル１の申請NOリストに同一の申請NOが存在した場合
                              // 出力対象となるサンプル２情報の件数をインクリメント
                              targetSample2Count += 1;
                              // サンプル１情報を追加
                              row.push(sample1Cols);
                              // サンプル２情報を追加
                              row.push([
                                  relatedRecords[i].申請NO.value,
                                  relatedRecords[i].担当者.value,
                                  sample1ApplyNo,
                                  // 半角カンマを全角に変換
                                  relatedRecords[i].件名.value.replace(',', '，'),
                              ]);
                              detailRows.push(row.join(','));
                              // 一度空にする
                              row = [];
                          }
                      }

                      if (targetSample2Count === 0) {
                          // 対象のサンプル２情報が1件も存在しない場合
                          row = sample1Cols.concat(sample2Cols);
                          detailRows.push(row.join(','));
                      }
                  } else {
                      // 対象のサンプル２情報が取得できなかった場合
                      row = sample1Cols.concat(sample2Cols);
                      detailRows.push(row.join(','));
                  }
                  // 連結していないサンプル１の情報があれば処理を継続する
                  if (sample1Records.length > sample1RowIndex + 1) {
                      return linkData(sample1RowIndex + 1, detailRows);
                  }
                  return detailRows;
              });
      }
  }

  /*
  * ファイルのダウンロード処理
  */
  function downloadFile(data) {
      const csv = data.join('\r\n');
      // ファイル名
      const filename = getFileName();

      // Blob準備
      const bom = new Uint8Array([0xef, 0xbb, 0xbf]);
      const blob = new Blob([bom, csv], { type: 'text/csv' });

      if (window.navigator.msSaveBlob) {
          window.navigator.msSaveBlob(blob, filename);
      } else {
          const e = new MouseEvent('click', { view: window, bubbles: true, cancelable: true });
          const url = window.URL || window.webkitURL;
          const blobUrl = url.createObjectURL(blob);
          const a = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
          a.href = blobUrl;
          a.download = filename;
          a.dispatchEvent(e);
      }
  }

  /*
  * ファイル名の取得
  * @return yyyymmdd_連結.csv
  */
  function getFileName() {
      const d = new Date();
      const YYYY = d.getFullYear();
      // 10未満の場合は数字の前に0を付ける
      const MM = (d.getMonth() + 1).toString().padStart(2, '0');
      const DD = d.getDate().toString().padStart(2, '0');
      return YYYY + MM + DD + '_連結.csv';
  }
})();
```

## 操作方法
1、サンプル1アプリにてデータを絞り込み、CSV出力ボタンを押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/51f7fd73-f20f-fa64-e15c-93fc549b50b2.png)

2、指定したファイル名でCSVファイルが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/9d7d8176-3e6b-3f3a-5a8c-fbf9167afa84.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/9b960820-7a59-957b-08cc-a96a3d5839b9.png)
このような感じで出力されました！！



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700107/8ccddbcd-d60b-6566-35fe-e9c4243096a1.png)
複数件データを検索しても問題無く出力できます。ただ、一覧画面に表示しているデータのみしか取得できないため、より多く出力する場合には表示件数を変更する必要があります。

##さいごに
アプリ同士でデータの連結をしようとしている方の参考になれば幸いです:point_up_tone1: