Skip to content

Events And Methods

Move a task toward the configured freeze window to exercise a cancelable before-change event. The custom row actions dispatch application events without installing methods on the grid.

Source code
TypeScriptts
import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();

import { GanttPlugin } from '@revolist/gantt';
import { currentTheme } from '../composables/useRandomData';
import './gantt-example-recipes.css';
import {
  RECIPE_ASSIGNMENTS,
  RECIPE_CALENDARS,
  RECIPE_COLUMNS,
  RECIPE_DEPENDENCIES,
  RECIPE_GANTT_CONFIG,
  RECIPE_PANEL_ROWS,
  RECIPE_PANEL_SOURCE_REFERENCES,
  RECIPE_RESOURCES,
  RECIPE_ROW_CONTEXT_MENU,
  RECIPE_TASKS,
  attachRecipeValidation,
} from './GanttExampleRecipesShared';

const { isDark } = currentTheme();

function createPanel(): HTMLElement {
  const panel = document.createElement('aside');
  panel.className = 'gantt-recipe-panel';
  panel.innerHTML = `
    <div class="gantt-recipe-panel__header">
      <strong>Feature recipes</strong>
      <span>Live outputs from exported example helpers</span>
    </div>
    <dl class="gantt-recipe-panel__list">
      ${RECIPE_PANEL_ROWS.map((row) => `
        <div>
          <dt>${row.label}</dt>
          <dd>${row.value}</dd>
        </div>
      `).join('')}
    </dl>
    <div class="gantt-recipe-panel__sources">
      ${RECIPE_PANEL_SOURCE_REFERENCES.map((source) => `<code>${source}</code>`).join('')}
    </div>
  `;
  return panel;
}

export function load(parentSelector: string) {
  const parent = document.querySelector(parentSelector);
  if (!parent) {
    return () => {};
  }

  const root = document.createElement('section');
  root.className = 'gantt-recipe-demo';
  const grid = document.createElement('revo-grid');
  grid.className = 'gantt-recipe-grid';
  grid.theme = isDark() ? 'darkCompact' : 'compact';
  grid.hideAttribution = true;
  grid.plugins = [GanttPlugin];
  grid.columns = RECIPE_COLUMNS;
  grid.gantt = RECIPE_GANTT_CONFIG;
  grid.ganttDependencies = RECIPE_DEPENDENCIES;
  grid.ganttCalendars = RECIPE_CALENDARS;
  grid.ganttResources = RECIPE_RESOURCES;
  grid.ganttAssignments = RECIPE_ASSIGNMENTS;
  grid.rowContextMenu = RECIPE_ROW_CONTEXT_MENU;

  root.appendChild(grid);
  root.appendChild(createPanel());
  parent.appendChild(root);
  const detachValidation = attachRecipeValidation(grid);
  grid.source = RECIPE_TASKS;

  return () => {
    detachValidation();
    root.remove();
  };
}
Vuevue
<template>
  <section class="gantt-recipe-demo">
    <RevoGrid
      ref="gridRef"
      hide-attribution
      class="gantt-recipe-grid"
      :theme="isDark ? 'darkCompact' : 'compact'"
      :plugins="plugins"
      :source="tasks"
      :columns="columns"
      :gantt.prop="ganttConfig"
      :gantt-dependencies.prop="dependencies"
      :gantt-calendars.prop="calendars"
      :gantt-resources.prop="resources"
      :gantt-assignments.prop="assignments"
      :row-context-menu.prop="rowContextMenu"
    />
    <aside class="gantt-recipe-panel">
      <div class="gantt-recipe-panel__header">
        <strong>Feature recipes</strong>
        <span>Live outputs from exported example helpers</span>
      </div>
      <dl class="gantt-recipe-panel__list">
        <div v-for="row in panelRows" :key="row.label">
          <dt>{{ row.label }}</dt>
          <dd>{{ row.value }}</dd>
        </div>
      </dl>
      <div class="gantt-recipe-panel__sources">
        <code v-for="source in sourceReferences" :key="source">{{ source }}</code>
      </div>
    </aside>
  </section>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import { GanttPlugin } from '@revolist/gantt';
