В этой статье описывается настройка серверной поддержки функции реального времени (Live Updates) в DHTMLX Scheduler.
Эта статья посвящена реализации режима Live Updates для DHTMLX Scheduler v7.2. Информацию о более ранних версиях смотрите здесь.
DHTMLX Scheduler включает вспомогательный модуль RemoteEvents
, который позволяет мгновенно синхронизировать изменения между несколькими пользователями.
RemoteEvents
открывает WebSocket-соединение сразу после инициализации Scheduler.DataProcessor
с использованием REST API.RemoteEvents
получает эти обновления и применяет их к Scheduler, обеспечивая, чтобы все пользователи видели одинаковые данные.Такая схема поддерживает работу с несколькими виджетами DHTMLX (например, Kanban, Gantt, Scheduler) в одном приложении, используя общий формат, который упрощает синхронизацию и не требует отдельного бэкенда для каждого виджета.
Настройте RemoteEvents
и DataProcessor
вместе в той части кода, где загружаются данные Scheduler.
const AUTH_TOKEN = "token";
scheduler.init('scheduler_here', new Date(2025, 3, 20), "week");
scheduler.load("/events");
const dp = scheduler.createDataProcessor({
url: "/events",
mode: "REST-JSON",
headers: {
"Remote-Token": AUTH_TOKEN
}
});
const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);
RemoteEvents
требует токен авторизации, который отправляется в заголовке "Remote-Token" для проверки на сервере.WebSocket
(например, /api/v1).remoteUpdates
обрабатывает входящие сообщения по WebSocket
и поддерживает синхронизацию данных Scheduler.В этом разделе описано, как создать сервер, поддерживающий live-обновления.
Чтобы попробовать:
npm install
и npm run start
.Когда запускается RemoteEvents
, он отправляет GET-запрос на сервер для установки соединения.
Пример:
GET /api/v1
Remote-Token: AUTH_TOKEN
Ответ:
{"api":{},"data":{},"websocket":true}
После handshake RemoteEvents
открывает WebSocket-соединение, используя указанный endpoint.
Пример:
ws://${URL}?token=${token}&ws=1
Сервер проверяет токен и отвечает сообщением вида:
{"action":"start","body":"connectionId"}
Пример кода:
app.get('/api/v1', (req, res) => {
const token = req.headers['remote-token'];
if (!token || !verifyAuthHeader(token)) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json({ api: {}, data: {}, websocket: true });
});
wss.on('connection', (ws, req) => {
const token = new URLSearchParams(req.url.split('?')[1]).get('token');
if (!token || !verifyAuthToken(token)) {
ws.close(1008, 'Unauthorized');
return;
}
const connectionId = generateConnectionId();
ws.send(JSON.stringify({ action: 'start', body: connectionId }));
});
После подключения RemoteEvents
подписывается на обновления определённых сущностей — для Scheduler это events
:
{"action":"subscribe","name":"events"}
Чтобы прекратить получение обновлений:
{"action":"unsubscribe","name":"events"}
Такая схема отлично подходит для приложений, использующих несколько виджетов DHTMLX одновременно. Каждый виджет может подписываться только на нужные ему обновления.
Пример обработки на сервере:
ws.on('message', function(message) {
try {
const msg = JSON.parse(message);
const client = clients.get(connectionId);
if (!client) return;
if (msg.action === 'subscribe') {
client.subscriptions.add(msg.name);
} else if (msg.action === 'unsubscribe') {
client.subscriptions.delete(msg.name);
}
} catch (err) {
console.error('Error parsing WebSocket message:', err);
}
});
Сервер отправляет сообщения по WebSocket, чтобы уведомить клиентов о создании, изменении или удалении событий, следуя следующему формату.
Когда такие сообщения приходят, Scheduler использует helper remoteUpdates
для автоматического обновления данных.
Событие создано
{"action":"event","body":{"name":"events",
"value":{"type":"add-event","event":EVENT_OBJECT}}}
Пример:
app.post('/events', (req, res) => {
const newEvent = req.body.event;
const insertedEvent = crud.events.insert(newEvent);
// Уведомить всех клиентов о новом событии
const message = {
name: 'events',
value: {
type: 'add-event', event: insertedEvent
}
};
broadcast('event', message);
res.status(200).json({ id: insertedEvent.id });
});
function broadcast(action, body) {
const entity = body.name;
for (const [connectionId, client] of clients.entries()) {
const { ws, subscriptions } = client;
if (subscriptions.has(entity) && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action, body }));
}
}
}
Событие обновлено
{"action":"event","body":{"name":"events",
"value":{"type":"update-event","event":EVENT_OBJECT}}}
Пример:
app.put('/events/:id', (req, res) => {
const id = req.params.id;
const updatedEvent = req.body.event;
crud.events.update(id, updatedEvent);
// Уведомить клиентов об обновлении
const message = {
name: 'events',
value: {
type: 'update-event', event: updatedEvent
}
};
broadcast('event', message);
res.status(200).send();
});
Событие удалено
{"action":"event","body":{"name":"events",
"value":{"type":"delete-event","event":{"id":ID}}}}
Пример:
app.delete('/events/:id', (req, res) => {
const id = req.params.id;
crud.events.delete(id);
// Сообщить клиентам об удалении
const message = {
name: 'events',
value: {
type: 'delete-event',
event: { id }
}
};
broadcast('event', message);
res.status(200).send();
});
Вспомогательный модуль RemoteEvents
отвечает за начальный handshake и WebSocket-соединение, а helper remoteUpdates
— за обработку входящих сообщений и обновление данных Scheduler.
const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);
Обычно этих helper-ов достаточно "из коробки". Однако, при необходимости можно расширить протокол, добавив собственные обработчики или helper-ы для специфических сценариев удалённых обновлений.
Метод RemoteEvents.on
принимает объект, в котором можно определить обработчики для одной или нескольких сущностей:
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on({
events: function(message) {
const { type, event } = message;
switch (type) {
case "add-event":
// обработать добавление события
break;
case "update-event":
// обработать обновление события
break;
case "delete-event":
// обработать удаление события
break;
}
}
});
Чтобы обрабатывать пользовательские действия, можно добавить ещё один обработчик в remoteEvents
:
const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);
remoteEvents.on({
events: function(message) {
const { type, event } = message;
switch (type) {
case "custom-action":
// обработать пользовательское действие
break;
}
}
});
Этот обработчик будет вызван сообщениями вида:
{"action":"event","body":{"name":"events",
"value":{"type":"custom-action","event":value}}}
Чтобы получать обновления для пользовательских сущностей, добавьте соответствующий обработчик:
const { RemoteEvents, remoteUpdates } = scheduler.ext.liveUpdates;
const remoteEvents = new RemoteEvents("/api/v1", AUTH_TOKEN);
remoteEvents.on(remoteUpdates);
// подписка на пользовательские сущности
remoteEvents.on({
calendars: function(message) {
const { type, value } = message;
switch (type) {
case "custom-action":
// обработать пользовательское действие
break;
}
}
});
При такой настройке remoteEvents
отправит сообщение о подписке:
{"action":"subscribe","name":"calendars"}
А обработчик будет реагировать на сообщения вида:
{"action":"event","body":{"name":"calendars",
"value":{"type":"custom-action","value":value}}}
Это руководство описывает основы настройки и расширения live-обновлений в DHTMLX Scheduler. Для полного рабочего примера посетите репозиторий на GitHub.
Наверх