Vue/Templates
Loading live demo…from dhtmlx.github.io
Vue
Templates
Customize task text, status controls, and the task delete dialog with Vue template components, plus theme and locale switching.
- Demo.vue
- DoneToggleButton.vue
- FilterDropdown.vue
- TaskDeleteDialog.vue
- TaskTextBadge.vue
- shared/projectData.ts
src/examples/templates/Demo.vue View on GitHub
<script setup lang="ts">
import { computed, h, ref } from "vue";
import {
VueGantt,
type GanttConfigOptions,
type OnBeforeTaskDeleteConfirmArgs,
type Link,
type Task,
type VueGanttRef
} from "@dhtmlx/trial-vue-gantt";
import "@dhtmlx/trial-vue-gantt/dist/vue-gantt.css";
import DoneToggleButton from "./components/DoneToggleButton.vue";
import FilterDropdown, { type TemplateFilterMode } from "./components/FilterDropdown.vue";
import TaskDeleteDialog from "./components/TaskDeleteDialog.vue";
import TaskTextBadge from "./components/TaskTextBadge.vue";
import { createProjectData } from "../shared/projectData";
type TaskWithCompleted = Task & {
completed?: boolean;
};
const dataSet = createProjectData();
const tasks = ref<TaskWithCompleted[]>(
dataSet.tasks.map(task => ({
...task,
completed: task.progress === 1
}))
);
const links = ref<Link[]>(dataSet.links);
const ganttRef = ref<VueGanttRef | null>(null);
const theme = ref("terrace");
const localeCycle = ["en", "es", "de", "cn"] as const;
type LocaleCode = (typeof localeCycle)[number];
const locale = ref<LocaleCode>("en");
const filterMode = ref<TemplateFilterMode>("all");
const filter = computed<((task: Task) => boolean) | undefined>(() => {
if (filterMode.value === "done") {
return task => Boolean((task as TaskWithCompleted).completed);
}
if (filterMode.value === "notDone") {
return task => !Boolean((task as TaskWithCompleted).completed);
}
return undefined;
});
const onFilterModeChange = (nextMode: TemplateFilterMode) => {
filterMode.value = nextMode;
};
const toggleCompleted = (taskId: string | number) => {
tasks.value = tasks.value.map(task => {
if (String(task.id) !== String(taskId)) {
return task;
}
return {
...task,
completed: !Boolean(task.completed)
};
});
};
const templates = {
task_text: (_start: Date, _end: Date, task: Task) =>
h(TaskTextBadge, {
task,
onToggle: () => toggleCompleted(task.id)
})
} as any;
const showTaskDeleteDialog = ref(false);
const pendingTaskDelete = ref<{ message: string; callback: () => void } | null>(null);
const handleDeleteTaskConfirm = ({ task, callback }: OnBeforeTaskDeleteConfirmArgs) => {
pendingTaskDelete.value = {
message: `Are you sure you want to delete "${task.text}"?`,
callback
};
showTaskDeleteDialog.value = true;
};
const onDialogConfirm = () => {
pendingTaskDelete.value?.callback();
pendingTaskDelete.value = null;
};
const onDialogCancel = () => {
pendingTaskDelete.value = null;
};
const modals = {
onBeforeTaskDelete: handleDeleteTaskConfirm
};
const config = computed<Partial<GanttConfigOptions>>(() => ({
row_height: 50,
scale_height: 70,
columns: [
{ name: "text", tree: true, width: 220, resize: true },
{ name: "start_date", align: "center", width: 130, resize: true },
{ name: "duration", align: "center", width: 90, resize: true },
{
name: "status",
align: "center",
width: 180,
resize: true,
label: h(FilterDropdown, {
modelValue: filterMode.value,
"onUpdate:modelValue": onFilterModeChange
}),
template: (task: Task) =>
h(DoneToggleButton, {
done: Boolean((task as TaskWithCompleted).completed),
onToggle: () => toggleCompleted(task.id)
})
},
{ name: "add", width: 44 }
],
scales: [
{ unit: "year", step: 1, format: "%Y" },
{ unit: "month", step: 1, format: "%F" },
{ unit: "day", step: 1, format: "%d %M" }
]
}));
const toggleTheme = () => {
theme.value = theme.value === "terrace" ? "dark" : "terrace";
};
const switchLocale = () => {
const index = localeCycle.indexOf(locale.value);
locale.value = localeCycle[(index + 1) % localeCycle.length];
};
const collapseAll = () => {
const gantt = ganttRef.value?.instance;
if (!gantt) {
return;
}
gantt.eachTask((task: Task & { $open?: boolean }) => {
task.$open = false;
});
gantt.render();
};
const expandAll = () => {
const gantt = ganttRef.value?.instance;
if (!gantt) {
return;
}
gantt.eachTask((task: Task & { $open?: boolean }) => {
task.$open = true;
});
gantt.render();
};
</script>
<template>
<section class="demo-panel" data-cy="templates-demo">
<div class="demo-toolbar">
<button data-cy="templates-toggle-theme" type="button" @click="toggleTheme">Switch Theme</button>
<button data-cy="templates-switch-locale" type="button" @click="switchLocale">Switch Locale</button>
<button data-cy="templates-collapse-all" type="button" @click="collapseAll">Collapse All</button>
<button data-cy="templates-expand-all" type="button" @click="expandAll">Expand All</button>
</div>
<VueGantt
ref="ganttRef"
class="demo-gantt"
:tasks="tasks"
:links="links"
:filter="filter"
:theme="theme"
:locale="locale"
:config="config"
:templates="templates"
:modals="modals"
/>
<TaskDeleteDialog
v-model="showTaskDeleteDialog"
:text="pendingTaskDelete?.message ?? ''"
@confirm="onDialogConfirm"
@cancel="onDialogCancel"
/>
</section>
</template>
src/examples/templates/components/DoneToggleButton.vue View on GitHub
<script setup lang="ts">
defineProps<{
done: boolean;
}>();
const emit = defineEmits<{
toggle: [];
}>();
const onClick = () => {
emit("toggle");
};
</script>
<template>
<v-btn
data-cy="templates-done-toggle"
:color="done ? 'success' : 'info'"
size="x-small"
variant="flat"
@click.stop="onClick"
>
{{ done ? "done" : "not done" }}
</v-btn>
</template>
src/examples/templates/components/FilterDropdown.vue View on GitHub
<script setup lang="ts">
import { computed } from "vue";
export type TemplateFilterMode = "all" | "done" | "notDone";
const props = defineProps<{
modelValue: TemplateFilterMode;
}>();
const emit = defineEmits<{
"update:modelValue": [value: TemplateFilterMode];
}>();
const currentLabel = computed(() => {
if (props.modelValue === "done") {
return "Done";
}
if (props.modelValue === "notDone") {
return "Not Done";
}
return "All";
});
const options: Array<{
value: TemplateFilterMode;
label: string;
dataCy: string;
}> = [
{ value: "done", label: "Show Done", dataCy: "templates-filter-option-done" },
{ value: "notDone", label: "Show Not Done", dataCy: "templates-filter-option-not-done" },
{ value: "all", label: "Show All", dataCy: "templates-filter-option-all" }
];
const selectOption = (value: TemplateFilterMode) => {
emit("update:modelValue", value);
};
</script>
<template>
<v-menu location="bottom start">
<template #activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
data-cy="templates-filter-menu-trigger"
size="small"
variant="text"
>
Show: {{ currentLabel }}
</v-btn>
</template>
<v-list density="compact">
<v-list-item
v-for="option in options"
:key="option.value"
:title="option.label"
:data-cy="option.dataCy"
@click="selectOption(option.value)"
/>
</v-list>
</v-menu>
</template>
src/examples/templates/components/TaskDeleteDialog.vue View on GitHub
<script setup lang="ts">
interface TaskDeleteDialogProps {
modelValue: boolean;
text: string;
title?: string;
}
const props = withDefaults(defineProps<TaskDeleteDialogProps>(), {
title: "Delete task"
});
const emit = defineEmits<{
"update:modelValue": [value: boolean];
confirm: [];
cancel: [];
}>();
const onCancel = () => {
emit("update:modelValue", false);
emit("cancel");
};
const onConfirm = () => {
emit("update:modelValue", false);
emit("confirm");
};
</script>
<template>
<v-dialog
:model-value="props.modelValue"
max-width="420"
persistent
data-cy="templates-delete-dialog"
@update:model-value="(value) => emit('update:modelValue', value)"
>
<v-card>
<v-card-title>{{ props.title }}</v-card-title>
<v-card-text>{{ props.text }}</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
data-cy="templates-delete-cancel"
variant="text"
@click="onCancel"
>
No
</v-btn>
<v-btn
data-cy="templates-delete-confirm"
color="error"
variant="flat"
autofocus
@click="onConfirm"
>
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
src/examples/templates/components/TaskTextBadge.vue View on GitHub
<script setup lang="ts">
import type { Task } from "@dhtmlx/trial-vue-gantt";
interface TaskTextBadgeProps {
task: Task & { completed?: boolean };
}
defineProps<TaskTextBadgeProps>();
const emit = defineEmits<{
toggle: [];
}>();
const onToggleClick = (event: MouseEvent) => {
event.stopPropagation();
emit("toggle");
};
</script>
<template>
<div class="task-text-badge" data-cy="templates-task-text">
<v-tooltip
:text="task.completed ? 'Status: Completed' : 'Status: Pending'"
location="top"
>
<template #activator="{ props: tooltipProps }">
<button
v-bind="tooltipProps"
type="button"
class="task-text-badge__toggle"
data-cy="templates-task-text-toggle"
@click="onToggleClick"
>
{{ task.completed ? "✓" : "○" }}
</button>
</template>
</v-tooltip>
<span class="task-text-badge__text">{{ task.text }}</span>
</div>
</template>
<style scoped>
.task-text-badge {
align-items: center;
gap: 6px;
box-sizing: border-box;
}
.task-text-badge__toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
font-size: 14px;
line-height: 1;
cursor: pointer;
color: inherit;
}
.task-text-badge__toggle:hover {
background: rgba(0, 0, 0, 0.08);
}
.task-text-badge__text {
font-weight: 500;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
src/examples/shared/projectData.ts View on GitHub
import type { Link, Task } from "@dhtmlx/trial-vue-gantt";
const baseTasks: Task[] = [
{ id: 1, text: "Office itinerancy", type: "project", start_date: new Date(2026, 6, 2), duration: 17, progress: 0.4, parent: 0, open: true },
{ id: 2, text: "Office facing", type: "project", start_date: new Date(2026, 6, 2), duration: 8, progress: 0.6, parent: 1, open: true },
{ id: 3, text: "Furniture installation", type: "project", start_date: new Date(2026, 6, 11), duration: 8, progress: 0.6, parent: 1, open: true },
{ id: 4, text: "The employee relocation", type: "project", start_date: new Date(2026, 6, 13), duration: 5, progress: 0.5, parent: 1, priority: 3, open: true },
{ id: 5, text: "Interior office", type: "task", start_date: new Date(2026, 6, 3), duration: 7, progress: 0.6, parent: 2, priority: 1 },
{ id: 6, text: "Air conditioners check", type: "task", start_date: new Date(2026, 6, 3), duration: 7, progress: 0.6, parent: 2, priority: 2 },
{ id: 7, text: "Workplaces preparation", type: "task", start_date: new Date(2026, 6, 12), duration: 8, progress: 0.6, parent: 3 },
{ id: 8, text: "Preparing workplaces", type: "task", start_date: new Date(2026, 6, 14), duration: 5, progress: 0.5, parent: 4, priority: 1 },
{ id: 9, text: "Workplaces importation", type: "task", start_date: new Date(2026, 6, 21), duration: 4, progress: 0.5, parent: 4 },
{ id: 10, text: "Workplaces exportation", type: "task", start_date: new Date(2026, 6, 27), duration: 3, progress: 0.5, parent: 4, priority: 2 },
{ id: 11, text: "Product launch", type: "project", start_date: new Date(2026, 6, 2), duration: 13, progress: 0.6, parent: 0, open: true },
{ id: 12, text: "Perform Initial testing", type: "task", start_date: new Date(2026, 6, 3), duration: 5, progress: 1, parent: 11 },
{ id: 13, text: "Development", type: "project", start_date: new Date(2026, 6, 3), duration: 11, progress: 0.5, parent: 11, open: true },
{ id: 14, text: "Analysis", type: "task", start_date: new Date(2026, 6, 3), duration: 6, progress: 0.8, parent: 11 },
{ id: 15, text: "Design", type: "project", start_date: new Date(2026, 6, 3), duration: 5, progress: 0.2, parent: 11, open: true },
{ id: 16, text: "Documentation creation", type: "task", start_date: new Date(2026, 6, 3), duration: 7, progress: 0, parent: 11, priority: 1 },
{ id: 17, text: "Develop System", type: "task", start_date: new Date(2026, 6, 3), duration: 2, progress: 1, parent: 13, priority: 2 },
{ id: 25, text: "Beta Release", type: "milestone", start_date: new Date(2026, 6, 6), duration: 0, progress: 0, parent: 13 },
{ id: 18, text: "Integrate System", type: "task", start_date: new Date(2026, 6, 10), duration: 2, progress: 0.8, parent: 13, priority: 3 },
{ id: 19, text: "Test", type: "task", start_date: new Date(2026, 6, 13), duration: 4, progress: 0.2, parent: 13 },
{ id: 20, text: "Marketing", type: "task", start_date: new Date(2026, 6, 13), duration: 4, progress: 0, parent: 13, priority: 1 },
{ id: 21, text: "Design database", type: "task", start_date: new Date(2026, 6, 3), duration: 4, progress: 0.5, parent: 15 },
{ id: 22, text: "Software design", type: "task", start_date: new Date(2026, 6, 3), duration: 4, progress: 0.1, parent: 15, priority: 1 },
{ id: 23, text: "Interface setup", type: "task", start_date: new Date(2026, 6, 3), duration: 5, progress: 0, parent: 15, priority: 1 },
{ id: 24, text: "Release v1.0", type: "milestone", start_date: new Date(2026, 6, 18), duration: 0, progress: 0, parent: 11 }
];
const baseLinks: Link[] = [
{ id: 2, source: 2, target: 3, type: "0" },
{ id: 3, source: 3, target: 4, type: "0" },
{ id: 7, source: 8, target: 9, type: "0" },
{ id: 8, source: 9, target: 10, type: "0" },
{ id: 16, source: 17, target: 25, type: "0" },
{ id: 17, source: 18, target: 19, type: "0" },
{ id: 18, source: 19, target: 20, type: "0" },
{ id: 22, source: 13, target: 24, type: "0" },
{ id: 23, source: 25, target: 18, type: "0" }
];
const cloneDate = (value: Date | undefined): Date | undefined => {
if (value instanceof Date) {
return new Date(value.getTime());
}
return value;
};
const cloneTask = (task: Task): Task => {
const next: Task = { ...task };
next.start_date = cloneDate(task.start_date);
next.end_date = cloneDate(task.end_date);
return next;
};
const cloneLink = (link: Link): Link => ({ ...link });
export function createProjectData() {
return {
tasks: baseTasks.map(cloneTask),
links: baseLinks.map(cloneLink)
};
}