import { currentThemeVue } from '../composables/useRandomData';
import {
  RECIPE_ASSIGNMENTS,
  RECIPE_CALENDARS,
  RECIPE_COLUMNS,
  RECIPE_DEPENDENCIES,
  RECIPE_GANTT_CONFIG,
  RECIPE_PANEL_ROWS,
  RECIPE_PANEL_SOURCE_REFERENCES,
  RECIPE_RESOURCES,
  RECIPE_ROW_CONTEXT_MENU,
  RECIPE_TASKS,
  attachRecipeValidation,
} from './GanttExampleRecipesShared';

const { isDark } = currentThemeVue();
const gridRef = ref<{ $el?: HTMLRevoGridElement } | null>(null);
const plugins = [GanttPlugin];
const tasks = ref(RECIPE_TASKS);
const columns = ref(RECIPE_COLUMNS);
const dependencies = ref(RECIPE_DEPENDENCIES);
const calendars = ref(RECIPE_CALENDARS);
const resources = ref(RECIPE_RESOURCES);
const assignments = ref(RECIPE_ASSIGNMENTS);
const ganttConfig = computed(() => RECIPE_GANTT_CONFIG);
const rowContextMenu = computed(() => RECIPE_ROW_CONTEXT_MENU);
const panelRows = RECIPE_PANEL_ROWS;
const sourceReferences = RECIPE_PANEL_SOURCE_REFERENCES;
let detachValidation: (() => void) | undefined;

onMounted(() => {
  const grid = gridRef.value?.$el;
  if (grid) {
    detachValidation = attachRecipeValidation(grid);
  }
});

onBeforeUnmount(() => {
  detachValidation?.();
});
</script>

<style src="./gantt-example-recipes.css"></style>
Reacttsx
import React, { useEffect, useMemo, useRef } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { GanttPlugin } from '@revolist/gantt';
import { currentTheme } from '../composables/useRandomData';
import {
  RECIPE_ASSIGNMENTS,
  RECIPE_CALENDARS,
  RECIPE_COLUMNS,
  RECIPE_DEPENDENCIES,
  RECIPE_GANTT_CONFIG,
  RECIPE_PANEL_ROWS,
  RECIPE_PANEL_SOURCE_REFERENCES,
  RECIPE_RESOURCES,
  RECIPE_ROW_CONTEXT_MENU,
  RECIPE_TASKS,
  attachRecipeValidation,
} from './GanttExampleRecipesShared';

const { isDark } = currentTheme();

export default function GanttExampleRecipes() {
  const gridRef = useRef<HTMLRevoGridElement | null>(null);
  const plugins = useMemo(() => [GanttPlugin], []);
  const columns = useMemo(() => RECIPE_COLUMNS, []);
  const gantt = useMemo(() => RECIPE_GANTT_CONFIG, []);
  const rowContextMenu = useMemo(() => RECIPE_ROW_CONTEXT_MENU, []);

  useEffect(() => {
    const grid = gridRef.current;
    return grid ? attachRecipeValidation(grid) : undefined;
  }, []);

  return (
    <section style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(0, 1fr) 300px',
      gap: 16,
      minHeight: 540,
    }}>
      <RevoGrid
        ref={gridRef}
        style={{ minHeight: 540 }}
        theme={isDark() ? 'darkCompact' : 'compact'}
        hideAttribution
        plugins={plugins}
        source={RECIPE_TASKS}
        columns={columns}
        gantt={gantt}
        ganttDependencies={RECIPE_DEPENDENCIES}
        ganttCalendars={RECIPE_CALENDARS}
        ganttResources={RECIPE_RESOURCES}
        ganttAssignments={RECIPE_ASSIGNMENTS}
        rowContextMenu={rowContextMenu}
      />
      <aside style={{
        borderLeft: '1px solid #d8dee8',
        padding: '12px 0 12px 16px',
        font: '13px/1.4 system-ui, sans-serif',
      }}>
        <div style={{ display: 'grid', gap: 3, marginBottom: 14 }}>
          <strong>Feature recipes</strong>
          <span style={{ color: '#64748b' }}>Live outputs from exported example helpers</span>
        </div>
        <dl style={{ display: 'grid', gap: 10, margin: 0 }}>
          {RECIPE_PANEL_ROWS.map((row) => (
            <div key={row.label} style={{ display: 'grid', gap: 2 }}>
              <dt style={{ color: '#64748b' }}>{row.label}</dt>
              <dd style={{ margin: 0, fontWeight: 600 }}>{row.value}</dd>
            </div>
          ))}
        </dl>
        <div style={{ display: 'grid', gap: 5, marginTop: 18 }}>
          {RECIPE_PANEL_SOURCE_REFERENCES.map((source) => (
            <code key={source} style={{ whiteSpace: 'normal', fontSize: 11, color: '#475569' }}>{source}</code>
          ))}
        </div>
      </aside>
    </section>
  );
}
Angularts
import { Component, NO_ERRORS_SCHEMA, ViewChild, ViewEncapsulation, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RevoGrid } from '@revolist/angular-datagrid';
import { GanttPlugin } from '@revolist/gantt';
import { currentTheme } from '../composables/useRandomData';
import {
  RECIPE_ASSIGNMENTS,
  RECIPE_CALENDARS,
  RECIPE_COLUMNS,
  RECIPE_DEPENDENCIES,
  RECIPE_GANTT_CONFIG,
  RECIPE_PANEL_ROWS,
  RECIPE_PANEL_SOURCE_REFERENCES,
  RECIPE_RESOURCES,
  RECIPE_ROW_CONTEXT_MENU,
  RECIPE_TASKS,
  attachRecipeValidation,
} from './GanttExampleRecipesShared';

