Handsontable を使って kintone を Excel ライクに入力しよう その 2 - 基本編

著者名: 村濱 一樹 (External link) (kintone エバンジェリスト)

目次

はじめに

前回、 Handsontable (External link) を使って、Excel のように入力ができるような Tips を公開しました。
今回は、前回の閲覧・編集に加えて、レコードの追加・削除にも対応したいと思います。
前回記事 のコードをベースに修正していきますので前回記事も参照ください。

また、コードは、 GitHub mura-/kintone_handsontable.js (External link) に公開しています。
基本的に上記をコピーして必要な部分を書き換えれば動くとは思いますが、必要に応じてコードを参照しながら下記をお読みください。
viewId はカスタマイズビュー設定時の viewId を入力してください。

デモ環境

デモ環境 (External link) で実際に動作を確認できます。
ログイン情報は cybozu developer network デモ環境 で確認してください。

サンプルアプリの設定

前回と同様のアプリ設定を用います。
前回記事の 「kintone カスタマイズでの導入方法 - サンプルアプリの準備」 の項目を参考にしてください。

information

カスタマイズで利用している Handsontable は、v7.0.0 以降 MIT ライセンスではなくなりました。
このカスタマイズでは、 MIT (External link) ライセンスの v6.2.2 を利用しています( ライセンス表記 (External link) )。
v7.0.0 以降を利用する際は Handsontable の HP (External link) で有償ライセンスを購入し、ライセンス条件にしたがって利用してください。
詳細は、 Cybozu CDN ライセンス対応ガイド を参照してください。

コードの改修の解説

レコードの追加に対応する

前回の saveRecords メソッドを拡張し、追加・更新両方に対応します。
今回は、追加・更新のために bulkRequest を利用しました。

bulkRequest は、本来複数アプリへのレコード一括処理をするためにありますが、もちろん同じアプリにも使えます。
これを使うことで 1 回の API の呼び出しだけで kintone のレコード更新が済みますし、なにより追加・更新処理どちらかでエラーが有った場合はロールバックされるので安全です。
引数には、kintone のレコードデータ(records)と、Handsontable 上でセルが編集された際に取得できる配列(changeDatas)を指定することとします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// kintoneのレコード更新、追加用メソッド
const saveRecords = (records, changedDatas, callback, errorCallback) =>{
  const requests = [];
  const updateRecords = [];
  const insertRecords = [];
  let changedRows = [];

  // 変更されたセルの配列から、変更があった行だけ抜き出す
  for (let i = 0; i < changedDatas.length; i++) {
    changedRows.push(changedDatas[i][0]);
  }
  // 変更があった行番号の重複を排除
  changedRows = changedRows.filter((x, i, self) => {
    return self.indexOf(x) === i;
  });

  // 変更があった行から、レコード追加か変更かを判断し、クエリをつくる
  for (let i = 0; i < changedRows.length; i++) {
    if (records[changedRows[i]]['レコード番号'].value === null) {
      insertRecords.push(
        setParams(records[changedRows[i]])
      );
    } else {
      updateRecords.push({
        id: records[changedRows[i]]['レコード番号'].value,
        record: setParams(records[changedRows[i]])
      });
    }
  }

  // 更新用bulkRequest
  requests.push({
    method: 'PUT',
    api: '/k/v1/records.json',
    payload: {
      app: kintone.app.getId(),
      records: updateRecords
    }
  });

  // 追加用bulkRequest
  requests.push({
    method: 'POST',
    api: '/k/v1/records.json',
    payload: {
      app: kintone.app.getId(),
      records: insertRecords
    }
  });

  // bulkrequestで一括で追加、更新。
  // 失敗した場合はロールバックされる。
  kintone.api('/k/v1/bulkRequest', 'POST', {requests: requests},
    (resp) => {
      console.dir(requests);
      console.dir(resp);
      callback(resp);
    },
    (resp) => {
      errorCallback(resp);
    }
  );
};

レコードの削除に対応する

handsontable の設定

削除は、コンテキストメニュー(右クリックメニュー)から実行できるようにしたいと思います。
まず、handsontable のコンテキストメニューオプションを設定します。
デフォルトで機能はたくさんあるのですが、今回は行削除(remove_row)だけに対応します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// handsontable初期化
hot = new Handsontable(container, {
  // この時点ではdataは入力せず、あとから読み込ませるようにする。(データ更新時も再読み込みさせたいため)
  data: [],

  // 空白行
  minSpareRows: 10,

  // 表示したいカラム
  colHeaders: ['レコード番号', '会社名', '先方担当者名', '見込み時期', '確度', '製品名', '単価', 'ユーザー数', '小計'],

  // コンテキストメニュー(右クリックメニュー)を指定。今回は削除用メニューのみ。
  contextMenu: ['remove_row'],

  // 必要に応じてreadOnlyの指定ができます。
  columns: [
    {data: 'レコード番号.value', readOnly: true},
    {data: '会社名.value'},
    {data: '先方担当者名.value'},
    {data: '見込み時期.value'},
    {data: '確度.value'},
    {data: '製品名.value'},
    {data: '単価.value'},
    {data: 'ユーザー数.value'},
    {data: '小計.value', readOnly: true}
  ],
  // : 以下略
});
削除メソッドの作成

