kintoneの「一覧をリアルタイムで更新したい」,「同レコードを複数人同時に編集したい」と感じることはありませんか?
Amazon API GatewayのWebSocket APIを利用すると,他ユーザーの操作をリアルタイムで検知して,上述機能を作成できます.
しかも超簡単,お手頃価格で.
デモ
一覧をリアルタイム更新するデモです.
左がレコード一覧を見ている人,右がレコードを追加編集している人です.
右の人がレコード操作すると,左の人の一覧が勝手に更新されます.
実装方法
WebSocketサンプルアプリのデプロイ
AWSがWebSocketのサンプルアプリを用意してくれているので,そのまま利用します.
AWSアカウントがない方は,AWS アカウント作成の流れを参考に作成してください.
アカウントを作成したら,simple-websockets-chat-appにアクセスします.
アクセスしたら,右上にあるDeployボタンをクリックします.
Deployボタンを押すと,simple-websockets-chat-appの設定とデプロイ画面に遷移します.
右下の方にある,「このアプリがカスタム IAM ロールとリソースポリシーを作成することを承認します。」にチェックを入れ,デプロイボタンを押します.
デプロイボタンを押すとデプロイが開始します.
WebSocket URLのメモ
デプロイが完了したらWebSocket URLをメモします.
ヘッダーの検索バーからAPI Gatewayを検索してクリックします.
API Gatewayを開いたら,「SimpleChatWebSocket」をクリックします.
サイドバーのダッシュボードをクリックして,ページ上部に表示されたWebSocket URLをメモします.
kintoneの設定
カスタマイズビューHTML(一覧名: リアルタイム更新)
<div id="async-view"></div>
JavaScript
下記を順に読み込みます.
- https://js.cybozu.com/kintone-rest-api-client/1.12.1/KintoneRestAPIClient.min.js
- https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/js/iziToast.min.js
- AsyncTable.js
- sample.js
・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;
}
設定は以上です.
気が向いたら,同レコードを複数人同時に編集の記事も書こうかと思います.