@Component({
  selector: 'gantt-example-recipes-grid',
  standalone: true,
  schemas: [NO_ERRORS_SCHEMA],
  imports: [CommonModule, RevoGrid],
  template: `
    <section class="gantt-recipe-demo">
      <revo-grid
        #grid
        class="gantt-recipe-grid"
        [theme]="theme"
        [hideAttribution]="true"
        [plugins]="plugins"
        [source]="tasks"
        [columns]="columns"
        [gantt]="ganttConfig"
        [ganttDependencies]="dependencies"
        [ganttCalendars]="calendars"
        [ganttResources]="resources"
        [ganttAssignments]="assignments"
        [rowContextMenu]="rowContextMenu"
      ></revo-grid>
      <aside class="gantt-recipe-panel">
        <div class="gantt-recipe-panel__header">
          <strong>Feature recipes</strong>
          <span>Live outputs from exported example helpers</span>
        </div>
        <dl class="gantt-recipe-panel__list">
          <div *ngFor="let row of panelRows">
            <dt>{{ row.label }}</dt>
            <dd>{{ row.value }}</dd>
          </div>
        </dl>
        <div class="gantt-recipe-panel__sources">
          <code *ngFor="let source of sourceReferences">{{ source }}</code>
        </div>
      </aside>
    </section>
  `,
  styleUrls: ['./gantt-example-recipes.css'],
  encapsulation: ViewEncapsulation.None,
})
export class GanttExampleRecipesGridComponent implements AfterViewInit, OnDestroy {
  @ViewChild('grid', { read: ElementRef }) grid?: ElementRef<HTMLRevoGridElement>;

  theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
  plugins = [GanttPlugin];
  tasks = RECIPE_TASKS;
  columns = RECIPE_COLUMNS;
  ganttConfig = RECIPE_GANTT_CONFIG;
  dependencies = RECIPE_DEPENDENCIES;
  calendars = RECIPE_CALENDARS;
  resources = RECIPE_RESOURCES;
  assignments = RECIPE_ASSIGNMENTS;
  rowContextMenu = RECIPE_ROW_CONTEXT_MENU;
  panelRows = RECIPE_PANEL_ROWS;
  sourceReferences = RECIPE_PANEL_SOURCE_REFERENCES;
  private detachValidation?: () => void;

  ngAfterViewInit(): void {
    const grid = this.grid?.nativeElement;
    if (grid) {
      this.detachValidation = attachRecipeValidation(grid);
    }
  }

  ngOnDestroy(): void {
    this.detachValidation?.();
  }
}
Sharedts
import type { ContextMenuConfig } from '@revolist/revogrid-pro';
import type {
  GanttBeforeTaskChangeDetail,
  GanttPluginConfig,
  GanttTaskSourceRow,
  TaskEntity,
} from '@revolist/gantt';
import {
  createCustomTaskEditorFormRecipe,
  createCustomValidationRecipe,
  createGanttColumnPresetRecipe,
  createGanttTaskStatusIndicatorRecipe,
} from './recipes';
import { createGanttContextMenuExtensionItems } from '@revolist/gantt';
import {
  ASSIGNMENTS,
  CALENDAR_ID,
  DEPENDENCIES,
  GANTT_BASE_CONFIG,
  RESOURCES,
  STANDARD_CALENDAR,
  TASKS,
} from './shared/gantt-project-data';

