Skip to content

Application State and Persistence

Edit a task, save the project to browser memory, change it again, then load the saved snapshot. The transport is an in-memory example; wire your own backend for durable storage.

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(); };
}

Your application owns persisted project data. Gantt owns scheduling, validation, projection, and supported local mutations while the grid is mounted.

Keep these collections together as one logical project snapshot:

  • source: authored task rows.
  • ganttDependencies.
  • ganttCalendars.
  • ganttResources.
  • ganttAssignments.
  • ganttBaselines.
  • gantt: project metadata and behavior configuration.

When a server response or collaborative update arrives, replace affected arrays and assign a new gantt object when configuration changed. Do not mutate an existing array in place and expect framework or custom-element change detection to observe it.

function applyProject(snapshot: ProjectSnapshot) {
grid.gantt = { ...snapshot };
grid.ganttCalendars = [...snapshot.calendars];
grid.ganttDependencies = [...snapshot.dependencies];
grid.ganttResources = [...snapshot.resources];
grid.ganttAssignments = [...snapshot.assignments];
grid.ganttBaselines = [...snapshot.baselines];
grid.source = snapshot.tasks.map(toSourceRow);
}
  1. Listen for the Gantt before-change events when the application needs authorization or validation.
  2. Allow Gantt to commit supported local mutations.
  3. Read the current project through the GanttPlugin instance or the application state updated by your integration.
  4. Persist a complete snapshot or an application-defined patch.
  5. Replace the public collections with the canonical server result when the server normalizes data.

Use JSON project helpers for snapshots and integration recipes for REST, GraphQL, PostgreSQL, and Supabase-style persistence.

  • Use stable entity IDs across every collection.
  • Version project writes in your backend and reject stale updates.
  • Re-run scheduling after dependency, calendar, task, resource, or assignment updates.
  • Keep optimistic rollback data outside the rendered DOM.
  • Use gantt.readOnly while a project is locked or awaiting approval.

See Events And Methods for cancelable events and plugin access.