Vue/Custom edit view
Loading live demo…from dhtmlx.github.io
Vue
Custom edit view
Split task editing into a dedicated Vue Router view with shared Gantt context via provide/inject.
- Demo.vue
- GanttView.vue
- TaskEditor.vue
- context.ts
- shared/projectData.ts
- shared/useDemoBatchState.ts
src/examples/custom-edit-view/Demo.vue View on GitHub
<script setup lang="ts">
import { provide } from "vue";
import { RouterView } from "vue-router";
import type { Task } from "@dhtmlx/trial-vue-gantt";
import { createProjectData } from "../shared/projectData";
import { useDemoBatchState } from "../shared/useDemoBatchState";
import { CUSTOM_EDIT_VIEW_CONTEXT_KEY } from "./context";
const { tasks, links, applyBatch } = useDemoBatchState(createProjectData());
const upsertTask = (task: Task) => {
const index = tasks.value.findIndex(item => String(item.id) === String(task.id));
if (index === -1) {
tasks.value = [...tasks.value, task];
return;
}
const nextTasks = [...tasks.value];
nextTasks[index] = { ...nextTasks[index], ...task };
tasks.value = nextTasks;
};
const saveTask = (task: Task) => {
const nextTask = { ...task } as any;
if (nextTask.$new) {
delete nextTask.$new;
}
upsertTask(nextTask);
};
const deleteTask = (taskId: string | number) => {
tasks.value = tasks.value.filter(task => String(task.id) !== String(taskId));
};
provide(CUSTOM_EDIT_VIEW_CONTEXT_KEY, {
tasks,
links,
saveTask,
upsertTask,
deleteTask,
applyBatch
});
</script>
<template>
<section class="demo-panel" data-cy="custom-edit-view-demo">
<RouterView />
</section>
</template>
src/examples/custom-edit-view/GanttView.vue View on GitHub
<script setup lang="ts">
import { computed, inject } from "vue";
import { useRouter } from "vue-router";
import {
defineGanttEvents,
VueGantt,
type Task
} from "@dhtmlx/trial-vue-gantt";
import "@dhtmlx/trial-vue-gantt/dist/vue-gantt.css";
import { createBatchSaveData } from "../shared/useDemoBatchState";
import { CUSTOM_EDIT_VIEW_CONTEXT_KEY } from "./context";
const router = useRouter();
const context = inject(CUSTOM_EDIT_VIEW_CONTEXT_KEY);
if (!context) {
throw new Error("Custom edit view context is not provided");
}
const events = defineGanttEvents({
onBeforeLightbox: (taskId: string | number) => {
router.push(`/custom-edit-view/editor/${taskId}`);
return false;
},
onTaskCreated: (task: Task) => {
context.upsertTask({ ...(task as any), $new: true });
router.push(`/custom-edit-view/editor/${task.id}`);
return false;
}
});
const data = createBatchSaveData(context.applyBatch);
const tasks = computed(() => context.tasks.value);
const links = computed(() => context.links.value);
</script>
<template>
<VueGantt class="demo-gantt" :tasks="tasks" :links="links" :events="events" :data="data" />
</template>
src/examples/custom-edit-view/TaskEditor.vue View on GitHub
<script setup lang="ts">
import { computed, inject, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import type { Task } from "@dhtmlx/trial-vue-gantt";
import { CUSTOM_EDIT_VIEW_CONTEXT_KEY } from "./context";
const router = useRouter();
const route = useRoute();
const context = inject(CUSTOM_EDIT_VIEW_CONTEXT_KEY);
if (!context) {
throw new Error("Custom edit view context is not provided");
}
const currentId = computed(() => String(route.params.id || ""));
const currentTask = computed(() => {
const found = context.tasks.value.find(task => String(task.id) === currentId.value);
if (found) {
return found;
}
return {
id: currentId.value,
text: "",
duration: 1,
progress: 0,
start_date: new Date(),
parent: 0
} as Task;
});
const text = ref("");
const duration = ref(1);
watch(
currentTask,
nextTask => {
text.value = String((nextTask as any).text || "");
duration.value = Number((nextTask as any).duration || 1);
},
{ immediate: true }
);
const save = () => {
context.saveTask({ ...(currentTask.value as any), text: text.value, duration: duration.value });
router.push("/custom-edit-view");
};
const remove = () => {
context.deleteTask(currentTask.value.id as string | number);
router.push("/custom-edit-view");
};
const cancel = () => {
router.push("/custom-edit-view");
};
</script>
<template>
<div class="task-editor" data-cy="custom-edit-editor">
<h3>Edit Task {{ currentTask.id }}</h3>
<label class="field">
<span>Task Text</span>
<input data-cy="custom-edit-text" type="text" v-model="text" />
</label>
<label class="field">
<span>Duration</span>
<input type="number" min="1" v-model.number="duration" />
</label>
<div class="actions">
<button data-cy="custom-edit-save" type="button" @click="save">Save</button>
<button data-cy="custom-edit-cancel" type="button" @click="cancel">Cancel</button>
<button data-cy="custom-edit-delete" type="button" @click="remove">Delete</button>
</div>
</div>
</template>
<style scoped>
.task-editor {
width: min(460px, 100%);
margin: 16px;
border: 1px solid #cfd8e2;
background: #fff;
padding: 14px;
}
h3 {
margin: 0 0 12px;
}
.field {
display: block;
margin-bottom: 10px;
}
.field span {
display: block;
font-size: 12px;
margin-bottom: 4px;
color: #475e74;
}
.field input {
width: 100%;
border: 1px solid #cfd8e2;
padding: 8px;
font-size: 14px;
}
.actions {
display: flex;
gap: 8px;
}
.actions button {
border: 1px solid #cfd8e2;
background: #fff;
padding: 6px 10px;
cursor: pointer;
}
</style>
src/examples/custom-edit-view/context.ts View on GitHub
import type { BatchChanges, Link, Task } from "@dhtmlx/trial-vue-gantt";
import type { InjectionKey, Ref } from "vue";
export interface CustomEditViewContext {
tasks: Ref<Task[]>;
links: Ref<Link[]>;
saveTask: (task: Task) => void;
upsertTask: (task: Task) => void;
deleteTask: (taskId: string | number) => void;
applyBatch: (changes: BatchChanges) => void;
}
export const CUSTOM_EDIT_VIEW_CONTEXT_KEY: InjectionKey<CustomEditViewContext> = Symbol("custom-edit-view-context");
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)
};
}
src/examples/shared/useDemoBatchState.ts View on GitHub
import { ref } from "vue";
import type { BatchChanges, Link, Task } from "@dhtmlx/trial-vue-gantt";
import { applyBatchChanges } from "./applyBatchChanges";
export interface DemoDataSet {
tasks: Task[];
links: Link[];
}
export function createBatchSaveData(applyBatch: (changes: BatchChanges) => void) {
return {
batchSave: applyBatch
};
}
export function useDemoBatchState(initialData: DemoDataSet) {
const tasks = ref<Task[]>(initialData.tasks);
const links = ref<Link[]>(initialData.links);
const applyBatch = (changes: BatchChanges) => {
const next = applyBatchChanges(tasks.value, links.value, changes);
tasks.value = next.tasks;
links.value = next.links;
};
return {
tasks,
links,
applyBatch,
data: createBatchSaveData(applyBatch)
};
}