2026年7月23日にリリースされた「SpreadJS(スプレッドJS)」のバージョン「V19.1J」では、Excelのように複数のユーザーで同じワークブックを同時に編集できる「共同編集機能」が追加されました。これによりチームでのデータ編集や確認を同一画面でリアルタイムに実施できます。
※ 共同編集機能を組み込んだアプリケーションを配布するには「配布ライセンス共同編集機能アドオン」が必要です。ライセンスの詳細はこちらをご覧ください。
前回の記事では、SpreadJSの共同編集機能の概要と最小限の実装に続いてご紹介しました。
本記事では、共同編集機能において他ユーザーの選択しているセルの状態など表示できる、プレゼンス機能について解説します。
SpreadJSの共同編集機能におけるプレゼンス機能
プレゼンス機能は、他ユーザーが操作しているセルやセル範囲をリアルタイムに表示することで、誰がどこを編集しているのかを視覚的に把握できるようにします。これにより、作業状況を共有しながら効率的に編集を進められるほか、他ユーザーの編集内容に加えて作業位置も確認できるため、編集の重複や意図しない競合を抑えやすくなります。
この機能はjs-collaboration-presenceモジュールによって実装され、次の2つのnpmパッケージとしてカプセル化されています。js-collaboration-presenceの詳細については、ヘルプをご一読いただければと思います。
- サーバー用パッケージ
- @mescius/js-collaboration-presence
- クライアント用パッケージ
- @mescius/js-collaboration-presence-client
サーバー側のプレゼンスは、presenceFeature関数により次の機能を提供します。
- 状態のブロードキャスト
- クライアントが送信した状態を受信し、サーバー経由で他のクライアントにブロードキャストします。
- 状態管理
- 現在のルーム内にいるすべてのユーザーの最新の状態を維持します。
クライアント側のプレゼンスは、Presenceクラスにより状態(ユーザーが操作しているセルやセル範囲)を管理し、サーバーと通信します。このクラスはConnectionオブジェクトを介してサーバーへの接続を確立し、次の操作を行います。
- ローカルの状態(操作中のセルやセル範囲)を送信する
- 他のユーザーの状態の変更を監視する
- すべての共同編集者の状態をインターフェイスに反映する
前回のプロジェクトにプレゼンス機能を追加する
前回作成したプロジェクトに、他ユーザーが操作しているセルやセル範囲を共有するプレゼンス機能を追加します。
プロジェクトのファイル構成
ファイル構成は前回と同じです。バックエンドを構成するファイルはプロジェクトのルートに置き、フロントエンドを構成するものは「public」フォルダ内に配置します。
project/
├── server.js # サーバー側のコード
├── package.json # プロジェクト設定ファイル
├── webpack.config.js # Webpack設定ファイル
└── public/ # 公開リソース用ディレクトリ
├── client.js # クライアント側のコード(index.html用)
├── container.js # クライアント側のコード(iframe.html用)
├── iframe.html # エントリーポイントとなるHTML
└── index.html # SpreadJSを配置するHTMLプロジェクトの変更
前回はSpreadJSを配置している2つのHTMLを1つのページに組み込みましたが、今回は3つのHTMLを1つのページに組み込みます。
それでは、前回作成したプロジェクトを変更してプレゼンス機能を追加しましょう。前回のプロジェクトは以下からもダウンロードできます。
npmパッケージの追加インストール
プレゼンス機能を利用できるようにするため、次のnpmパッケージを追加します。@以降で使用するSpreadJSのバージョンを指定します。
npm install sqlite3 @mescius/js-collaboration-ot-sqlite@19.1.5
npm install @mescius/js-collaboration-presence@19.1.5
npm install @mescius/js-collaboration-presence-client@19.1.5サーバーのセットアップ
前回作成したserver.jsファイルを次のように変更します。
import express from 'express';
import http from 'http';
import { Server } from '@mescius/js-collaboration';
import * as OT from '@mescius/js-collaboration-ot';
import { type } from '@mescius/spread-sheets-collaboration';
import { presenceFeature } from '@mescius/js-collaboration-presence';
// typeを登録
OT.TypesManager.register(type);
const app = express();
const httpServer = http.createServer(app);
const server = new Server({ httpServer });
const port = 8080;
// ライセンスの設定
//server.LicenseKey = 'localhost環境以外では、ここにSpreadJSの共同編集機能が有効なサーバー用ライセンスキーを設定します';
// OTドキュメントサービスを初期化
const documentServices = new OT.DocumentServices();
server.useFeature(OT.documentFeature(documentServices));
server.useFeature(presenceFeature());
// publicディレクトリを静的リソースとして公開
app.use(express.static('public'));
// サーバーを起動
httpServer.listen(port, () => {
console.log(`Server listening on port ${port}`);
console.log(`http://127.0.0.1:${port}/iframe.html`);
});※ localhost環境以外では、共同編集機能が有効なサーバー用ライセンスキー(配布ライセンス共同編集機能アドオンが適用されたもの)を設定しない場合、ライセンス情報がないことなどを示すメッセージが表示されます。
サーバー側では、server.useFeature(presenceFeature());でプレゼンス機能をコラボレーションサーバーに追加しています。
クライアントのセットアップ
クライアント側では以下のファイルを変更します。
- iframe.html
- index.html
- container.js
- client.js
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>SpreadJSの共同編集機能</title>
<script src="iframe.js"></script>
<style>
iframe {
border: 1px solid silver;
}
</style>
</head>
<body>
<iframe id="iframe1" src="index.html" width=600 height=500></iframe>
<iframe id="iframe2" src="index.html" width=600 height=500></iframe>
<iframe id="iframe3" src="index.html" width=420 height=500></iframe>
</body>
</html>iframeを1つ追加して3つのページを一度に閲覧できるように変更します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<script src="index.js"></script>
</head>
<body>
<div id="username" style="border-radius:5px;color:white;margin-bottom:5px;padding:5px;"></div>
<div id="ss" style="width:95vw;height:95vh;border:1px solid silver;"></div>
</body>
</html>上記で追加したコードは、SpreadJSの上部にユーザー名を表示します。
window.onload = () => {
// iframeのリロード
window.setTimeout(() => {
const iframe1 = document.getElementById('iframe1');
iframe1.contentWindow.location.reload();
const iframe2 = document.getElementById('iframe2');
iframe2.contentWindow.location.reload();
const iframe3 = document.getElementById('iframe3');
iframe3.contentWindow.location.reload();
}, 500);
}ここでは、3番目のiframeをリロードするコードを追加しています。
import * as GC from '@mescius/spread-sheets'
import '@mescius/spread-sheets-resources-ja';
import '@mescius/spread-sheets-collaboration-addon';
import { Client } from "@mescius/js-collaboration-client";
import * as OT from "@mescius/js-collaboration-ot-client";
import { type, bind, bindPresence } from '@mescius/spread-sheets-collaboration-client';
import "@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css";
import { Presence } from "@mescius/js-collaboration-presence-client";
// ライセンスの設定
GC.Spread.Sheets.LicenseKey = 'ここにSpreadJSの共同編集機能が有効なライセンスキーを設定します';
const mockUsers = [
{ id: '0', name: "User1", color: "#0000ff"},
{ id: '1', name: "User2", color: '#008000'},
{ id: '2', name: "User3", color: '#9900cc'},
];
// typeを登録
OT.TypesManager.register(type);
window.onload = async () => {
// ワークブックの生成
const workbook = new GC.Spread.Sheets.Workbook('ss');
// カルチャの設定
GC.Spread.Common.CultureManager.culture('ja-jp');
// シートの設定
const sheet = workbook.getActiveSheet();
sheet.zoom(1.6);
// モックユーザー(iframe1〜3 に User1〜3 を割り当て)
const index = ['iframe1', 'iframe2', 'iframe3'].indexOf(window.frameElement.id);
const user = mockUsers[index];
// サーバーに接続してルームに参加
const conn = new Client().connect('room1');
const doc = new OT.SharedDoc(conn);
// プレゼンスを作成してバインド
const presence = new Presence(conn);
await bindPresence(workbook, presence, user);
// エラー監視
doc.on('error', (err) => {
console.error('error:', err);
});
await doc.fetch();
// ドキュメントが存在しない場合は初期化
if (!doc.type) {
await doc.create(workbook.collaboration.toSnapshot(), type.uri, {});
await bind(workbook, doc);
} else {
await bind(workbook, doc);
}
// ユーザー名の表示
const userName = document.getElementById("username");
userName.innerText = user.name;
userName.style.backgroundColor = user.color;
}※ 共同編集機能が有効なライセンスキー(配布ライセンス共同編集機能アドオンが適用されたもの)を設定しない場合、トライアル版を示すメッセージが表示されます。
上記では、プレゼンス機能に関するコードとユーザー名を表示するコードを追加しています。
編集状況を見やすくするためにWorksheetクラスのzoomメソッドでSpreadJSを1.6倍に拡大しています。また、userIdは、index.htmlを収納しているiframeのidから自動的に取得しています。これにより、すべてのiframeに重複しない異なるユーザーを設定できます。
プロジェクトのビルド
次のコマンドを実行してクライアントコードをバンドルします。
npm run buildプレゼンス機能の動作確認
以上でプロジェクトが完成したので、次のコマンドを実行してサーバーを起動しましょう。
npm run start続いて、ブラウザを開いて「http://127.0.0.1:8080/iframe.html」にアクセスします。
表示された3つのiframe.htmlにはそれぞれ異なるユーザーが設定されており、各ユーザーの操作状況が他のユーザーにリアルタイムで分かるようになっています。
今回作成したサンプルは以下よりダウンロード可能です。実際に動かしながら動作を確認したい方はぜひご利用ください。
さいごに
今回の記事では、SpreadJSの共同編集機能のプレゼンス機能についてご紹介しました。共同編集機能を利用することで、Webアプリケーション上にExcelライクな操作性を提供しながら、リアルタイム同期や編集状態の共有、ユーザーごとの権限設定などを実現できます。また、コラボレーションフレームワークにより大規模な業務アプリケーションにも柔軟に組み込むことが可能です。
製品サイトでは、SpreadJSの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。
また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。