export const RECIPE_GANTT_CONFIG = {
  ...GANTT_BASE_CONFIG,
  id: 'project-gantt-recipes',
  name: 'Gantt Feature Recipes',
  primaryCalendarId: CALENDAR_ID,
  updatedAt: '2026-04-06T00:00:00Z',
  statusDate: '2026-04-06',
  zoomPreset: 'week-month' as const,
  allowTaskCreate: true,
  visuals: {
    showTaskLabels: true,
    showTodayLine: true,
    shadeNonWorkingTime: true,
  },
} satisfies GanttPluginConfig;

const lockedTaskIds = ['t7'];
const taskById = new Map(TASKS.map((task) => [task.id, task]));
function toRecipeTask(task: GanttTaskSourceRow | undefined): TaskEntity | undefined {
  if (!task) {
    return undefined;
  }

  return {
    ...task,
    projectId: RECIPE_GANTT_CONFIG.id,
    parentId: task.parentId ?? null,
    wbsCode: '',
    type: task.type ?? 'task',
    workflowStatus: task.workflowStatus ?? 'not-started',
    duration: task.duration ?? 0,
    progressPercent: task.percentDone ?? 0,
    calendarId: String(task.calendarId ?? CALENDAR_ID),
    isCritical: false,
    tags: Array.isArray(task.tags) ? task.tags : [],
  } as TaskEntity;
}
const columnPreset = createGanttColumnPresetRecipe({ preset: 'core' });
const editorRecipe = createCustomTaskEditorFormRecipe({
  task: toRecipeTask(taskById.get('t4'))!,
  resources: RESOURCES,
  assignments: ASSIGNMENTS,
  fieldIds: ['name', 'startDate', 'endDate', 'progressPercent', 'resourceLabels'],
});
const statusIndicators = createGanttTaskStatusIndicatorRecipe({
  rows: TASKS.map((task) => ({ id: task.id })),
  lockedTaskIds,
});
const validationRecipe = createCustomValidationRecipe({
  context: {
    getTaskById: (taskId) => toRecipeTask(taskById.get(taskId)),
    roleContext: { roles: ['planner'] },
  },
  rolePolicy: {
    roles: {
      planner: ['task:move', 'task:resize', 'task:progress'],
    },
  },
  freezeWindows: [
    { startDate: '2026-04-14', endDate: '2026-04-18', label: 'design freeze' },
  ],
});

const validationDetail: GanttBeforeTaskChangeDetail = {
  action: 'move',
  taskId: 't4',
  changes: {
    startDate: '2026-04-15',
    endDate: '2026-04-18',
  },
  previousValues: {
    startDate: '2026-04-13',
    endDate: '2026-04-24',
  },
};
const validationDecision = validationRecipe.validate(validationDetail);

export const RECIPE_COLUMNS = columnPreset.columns;
export const RECIPE_TASKS = TASKS.map((task) =>
  lockedTaskIds.includes(task.id) ? { ...task, locked: true } : task);
export const RECIPE_DEPENDENCIES = DEPENDENCIES;
export const RECIPE_CALENDARS = [STANDARD_CALENDAR];
export const RECIPE_RESOURCES = RESOURCES;
export const RECIPE_ASSIGNMENTS = ASSIGNMENTS;
export const RECIPE_ROW_CONTEXT_MENU: ContextMenuConfig = {
  items: [
    ...createGanttContextMenuExtensionItems({
      duplicate: {
        icon: 'fa-solid fa-copy',
        copyName: (task) => `${task.name ?? 'Task'} copy`,
      },
      delete: false,
      assignResource: {
        icon: 'fa-solid fa-user-plus',
        name: 'Open assignment panel',
        onAssignResource: (context) => {
          context.revogrid.dispatchEvent(new CustomEvent('gantt-recipe-action', {
            bubbles: true,
            detail: { action: 'assign-resource', taskId: context.taskId },
          }));
        },
      },
      export: {
        icon: 'fa-solid fa-file-excel',
        name: 'Export task workbook',
        exportOptions: (context) => ({
          sheetName: context.task?.name ?? 'Gantt',
          workbookName: 'gantt-recipes.xlsx',
        }),
      },
    }),
  ],
};

