LoginSignup
5
2

More than 1 year has passed since last update.

Amazon API Gatewayを使ってリアルタイムkintone

Last updated at Posted at 2021-04-26

kintoneの「一覧をリアルタイムで更新したい」,「同レコードを複数人同時に編集したい」と感じることはありませんか?
Amazon API GatewayのWebSocket APIを利用すると,他ユーザーの操作をリアルタイムで検知して,上述機能を作成できます.

しかも超簡単,お手頃価格で.

デモ

一覧をリアルタイム更新するデモです.

左がレコード一覧を見ている人,右がレコードを追加編集している人です.
右の人がレコード操作すると,左の人の一覧が勝手に更新されます.

demo.gif

実装方法

WebSocketサンプルアプリのデプロイ

AWSがWebSocketのサンプルアプリを用意してくれているので,そのまま利用します.
AWSアカウントがない方は,AWS アカウント作成の流れを参考に作成してください.

アカウントを作成したら,simple-websockets-chat-appにアクセスします.

アクセスしたら,右上にあるDeployボタンをクリックします.
スクリーンショット 2021-04-26 22.13.04.png

Deployボタンを押すと,simple-websockets-chat-appの設定とデプロイ画面に遷移します.
右下の方にある,「このアプリがカスタム IAM ロールとリソースポリシーを作成することを承認します。」にチェックを入れ,デプロイボタンを押します.
スクリーンショット 2021-04-26 22.15.22.png
デプロイボタンを押すとデプロイが開始します.

WebSocket URLのメモ

デプロイが完了したらWebSocket URLをメモします.

ヘッダーの検索バーからAPI Gatewayを検索してクリックします.
スクリーンショット 2021-04-26 22.29.30.png

API Gatewayを開いたら,「SimpleChatWebSocket」をクリックします.
スクリーンショット 2021-04-26 22.32.00.png

サイドバーのダッシュボードをクリックして,ページ上部に表示されたWebSocket URLをメモします.
スクリーンショット 2021-04-26 22.36.09.png

kintoneの設定

カスタマイズビューHTML(一覧名: リアルタイム更新)

<div id="async-view"></div>

JavaScript

下記を順に読み込みます.

・AsyncTable.js

class AsyncTable {
  constructor(columns) {
    this.columns = columns;
    this.table = document.createElement('table');
    this.thead = document.createElement('thead');
    this.tbody = document.createElement('tbody');
    this.table.appendChild(this.thead);
    this.table.appendChild(this.tbody);
    this.table.classList.add('async-table');
  }
  getTable () {
    return this.table;
  }
  setFormFields (formFields) {
    this.thead.innerHTML = '';
    const tr = document.createElement('tr');
    tr.innerHTML = '<th>$id</th>';
    this.columns.forEach((column) => {
      const th = document.createElement('th');
      th.innerText = formFields.properties[column].label;
      tr.appendChild(th);
    });
    this.thead.appendChild(tr);
  }
  createRowContent (record) {
    const fragment = document.createDocumentFragment();
    this.columns.forEach((column) => {
      const td = document.createElement('td');
      td.innerText = record[column].value;
      fragment.appendChild(td);
    });
    return fragment;
  }
  createRow (record) {
    const id = record.$id.value;
    const tr = document.createElement('tr');
    tr.id = `record-${id}`;
    tr.innerHTML = `<td><a href="show#record=${id}">${id}</a></td>`;
    tr.appendChild(this.createRowContent(record));
    return tr;
  }
  updateRow (record) {
    const id = record.$id.value;
    const tr = document.getElementById(`record-${id}`);
    tr.innerHTML = `<td><a href="show#record=${id}">${id}</a></td>`;
    tr.appendChild(this.createRowContent(record));
    return tr;
  }
  focusRow (tr) {
    tr.classList.add('focused');
    setTimeout(() => tr.classList.remove('focused'), 5000);
  }
  setInitialRecords (records) {
    const _this = this;
    this.tbody.innerHTML = '';
    const fragment = document.createDocumentFragment();
    records.forEach((record) => {
      fragment.appendChild(_this.createRow(record));
    });
    this.tbody.appendChild(fragment);
  }
  addRecord (record) {
    const tr = this.tbody.insertBefore(this.createRow(record), this.tbody.firstChild);
    this.focusRow(tr);
  }
  updateRecord (record) {
    const tr = this.updateRow(record);
    this.focusRow(tr);
  }
}

・sample.js
2行目の「WebSocket URL」と6行目の「一覧に表示するフィールドのフィルドコード」は適宜変更してください.

(() => {
  const url = 'wss://*****.execute-api.ap-northeast-1.amazonaws.com/Prod/'; // WebSocket URL
  kintone.events.on([
    'app.record.index.show'
  ], (event) => {
    if (event.viewName !== 'リアルタイム更新') return event;
    const columns = [// 一覧に表示するフィールドのフィルドコード
      '品名',
      '値段',
    ];
    const containerId = 'async-view';
    const client = new KintoneRestAPIClient();
    const asyncTable = new AsyncTable(columns);
    kintone.Promise.all([
      client.app.getFormFields({ app: kintone.app.getId() }).then((formFields) => {
        asyncTable.setFormFields(formFields);
      }),
      client.record.getAllRecordsWithCursor({ app: kintone.app.getId() }).then((records) => {
        asyncTable.setInitialRecords(records);
      }),
    ]).then(() => {
      document.getElementById(containerId).appendChild(asyncTable.getTable());
    });
    const socket = new WebSocket(url);
    socket.addEventListener('message', (e) => {
      const sentEvent = JSON.parse(e.data);
      if (sentEvent.type === 'app.record.create.submit.success') {
        iziToast.info({
          title: 'レコードが追加されました',
          message: `$id: ${sentEvent.record.$id.value}`,
        });
        asyncTable.addRecord(sentEvent.record);
      }
      if (sentEvent.type === 'app.record.edit.submit.success') {
        iziToast.info({
          title: 'レコードが更新されました',
          message: `$id: ${sentEvent.record.$id.value}`,
        });
        asyncTable.updateRecord(sentEvent.record);
      }
    });
    return event;
  });
  kintone.events.on([
    'app.record.create.submit.success',
    'app.record.edit.submit.success'
  ], (event) =>
      new kintone.Promise((resolve) => {
        const socket = new WebSocket(url);
        socket.addEventListener('open', () => {
          socket.send(`{"action": "sendmessage", "data": ${JSON.stringify(JSON.stringify(event))}}`);
          resolve(event);
        });
      })
  );
})();

CSS

下記を順に読み込みます.

・AsyncTable.css

.async-table{
  width: 100%;
}
.async-table tbody tr:nth-child(2n+1){
  background: #f5f5f5;
}
.async-table th,
.async-table td{
  border: 1px solid #e3e7e8;
  padding: 10px;
  font-weight: normal;
  text-align: left;
}
.async-table tbody tr.focused{
  background: #9ddeff;
}

設定は以上です.

気が向いたら,同レコードを複数人同時に編集の記事も書こうかと思います.

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2