Skip to content

Integrations And Frameworks

Save and load a project through the labeled in-memory transport, or export JSON and import the file again. This demonstrates the snapshot boundary; REST, GraphQL, SQL, and Supabase source recipes below require your own service/client.

Source code
import { controlAttributes, createControl } from '@revolist/ui';
import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();
import {
defineGanttToolbar,
GanttPlugin,
GanttCalendarEditorPlugin,
type ProjectSnapshot,
} from '@revolist/gantt';
import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
SHOWCASE_ASSIGNMENTS,
SHOWCASE_BASELINES,
SHOWCASE_COLUMNS_WITH_ROW_SELECT,
SHOWCASE_DEPENDENCIES,
SHOWCASE_RESOURCES,
SHOWCASE_TOOLBAR_COLUMNS,
STANDARD_CALENDAR,
} from './shared/gantt-project-data';
import {
createProductionGanttConfig,
createProductionTasks,
createProductionPersistence,
productionError,
productionSourceRows,
} from './GanttProductionPatternsShared';
import './gantt-production-patterns.css';
export function load(parentSelector: string) {
const parent = document.querySelector(parentSelector);
if (!parent) return () => {};
const root = document.createElement('section');
const controls = document.createElement('div');
const status = document.createElement('p');
const toolbar = document.createElement('div');
const grid = document.createElement('revo-grid');
const persistence = createProductionPersistence();
let readOnly = false;
let localized = false;
let large = false;
let project: ProjectSnapshot | null = null;
const applyConfig = () => {
grid.gantt = createProductionGanttConfig(readOnly, localized, project);
};
const applyProject = () => {
applyConfig();
grid.ganttDependencies =
project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES);
grid.ganttCalendars = project?.calendars ?? [STANDARD_CALENDAR];
grid.ganttResources = project?.resources ?? SHOWCASE_RESOURCES;
grid.ganttAssignments =
project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS);
grid.ganttBaselines =
project?.baselines ?? (large ? [] : SHOWCASE_BASELINES);
grid.source = project
? productionSourceRows(project)
: createProductionTasks(large);
};
const toggle = (label: string, update: (value: boolean) => void) => {
const element = document.createElement('label');
const input = createControl(document, 'input', { type: 'checkbox' });
input.type = 'checkbox';
input.addEventListener('change', () => update(input.checked));
element.append(input, document.createTextNode(label));
return { element, input };
};
const run = async (action: () => unknown | Promise<unknown>) => {
try {
await action();
} catch (error) {
status.textContent = productionError(error);
}
};
const button = (label: string, action: () => unknown | Promise<unknown>) => {
const element = createControl(document, 'button', { type: 'button' });
element.type = 'button';
element.textContent = label;
element.className = controlAttributes('button', { class: 'rv-btn' }).class;
element.addEventListener('click', () => {
void run(action);
});
return element;
};
root.className = 'gantt-production-demo';
controls.className = 'gantt-production-controls';
status.className = 'gantt-production-status';
status.setAttribute('role', 'status');
toolbar.className = 'gantt-production-toolbar';
grid.className = 'gantt-production-grid';
grid.theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
grid.hideAttribution = true;
grid.plugins = [
GanttPlugin,
GanttCalendarEditorPlugin,
ExportExcelPlugin,
RowSelectPlugin,
];
grid.rowSelect = { rowOrder: true };
grid.columns = [...SHOWCASE_COLUMNS_WITH_ROW_SELECT];
const largeToggle = toggle('750 tasks', (value) => {
large = value;
project = null;
applyProject();
});
const restore = button('Restore', async () => {
project = await persistence.load();
large = false;
largeToggle.input.checked = false;
applyProject();
status.textContent =
'Restored the saved project, including tasks and project collections.';
});
restore.disabled = true;
const fileInput = createControl(document, 'input', { type: 'file' });
fileInput.type = 'file';
fileInput.hidden = true;
fileInput.accept = '.json,application/json';
fileInput.setAttribute('aria-label', 'Import JSON file');
fileInput.addEventListener('change', async () => {
const file = fileInput.files?.[0];
if (file)
await run(async () => {
project = await persistence.importFile(file);
large = false;
largeToggle.input.checked = false;
applyProject();
status.textContent = `Imported ${project.tasks.length} tasks from ${file.name}.`;
});
fileInput.value = '';
});
const options = document.createElement('div');
options.className = 'gantt-production-options';
const actions = document.createElement('div');
actions.className = 'gantt-production-actions';
options.append(
toggle('Read only', (value) => {
readOnly = value;
applyConfig();
}).element,
toggle('Español dates', (value) => {
localized = value;
applyConfig();
}).element,
largeToggle.element,
);
actions.append(
fileInput,
button('Import JSON', () => fileInput.click()),
button('JSON', () => persistence.exportFile(grid)),
button('Save', async () => {
await persistence.save(grid);
restore.disabled = false;
status.textContent =
'Saved the current edited project in browser memory.';
}),
restore,
button('Print', () => persistence.print(grid)),
);
controls.append(options, actions);
root.append(controls, status, toolbar, grid);
parent.appendChild(root);
applyProject();
defineGanttToolbar(toolbar, {
grid,
columns: SHOWCASE_TOOLBAR_COLUMNS,
controls: { baseline: true, calendars: true },
});
return () => {
toolbar.textContent = '';
grid.remove();
root.remove();
};
}