export const RECIPE_PANEL_ROWS = [
  {
    label: 'Column preset',
    value: `${columnPreset.presetProps.length} core fields`,
  },
  {
    label: 'Editor form',
    value: `${editorRecipe.fields.length} fields for ${editorRecipe.initialValues.name}`,
  },
  {
    label: 'Status indicators',
    value: `${statusIndicators.filter((row) => row.status.indicators.length > 0).length} protected rows`,
  },
  {
    label: 'Validation result',
    value: validationDecision.ok ? 'Allowed' : validationDecision.message ?? 'Blocked',
  },
  {
    label: 'Row menu extensions',
    value: `${RECIPE_ROW_CONTEXT_MENU.items.length} custom actions`,
  },
] as const;

export const RECIPE_PANEL_SOURCE_REFERENCES = [
  'packages/gantt/src/gantt/features/context-menu/context-menu-extensions.ts',
  'examples/components/src/components/gantt/recipes/feature-helper-examples.ts',
  'examples/components/src/components/gantt/recipes/grid-projection-examples.ts',
  'examples/components/src/components/gantt/recipes/validation-recipes.ts',
] as const;

export function attachRecipeValidation(grid: HTMLRevoGridElement): () => void {
  const handler = validationRecipe.handler as unknown as EventListener;
  grid.addEventListener('gantt-before-task-change', handler);
  return () => grid.removeEventListener('gantt-before-task-change', handler);
}

Outside plugin code, locate the runtime through the grid’s plugin collection.

import { GanttPlugin } from '@revolist/gantt';
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin instanceof GanttPlugin);

Do not install reverse callbacks or custom methods directly on the grid element.

PurposeMethods
Project lifecycleapplyGantt, clearGantt, getProjectSnapshot
Zoom and rangezoomIn, zoomOut, setZoomLevel, getZoomLevel, canZoomIn, canZoomOut, getVisibleRange
TaskscreateTask, updateTask, finalizeTaskCreate, discardTaskCreate
HierarchyindentTask(s), outdentTask(s), deleteTask(s), convertTask(s)ToMilestones
BaselinescaptureBaseline

Use the Gantt API for exact signatures and return types.

Call preventDefault() to reject a proposed packaged mutation.

grid.addEventListener('gantt-before-task-change', (event) => {
if (!canEditTask(event.detail.taskId, event.detail.action)) {
event.preventDefault();
}
});
EventWhen it fires
gantt-before-task-changeBefore create, move, resize, edit, progress, split, indent, outdent, delete, or milestone conversion.
gantt-before-dependency-changeBefore dependency create, delete, edit, or set replacement.
gantt-before-assignment-changeBefore task assignments are edited.

Gantt publishes successful task, dependency, and assignment mutations through EventManager’s existing gridedit event. This gives persistence and audit integrations one committed-change stream for grid edits and Gantt interactions such as moving, resizing, changing progress, editing hierarchy, and deleting tasks.

grid.addEventListener('gridedit', (event) => {
for (const change of event.detail.domainChanges ?? []) {
if (change.type === 'gantt-task') {
if (change.detail.task) saveTask(change.detail.task);
}
}
});

Use event.detail.eventTypes to detect gantt-task-change, gantt-dependency-change, or gantt-assignment-change. domainChanges preserves the committed changes in order and includes previous and current domain state. Bulk operations and scheduler cascades include every affected task.

data, previousData, and models keep the normal grid row/field shape. Stable task-row updates appear there exactly like other grid edits; dependencies, assignments, creates, deletes, and reorders that do not have a safe row coordinate stay in domainChanges. Replay-only project snapshots are stored separately in history and are never mixed into grid fields.

Cancellation belongs to the corresponding gantt-before-*-change event. A Gantt change in gridedit has already committed, so calling preventDefault() there does not roll it back. When EventManager collection is enabled, consecutive Gantt mutations are emitted as one collected transaction and one history step.

The grid also uses typed events for task creation/edit requests, hierarchy commands, zoom, search, dependency hover/selection, task-created notification, and panel resize. Prefer exported event constants when code must be refactor-safe.

import {
GANTT_TASK_CREATE_EVENT,
GANTT_ZOOM_IN_EVENT,
} from '@revolist/gantt';
grid.dispatchEvent(new CustomEvent(GANTT_TASK_CREATE_EVENT));
grid.dispatchEvent(new CustomEvent(GANTT_ZOOM_IN_EVENT));

Application listeners should update or validate project data, not query rendered task-bar geometry. See Application State for persistence ownership.