Розроблення користувацьких віджетів у Appsmith
Appsmith — це відкритий вихідний код конкурент Retool з подібною моделлю кастомізації. Custom Widget у Appsmith — це також iframe з postMessage, але API дещо відрізняється. Тоді як Retool використовує npm-пакет з гаками, Appsmith надає глобальний об'єкт appsmith прямо в контексту iframe.
Механізм Custom Widget
Користувацький віджет у Appsmith отримує дані через appsmith.model, надсилає события через appsmith.triggerEvent і оновлює стан через appsmith.updateModel. Все це синхронізується з основною програмою через postMessage без необхідності встановлювати SDK.
Віджет підключається як HTML-сторінка — можна писати ванільний JS прямо в редакторі Appsmith або задати зовнішній URL із зібраним бандлом.
Вбудований редактор проти зовнішнього бандлу
Для простих компонентів — код прямо у Appsmith:
<!-- Вбудований HTML у редакторі Custom Widget -->
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
</head>
<body>
<canvas id="chart"></canvas>
<script>
let chart = null;
function initChart(data) {
const ctx = document.getElementById('chart').getContext('2d');
chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: data.map(d => d.label),
datasets: [{
data: data.map(d => d.value),
backgroundColor: data.map(d => d.color),
}],
},
options: {
responsive: true,
onClick: (event, elements) => {
if (elements.length > 0) {
const idx = elements[0].index;
appsmith.triggerEvent('onSegmentClick', { item: data[idx] });
}
},
},
});
}
// Ініціалізація при завантаженні
appsmith.onReady(() => {
initChart(appsmith.model.items || []);
});
// Реакція на зміну даних
appsmith.onModelChange((model) => {
if (chart) {
chart.data.labels = model.items.map(d => d.label);
chart.data.datasets[0].data = model.items.map(d => d.value);
chart.update();
}
});
</script>
</body>
</html>
Для складних компонентів з React та TypeScript — зовнішній бандл:
// src/Widget.tsx
import { useEffect, useState } from 'react';
declare global {
interface Window {
appsmith: {
model: Record<string, unknown>;
onReady: (cb: () => void) => void;
onModelChange: (cb: (model: Record<string, unknown>) => void) => void;
triggerEvent: (name: string, payload?: unknown) => void;
updateModel: (updates: Record<string, unknown>) => void;
};
}
}
interface ScheduleItem {
id: string;
title: string;
start: string;
end: string;
resourceId: string;
}
export function SchedulerWidget() {
const [items, setItems] = useState<ScheduleItem[]>([]);
const [resources, setResources] = useState([]);
useEffect(() => {
window.appsmith.onReady(() => {
const model = window.appsmith.model;
setItems((model.items as ScheduleItem[]) || []);
setResources((model.resources as []) || []);
});
window.appsmith.onModelChange((model) => {
setItems((model.items as ScheduleItem[]) || []);
setResources((model.resources as []) || []);
});
}, []);
const handleEventDrop = (item: ScheduleItem, newStart: string, newEnd: string) => {
const updated = { ...item, start: newStart, end: newEnd };
window.appsmith.triggerEvent('onEventReschedule', { item: updated });
window.appsmith.updateModel({ lastAction: { type: 'reschedule', item: updated } });
};
return (
<FullCalendarWrapper
items={items}
resources={resources}
onEventDrop={handleEventDrop}
/>
);
}
Побудова для зовнішнього розгортання
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
rollupOptions: {
input: 'index.html',
},
},
base: './',
});
index.html у dist розгортається на Vercel/Netlify/S3 із публічним доступом. У Custom Widget Appsmith вказуєте URL на index.html.
Передача подій та двостороння синхронізація
// Оновлення стану віджета з коду Appsmith (через Model binding)
// У полі "Default Model" у налаштуваннях віджета:
{
"items": "{{fetchData.data}}",
"selectedId": "{{appState.selectedItem}}"
}
// У віджеті — реакція на зміну selectedId
appsmith.onModelChange((model) => {
highlightItem(model.selectedId);
});
// З віджета — оновити глобальний стан Appsmith
appsmith.updateModel({ selectedId: clickedItem.id });
// Це значення доступне як Widget.model.selectedId у інших запитах та віджетах
Різниці з Retool в розробці
У Retool можна використовувати npm-пакети через bundler безперебійно. У Appsmith вбудований редактор не має npm — тільки CDN. Для серйозних компонентів завжди потрібен зовнішній бандл. Однак Appsmith повністю з відкритим вихідним кодом — може бути самостійно розгорнутим без обмежень ліцензії, і користувацькі віджети будуть працювати однаково у хмарі та при самостійному розгортанні.
Типові задачі
Найчастіші запити: Gantt-планувальник ресурсів (FullCalendar + користувацький вигляд ресурсів), карти теплових виділень активності (D3 або ECharts heatmap), вбудований редактор складних конфігів JSON (Monaco Editor), геоаналітика (Mapbox GL з фільтрами з Appsmith).
Часові шкали
Простий компонент на Chart.js або аналогічній з передачею даних та однією подією — 1–2 дні. Компонент з двостороннією синхронізацією стану, користувацьким стилем та складною інтерактивністю — 3–5 днів. Важкий планувальник або сітка даних з персистентністю — 1–2 тижні.