These recipes keep transport and framework ownership in your application. Inject fetch, state, database clients, DOM targets, and file readers/writers from your host app.

The application recipes below live in the examples source browser. Copy their recipes/ files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.

import {
cloneProjectSnapshot,
exportProjectSnapshot,
parseProjectSnapshotJson,
} from '@revolist/gantt';
import { ProjectSnapshotGraphqlAdapter } from './recipes/graphql-adapter';
import { ProjectSnapshotRestAdapter } from './recipes/rest-adapter';
import {
ProjectSnapshotSupabaseAdapter,
buildCreateProjectSnapshotTableSql,
buildLoadProjectSnapshotSql,
buildSaveProjectSnapshotSql,
} from './recipes/postgres-persistence';
import {
createJsonSnapshotRecipe,
createPrintPdfExportRecipe,
} from './recipes/persistence-examples';
import { printGanttContainer } from './recipes/print-pdf-recipe';

Use the published createGanttQueryAdapter with existing load(request) recipes for native polling, retries and shared requests. Hydrate complete projects with GanttPlugin.setProjectSnapshot(project, options) and capture custom row fields with getProjectData(). See Gantt query usage and configuration types and committed commands, revisions and compensating undo/redo. Existing save(project, request) callbacks remain application-owned.

Use JSON helpers for local import/export, file handoff, tests, undo buffers, or backend payloads.

const draft = cloneProjectSnapshot(project);
const json = exportProjectSnapshot(draft, { space: 2 });
const parsed = parseProjectSnapshotJson(json);
if (parsed.ok) {
applyProject(parsed.project);
}

For file picker flows:

const recipe = createJsonSnapshotRecipe({
readText: () => fileInputText,
writeText: (_fileName, contents) => saveTextFile(contents),
});
await recipe.save(project, 2);
const imported = await recipe.load();

ProjectSnapshotRestAdapter loads and saves serialized project snapshots through an injected fetchImpl.

const adapter = new ProjectSnapshotRestAdapter({
url: '/api/gantt/project/demo',
headers: { authorization: 'Bearer token' },
fetchImpl,
space: 2,
});
const project = await adapter.load();
await adapter.save(project);

Use the GraphQL adapter when the backend exposes project snapshots through operations.

const adapter = new ProjectSnapshotGraphqlAdapter({
url: '/graphql',
fetchImpl,
loadQuery: 'query LoadProject($id: ID!) { projectSnapshot(id: $id) }',
saveMutation:
'mutation SaveProject($id: ID!, $snapshot: String!) { saveProjectSnapshot(id: $id, snapshot: $snapshot) }',
});
const project = await adapter.load({ variables: { id: 'demo' } });
await adapter.save(project, { variables: { id: 'demo' } });

Use SQL builders with any PostgreSQL executor, or the Supabase-like adapter with a compatible client shape.

await executor.query(buildCreateProjectSnapshotTableSql());
await executor.query(buildSaveProjectSnapshotSql(project));
const result = await executor.query(buildLoadProjectSnapshotSql(project.id));
const adapter = new ProjectSnapshotSupabaseAdapter(supabaseLikeClient);
await adapter.save(project);
const restored = await adapter.load(project.id);

The print recipe prepares a Gantt container for browser printing. Users can choose Print or Save as PDF in the native dialog.

const result = printGanttContainer(host, {
document,
window,
metadata: {
title: project.name,
subtitle: `${project.tasks.length} tasks`,
range: { startDate: '2026-05-01', endDate: '2026-05-31' },
},
});

For a reusable command:

const printRecipe = createPrintPdfExportRecipe({
target: host,
document,
window,
});
printRecipe.exportProject(project, { label: 'May reporting window' });

Use the complete public-package workflow for your primary wrapper:

Svelte remains a secondary integration recipe. Use the custom-element approach from the Vanilla guide until its packaged wrapper and support contract match the four primary framework paths.

The Resource Planning demo shows a host-owned resource filter UI. Filtering updates grid.gantt.resourceFilterIds without reading rendered cells.

Resource filtering updates grid.gantt.resourceFilterIds:

grid.gantt = {
...project,
resourceFilterIds:
selectedResourceIds.length === allResourceIds.length
? undefined
: selectedResourceIds,
};