diff --git a/docs/guides/guides.md b/docs/guides/guides.md index 39f0ad75..3833170a 100644 --- a/docs/guides/guides.md +++ b/docs/guides/guides.md @@ -165,5 +165,15 @@ Describes the elements of the Gantt Chart interface from the point of view of th - ### [Interface of Gantt Chart](guides/overview.md) +## Migrating from Other Gantt Libraries + +Step-by-step tutorials for moving an existing app from Bryntum, Syncfusion, DevExpress, or Frappe Gantt to DHTMLX Gantt, with companion demo repositories on GitHub. + +- ### [Migrating to DHTMLX Gantt](/migrating/) +- ### [From Bryntum](/migrating/from-bryntum) +- ### [From Syncfusion](/migrating/from-syncfusion) +- ### [From DevExpress](/migrating/from-devexpress) +- ### [From Frappe](/migrating/from-frappe) + \ No newline at end of file diff --git a/docs/migrating/from-bryntum.md b/docs/migrating/from-bryntum.md new file mode 100644 index 00000000..ae956188 --- /dev/null +++ b/docs/migrating/from-bryntum.md @@ -0,0 +1,801 @@ +--- +title: "Migrating from Bryntum to DHTMLX Gantt" +sidebar_label: "From Bryntum" +--- + +:::note +The complete demo source code is available on GitHub: [https://github.com/DHTMLX/gantt-migrating-from-bryntum](https://github.com/DHTMLX/gantt-migrating-from-bryntum). +::: + +# Migrating from Bryntum Gantt to DHTMLX Gantt + +## Introduction + +[Bryntum Gantt](https://bryntum.com/products/gantt/) is a JavaScript Gantt chart component for project management tools. + +This guide will walk you through the process of migrating an existing application from Bryntum Gantt to DHTMLX Gantt. We'll cover all necessary steps including database schema changes, server-side API modifications, and client-side code updates. + +## Prerequisites + +Before starting the migration, ensure you have: +- An existing working application using Bryntum Gantt +- Node.js (>= 20.0.0) installed +- MySQL database with Bryntum data structure +- Basic knowledge of Express.js and JavaScript + + +## Step 1: Database Migration + +### Understanding Current Schema + +If you followed the Bryntum demo setup, you should have two tables: `tasks` and `dependencies`. + +The `tasks` table structure: + + +The `dependencies` table structure: + + +### Create DHTMLX Tables + +DHTMLX Gantt uses a simpler database structure. Create two new tables compatible with DHTMLX Gantt: + +```sql +CREATE TABLE `gantt_tasks` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `text` varchar(255) NOT NULL, + `start_date` datetime NOT NULL, + `end_date` datetime NOT NULL, + `duration` int(11) NOT NULL, + `progress` float NOT NULL, + `parent` int(11) NOT NULL, + `constraint_type` varchar(20) DEFAULT 'asap', + `constraint_date` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `gantt_links` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `source` int(11) NOT NULL, + `target` int(11) NOT NULL, + `type` varchar(1) NOT NULL, + PRIMARY KEY (`id`) +); +``` + +**Note:** +- We've added `constraint_type` and `constraint_date` fields to support auto-scheduling with constraints, which is a feature available in both Bryntum and DHTMLX Gantt. +- We've added `end_date` field to preserve the exact end date calculated by the Gantt. Without storing `end_date`, it would be recalculated based on `start_date` and `duration`, which can vary depending on enabled features (auto-scheduling, work time, constraints). Storing `end_date` prevents loss of this fundamental information. + +### Migrate Existing Data + +Now migrate your existing Bryntum data to the new DHTMLX tables. + +**Migrate tasks:** +```sql +INSERT INTO gantt_tasks ( + `id`, + `text`, + `start_date`, + `end_date`, + `duration`, + `progress`, + `parent`, + `constraint_type`, + `constraint_date` +) +SELECT + `id`, + `name`, -- 'name' --> 'text' + `startDate`, + DATE_ADD(`startDate`, INTERVAL `duration` DAY), -- Calculate end_date from start_date + duration + `duration`, + `percentDone`, -- 'percentDone' --> 'progress' + IFNULL(`parentId`, 0), -- 'parentId' --> 'parent' (0 for root tasks) + CASE `constraintType` -- Map constraint types + WHEN 'assoonaspossible' THEN 'asap' + WHEN 'aslateaspossible' THEN 'alap' + WHEN 'startnoearlierthan' THEN 'snet' + WHEN 'startnolaterthan' THEN 'snlt' + WHEN 'finishnoearlierthan' THEN 'fnet' + WHEN 'finishnolaterthan' THEN 'fnlt' + WHEN 'muststarton' THEN 'mso' + WHEN 'mustfinishon' THEN 'mfo' + ELSE 'asap' + END, + `constraintDate` +FROM tasks; +``` + +**Migrate links (dependencies):** +```sql +INSERT INTO gantt_links (`id`, `source`, `target`, `type`) +SELECT + `id`, + `fromEvent`, -- 'fromEvent' --> 'source' + `toEvent`, -- 'toEvent' --> 'target' + CASE `type` -- Convert Bryntum link types to DHTMLX format + WHEN 0 THEN '1' -- Start-to-Start + WHEN 1 THEN '3' -- Start-to-Finish + WHEN 2 THEN '0' -- Finish-to-Start (most common) + WHEN 3 THEN '2' -- Finish-to-Finish + ELSE '0' -- Default to Finish-to-Start + END +FROM dependencies; +``` + +You can verify that the data was migrated correctly by running the following commands: + +```sql +SELECT * FROM gantt_tasks; +SELECT * FROM gantt_links; +``` + +You should see all your tasks and links properly transferred with the correct field mappings. + +### Mapping Bryntum Task Fields to DHTMLX Gantt + +Bryntum Gantt's [TaskModel](https://bryntum.com/products/gantt/docs/api/Gantt/model/TaskModel) contains a number of fields that are either implemented differently in DHTMLX Gantt or require special handling during migration. The following table explains how to map the most common Bryntum task fields to DHTMLX Gantt: + +| Bryntum Field | Description | Recommended Approach | +|--------------|-------------|---------------------| +| `effort` / `effortUnit` | Amount of work required to complete a task (e.g. 16h), used for effort-driven and resource-based scheduling | DHTMLX Gantt does not support effort-driven scheduling at the task level. As an alternative, you can use the [Resource Management](guides/resource-management.md) module to assign resources and visualize workload. Resource assignments can represent effort (e.g. hours per day), but task duration is not recalculated automatically and must be managed manually or via custom logic. | +| `durationUnit` | Unit used to interpret the task duration (hours, days, weeks, etc.) | DHTMLX Gantt uses a global duration unit configured via `gantt.config.duration_unit`. During migration, it's recommended to normalize all durations to a single unit. If you want to have different duration units for different tasks, i.e. to show durations of some tasks in hours and some tasks in "days", you can use the [formatter module](https://docs.dhtmlx.com/gantt/guides/working-time/#taskdurationindecimalformat). | +| `schedulingMode` | Defines how task scheduling behaves (Normal, FixedDuration, FixedEffort, etc.) | No direct equivalent. DHTMLX Gantt does not support per-task scheduling modes. You can store this value as a custom field and, if required, enforce custom behavior using Gantt events (e.g., [onBeforeTaskUpdate](api/event/onbeforetaskupdate.md)). | +| `note` | Free-text notes or description attached to a task | Can be migrated directly as a custom text field (e.g. `note` or `description`) and shown in the lightbox, tooltip, or a custom grid column. | +| `manuallyScheduled` | Indicates whether a task is excluded from automatic scheduling | The `task.auto_scheduling` property of the task can be used which allows individual tasks to be excluded from auto scheduling while keeping it enabled globally. See [Disabling auto scheduling for specific tasks](guides/auto-scheduling.md#disabling-auto-scheduling-for-specific-tasks). | +| `calendar` | The calendar, assigned to the task | DHTMLX Gantt supports multiple working calendars. A calendar can be assigned to a task via the `calendar_id` property (or a custom property defined by `gantt.config.calendar_property`). See [Assigning Calendar to Task](guides/working-time.md#assigningcalendartotask). | +| `deadline` | A target date that the task should not exceed | Fully supported via the `task.deadline` property. When specified, DHTMLX Gantt displays a visual deadline indicator on the timeline. The value uses the same date format as `start_date`. | +--- + +## Step 2: Backend Migration (server.js) + +### Remove Bryntum-Specific Code + +First, remove the Bryntum package serving middleware from your `server.js`: + +```js +// DELETE this line: +app.use(express.static(path.join(__dirname, '/node_modules/@bryntum/gantt'))); +``` + +**Important:** With Vite which will be used in this demo, you no longer need to serve `node_modules` directly. Remove any middleware that exposes the entire `node_modules` directory: + +```js +// DELETE this line if present: +app.use('/node_modules', express.static(path.join(__dirname, 'node_modules'))); +``` + +### Remove Bryntum Endpoints + +Delete the following Bryntum-specific endpoints and helper functions: +- `app.get('/load', ...)` - Bryntum data loading endpoint +- `app.post('/sync', ...)` - Bryntum sync endpoint +- `applyTableChanges()` function +- `createOperation()` function +- `updateOperation()` function +- `deleteOperation()` function + +### Install DHTMLX Gantt Package and Vite + +Remove Bryntum dependency. If you were using Bryntum via npm, uninstall it: +```bash +npm uninstall @bryntum/gantt +``` + +Install DHTMLX Gantt following the [installation guide](guides/installation.md). + +For this tutorial, we will use the trial version of DHTMLX Gantt: + +```bash +npm install @dhx/trial-gantt +``` + +Let's also install Vite as a build tool: + +```bash +npm install --save-dev vite +``` + +### Add Data Loading Endpoint + +Add the GET endpoint to load data in DHTMLX format: + +```js +import dateFormat from 'date-format-lite'; + +// GET /data - Load tasks and links +app.get('/data', async (req, res) => { + try { + const [[tasks], [links]] = await Promise.all([ + db.query('SELECT * FROM gantt_tasks'), + db.query('SELECT * FROM gantt_links'), + ]); + + // Format dates for DHTMLX Gantt + for (let i = 0; i < tasks.length; i++) { + tasks[i].start_date = tasks[i].start_date.format("YYYY-MM-DD hh:mm:ss"); + tasks[i].end_date = tasks[i].end_date.format("YYYY-MM-DD hh:mm:ss"); + // Format constraint_date if it exists + if (tasks[i].constraint_date) { + tasks[i].constraint_date = tasks[i].constraint_date.format("YYYY-MM-DD hh:mm:ss"); + } + } + + res.json({ + tasks, + links + }); + } catch (error) { + res.status(500).json({ + success: false, + message: error.message, + }); + } +}); +``` + +**Note:** The response format is different from Bryntum. DHTMLX expects `{ tasks: [], links: [] }` instead of Bryntum's nested structure. + +### Add CRUD Endpoints for Tasks and Links + +DHTMLX Gantt DataProcessor uses RESTful endpoints. Add handlers for task operations: + +```js +// Create a new task +app.post("/data/task", async (req, res) => { + const task = getTask(req.body); + const { text, start_date, end_date, duration, progress, parent, constraint_type, constraint_date } = task; + + try { + const [result] = await db.query( + "INSERT INTO gantt_tasks(text, start_date, end_date, duration, progress, parent, constraint_type, constraint_date) VALUES (?,?,?,?,?,?,?,?)", + [text, start_date, end_date, duration, progress, parent, constraint_type, constraint_date] + ); + sendResponse(res, "inserted", result.insertId); + } catch (error) { + sendResponse(res, "error", null, error); + } +}); + +// Update an existing task +app.put("/data/task/:id", async (req, res) => { + const sid = req.params.id; + const task = getTask(req.body); + const { text, start_date, end_date, duration, progress, parent, constraint_type, constraint_date } = task; + + try { + await db.query( + "UPDATE gantt_tasks SET text = ?, start_date = ?, end_date = ?, duration = ?, progress = ?, parent = ?, constraint_type = ?, constraint_date = ? WHERE id = ?", + [text, start_date, end_date, duration, progress, parent, constraint_type, constraint_date, sid] + ); + sendResponse(res, "updated"); + } catch (error) { + sendResponse(res, "error", null, error); + } +}); + +// Delete a task +app.delete("/data/task/:id", async (req, res) => { + const sid = req.params.id; + + try { + await db.query("DELETE FROM gantt_tasks WHERE id = ?", [sid]); + sendResponse(res, "deleted"); + } catch (error) { + sendResponse(res, "error", null, error); + } +}); +``` + +Add handlers for link (dependency) operations: + +```js +// Create a new link +app.post("/data/link", async (req, res) => { + const link = getLink(req.body); + const { source, target, type } = link; + + try { + const [result] = await db.query( + "INSERT INTO gantt_links(source, target, type) VALUES (?,?,?)", + [source, target, type] + ); + sendResponse(res, "inserted", result.insertId); + } catch (error) { + sendResponse(res, "error", null, error); + } +}); + +// Update an existing link +app.put("/data/link/:id", async (req, res) => { + const sid = req.params.id; + const link = getLink(req.body); + const { source, target, type } = link; + + try { + await db.query( + "UPDATE gantt_links SET source = ?, target = ?, type = ? WHERE id = ?", + [source, target, type, sid] + ); + sendResponse(res, "updated"); + } catch (error) { + sendResponse(res, "error", null, error); + } +}); + +// Delete a link +app.delete("/data/link/:id", async (req, res) => { + const sid = req.params.id; + + try { + await db.query("DELETE FROM gantt_links WHERE id = ?", [sid]); + sendResponse(res, "deleted"); + } catch (error) { + sendResponse(res, "error", null, error); + } +}); +``` + +### Add Helper Functions + +Also, let's add utility functions to process data and send responses: + +```js +function getTask(data) { + return { + text: data.text, + start_date: data.start_date.date("YYYY-MM-DD"), + end_date: data.end_date.date("YYYY-MM-DD"), + duration: data.duration, + progress: data.progress || 0, + parent: data.parent, + constraint_type: data.constraint_type || 'asap', + constraint_date: data.constraint_date || null + }; +} + +function getLink(data) { + return { + source: data.source, + target: data.target, + type: data.type + }; +} + +function sendResponse(res, action, tid, error) { + if (action === "error") { + console.log(error); + } + + const result = { action: action }; + + if (tid !== undefined && tid !== null) { + result.tid = tid; + } + + res.send(result); +} +``` + +--- + +## Step 3: Frontend Migration with Vite + +### Set Up Vite Configuration + +Create a `vite.config.js` file in the root of your project: + +```javascript +import { defineConfig } from 'vite'; +import path from 'path'; + +export default defineConfig({ + root: '.', + + server: { + port: 5173, + proxy: { + // Proxy API requests to Express backend + '/data': { + target: 'http://localhost:1337', + changeOrigin: true, + } + } + }, + + build: { + outDir: 'dist', + emptyOutDir: true, + sourcemap: true, + }, + + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}); +``` + +### Restructure Project Files + +Organize your project with this structure: + +``` +dhtmlx-demo/ +├── index.html # Move from public/ to root +├── src/ +│ └── main.js # Create this file for app logic +├── dist/ # Generated by Vite build +├── server.js # Backend +├── vite.config.js # Vite configuration +└── package.json +``` + +### Update index.html + +Move `index.html` to the project root and update it. With Vite, you don't need to manually include CSS and JS files - Vite will bundle them automatically. + +```html + + +
+ + +