次に、削除用メソッド(deleteRecords)を作成します。
引数には、kintone のレコードデータ(records)と、削除する行(index)・削除できる行数(amount)を指定できるようにします。
削除するべきレコード番号を抽出して、削除 API を呼び出します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// kintoneのレコード削除用メソッド
const deleteRecords = (records, index, amount, callback, errorCallback) => {
  let i;
  const ids = [];
  for (i = index; i < index + amount; i++) {
    ids.push(records[i]['レコード番号'].value);
  }
  kintone.api('/k/v1/records', 'DELETE', {app: kintone.app.getId(), ids: ids},
    (resp) => {
      callback(resp);
    },
    (resp) => {
      errorCallback(resp);
    }
  );
};
削除メソッドの呼び出し

レコードを削除すると、Handsontable の beforeRemoveRow イベントが発行します。
beforeRemoveRow イベントの中で、deleteRecords メソッドを呼び出して、削除処理を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
hot = new Handsontable(container, {
  // : 省略
  beforeRemoveRow: (index, amount) => {
    // kintoneのレコードを削除する
    deleteRecords(hot.getSourceData(), index, amount,
      (deleteRecordsResp)=> {
        console.dir(deleteRecordsResp);
        getRecords((getRecordsResp)=> {
        // 削除後、データを再読み込み
          hot.loadData(getRecordsResp.records);
        });
      },
      (resp)=> {
        console.dir(resp);
      }
    );
  },
  // : 省略
});

データを同期する

データの乖離が起きないよう、追加・更新・削除のタイミングでデータを再同期します。

データ取得メソッドの作成

今回は特に検索条件を指定しませんが、次のように並び替えだけはレコード番号昇順にしてください。
降順だとレコードを追加した行が一番先頭に表示されてしまいます。
また、limit も 500 と指定することで 500 件まで表示できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// kintoneのレコード取得用メソッド
const getRecords = (callback, errorCallback) => {
  kintone.api('/k/v1/records', 'GET', {app: kintone.app.getId(), query: 'order by レコード番号 asc limit 500'},
    (resp) => {
      callback(resp);
    },
    (resp) => {
      errorCallback(resp);
    }
  );
};
追加・更新・削除時に同期する

次のように、追加・更新・削除時の callback メソッドに同期するメソッドを指定します。
Handsontable は loadData メソッド (External link) があり、引数に取得した kintone レコードデータを指定することで再同期できます。
また、同期することにより、フォーム設定画面で指定したデフォルト値が表示されたり、計算フィールドのレコードが計算されて表示されるなどのメリットもあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
hot = new Handsontable(container, {
  // : 省略
  afterChange: (change, source) => {
    // kintoneのレコードを更新、追加する
    saveRecords(hot.getSourceData(), change,
      (resp)=> {
        console.dir(resp);
        getRecords((saveRecordsResp)=> {
          // 更新後、データを再読み込み
          hot.loadData(saveRecordsResp.records);
        },
        (getRecordResp)=> {
          // レコード取得失敗時に呼び出される
          console.dir(getRecordResp);
        });
      },
      (resp)=> {
        // 更新・追加時に呼び出される
        console.dir(resp);
      }
    );
  }
});

データを定期的に同期する

追加・更新・削除時だけでなく、長く画面を開いていればデータに乖離が起こる場合もあります。
そこで、今回はデータを定期的に同期する処理もいれてみました。

1
2
3
4
5
6
7
8
9
// 定期的にkintone上のデータを再取得する
const autoload = () =>{
  getRecords((resp)=> {
    hot.loadData(resp.records);
  });
  setTimeout(()=> {
    autoload();
  }, 10000); // 10秒。APIの呼び出し数の上限があるので、必要に応じて変更してください。
};

注意点

  • 今回のコードにおいて表示できるデータ件数は 500 件まで、更新は 100 件までとなっています。コードをカスタマイズすることで、絞り込みやデータの条件を増やすことができます。
    • 表示できるデータ件数に制限があるため、今回のサンプルは 一覧の設定 (External link) の「ページネーションを表示する」に対応していません。
  • API の呼び出し回数は 1 アプリ 1 日あたり上限 10,000 回です。今回は同期処理をいれていますが、必要に応じて同期間隔を遅くしてください。開きっぱなしに注意してください。

最後に

Excel ライクな操作で前回の閲覧・編集に加えて、レコードの追加・削除ができるようになりました。
一括でデータを追加、更新したい場合や、削除したい場合に便利ですね。
Handsontable (External link) を利用した kintone スプレッドシートプラグイン を開発して公開もしています。
ぜひ活用ください。

information

この Tips は、2022 年 7 月版 kintone で動作を確認しています。