Einrichtung von dhtmlxGantt mit Node.js

Diese Anleitung beschreibt die Erstellung einer Gantt-Diagramm-Anwendung mit Node.js und einer REST-API für die serverseitige Kommunikation. Für die Datenbankverwaltung wird MySQL verwendet. Wenn Sie einen anderen Technologiestack verwenden, gibt es andere Integrationsmöglichkeiten:

Diese Implementierung nutzt vorhandene Node.js-Module, um Boilerplate-Coding zu minimieren. Der vollständige Quellcode ist auf GitHub verfügbar.

Ein Video-Tutorial steht ebenfalls zur Verfügung, um Ihnen bei der Erstellung eines Gantt-Diagramms mit Node.js zu helfen.

Schritt 1: Einrichtung des Projekts

Beginnen Sie mit der Erstellung eines Ordners für Ihr Projekt und der Installation der erforderlichen Abhängigkeiten. Die folgenden Module werden verwendet:

  • Express - ein leichtgewichtiges Framework für Node.js
  • body-parser - eine Middleware zum Parsen von Anfragekörpern

Erstellen Sie einen Projektordner namens "dhx-gantt-app" und navigieren Sie in diesen:

mkdir dhx-gantt-app
cd dhx-gantt-app

Hinzufügen von Abhängigkeiten

Erzeugen Sie eine package.json-Datei mit dem folgenden Befehl:

npm init -y

Nachdem die Datei erstellt wurde, aktualisieren Sie sie, um die notwendigen Abhängigkeiten einzuschließen. Sie sollte folgendermaßen aussehen:

package.json

{
  "name": "dhx-gantt-app",
  "version": "1.0.2",
  "description": "",
  "main": "server.js",
  "dependencies": {
    "body-parser": "^1.19.1",
    "express": "^4.17.2"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "MIT"
}

Installieren Sie die Abhängigkeiten mit:

npm install

Backend-Einrichtung

Das Backend wird eine einfache Express-Einrichtung mit einer einzigen JavaScript-Datei (server.js) für die Serverlogik, einem Ordner für statische Dateien (public) und einer HTML-Seite sein.

Die Projektstruktur wird folgendermaßen aussehen:

dhx-gantt-app
├── node_modules
├── server.js 
├── package.json 
└── public 
    └── index.html

Erstellen Sie eine server.js-Datei und fügen Sie den folgenden Code hinzu:

server.js

const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
 
const port = 1337;
const app = express();
 
app.use(express.static(path.join(__dirname, "public")));
app.use(bodyParser.urlencoded({ extended: true }));
 
app.listen(port, () =>{
    console.log("Server is running on port "+port+"...");
});

Dieser Code richtet den Server ein, um:

  • Statische Dateien aus dem public-Ordner bereitzustellen
  • Auf Port 1337 zu lauschen

Erstellen Sie als Nächstes den public-Ordner, um die Hauptseite (index.html) zu speichern.

Der public-Ordner ist auch der Ort, an dem Sie die JavaScript- und CSS-Dateien für dhtmlxGantt ablegen können. Zur Vereinfachung wird in diesem Tutorial Gantt von einem CDN geladen, sodass nur die HTML-Seite enthalten ist.

Schritt 2: Hinzufügen von Gantt zur Seite

Erstellen Sie den public-Ordner und fügen Sie eine index.html-Datei mit folgendem Inhalt hinzu:

index.html

<!DOCTYPE html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 
  <script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
  <link href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css" rel="stylesheet">
 
  <style type="text/css">
    html, body {
      height: 100%;
      padding: 0;
      margin: 0;
      overflow: hidden;
    }
</style> </head> <body> <div id="gantt_here" style='width:100%; height:100%;'></div> <script type="text/javascript">
    gantt.init("gantt_here");
</script> </body>

Starten Sie den Server mit:

node server.js

Besuchen Sie http://127.0.0.1:1337 in Ihrem Browser, um ein leeres Gantt-Diagramm zu sehen.

Schritt 3: Datenbankeinrichtung

Erstellen Sie eine Datenbank mit zwei Tabellen für Aufgaben und Verknüpfungen:

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`)
);
CREATE TABLE `gantt_tasks` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `text` VARCHAR(255) NOT NULL,
  `start_date` datetime NOT NULL,
  `duration` INT(11) NOT NULL,
  `progress` FLOAT NOT NULL,
  `parent` INT(11) NOT NULL,
  PRIMARY KEY (`id`)
);

Fügen Sie einige Testdaten hinzu:

INSERT INTO `gantt_tasks` VALUES 
('1', 'Project #1', '2017-04-01 00:00:00', '5', '0.8', '0'),
('2', 'Task #1', '2017-04-06 00:00:00', '4', '0.5', '1'),
('3', 'Task #2', '2017-04-05 00:00:00', '6', '0.7', '1'),
('4', 'Task #3', '2017-04-07 00:00:00', '2', '0', '1'),
('5', 'Task #1.1', '2017-04-05 00:00:00', '5', '0.34', '2'),
('6', 'Task #1.2', '2017-04-11 13:22:17', '4', '0.5', '2'),
('7', 'Task #2.1', '2017-04-07 00:00:00', '5', '0.2', '3'),
('8', 'Task #2.2', '2017-04-06 00:00:00', '4', '0.9', '3');

Für weitere Details siehe dieses Beispiel.

Schritt 4: Daten laden

Um Daten zu laden, installieren Sie die notwendigen MySQL-Module:

npm install bluebird@3.7.2 --save
npm install promise-mysql@5.1.0 --save
npm install date-format-lite@17.7.0 --save

Aktualisieren Sie server.js, um Folgendes einzuschließen:

server.js

const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
 
const port = 1337;
const app = express();
 
app.use(express.static(path.join(__dirname, "public")));
app.use(bodyParser.urlencoded({ extended: true }));
 
app.listen(port, () =>{
    console.log("Server is running on port "+port+"...");
});
 
const Promise = require('bluebird');
require("date-format-lite");
 
const mysql = require('promise-mysql');
async function serverСonfig() {
    const db = await mysql.createPool({
        host: 'localhost',
        user: 'root',
        password: '',
        database: 'gantt_howto_node'
    });
    app.get("/data", (req, res) => {
        Promise.all([
            db.query("SELECT * FROM gantt_tasks"),
            db.query("SELECT * FROM gantt_links")
        ]).then(results => {
            let tasks = results[0],
                links = results[1];
 
            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].open = true;
            }
 
            res.send({
                data: tasks,
                collections: { links: links }
            });
 
        }).catch(error => {
            sendResponse(res, "error", null, error);
        });
    });
 
    function sendResponse(res, action, tid, error) {
 
        if (action == "error")
            console.log(error);
 
        let result = {
            action: action
        };
        if (tid !== undefined && tid !== null)
            result.tid = tid;
 
        res.send(result);
    }
};
serverСonfig();

Dieser Code verbindet sich mit der Datenbank und richtet eine Route (GET /data) ein, um Aufgaben- und Verknüpfungsdaten abzurufen und diese für den Client zu formatieren.

Aktualisieren Sie index.html, um die Daten zu laden:

public/index.html

gantt.config.date_format = "%Y-%m-%d %H:%i:%s"; 
gantt.init("gantt_here");
 
gantt.load("/data");

Besuchen Sie http://127.0.0.1:1337, um das Gantt-Diagramm mit Testdaten gefüllt zu sehen.

Schritt 5: Änderungen speichern

Um clientseitige Updates zu verarbeiten, richten Sie das Speichern von Daten ein. Fügen Sie einen gantt.dataProcessor zu index.html hinzu:

public/index.html

gantt.config.date_format = "%Y-%m-%d %H:%i:%s";
 
gantt.init("gantt_here");
 
gantt.load("/data");
 
const dp = new gantt.dataProcessor("/data");dp.init(gantt);dp.setTransactionMode("REST");

Dies ermöglicht es, clientseitige Updates zurück an den Server zu senden.

Anfragen und Antworten

Immer wenn Benutzer Aktionen wie das Hinzufügen, Ändern oder Entfernen von Aufgaben oder Verknüpfungen ausführen, sendet der DataProcessor eine AJAX-Anfrage an die entsprechende URL. Diese Anfragen enthalten alle notwendigen Parameter, um die Änderungen in der Datenbank zu speichern.

Da der DataProcessor im REST-Modus arbeitet, verwendet er spezifische HTTP-Verben für verschiedene Operationen. Eine detaillierte Liste dieser Verben zusammen mit Anfragen- und Antwortdetails finden Sie in der REST API-Dokumentation.

Der nächste Schritt besteht darin, die notwendigen Routen und Handler in der server.js-Datei hinzuzufügen, um sicherzustellen, dass alle auf der Clientseite vorgenommenen Änderungen korrekt in der Datenbank widergespiegelt werden. So sieht der Code aus:

server.js

// add a new task
app.post("/data/task", (req, res) => {
    let task = getTask(req.body);
 
    db.query("INSERT INTO gantt_tasks(text, start_date, duration, progress, parent)"
        + " VALUES (?,?,?,?,?)",
        [task.text, task.start_date, task.duration, task.progress, task.parent])
    .then(result => {
        sendResponse(res, "inserted", result.insertId);
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
 
// update a task
app.put("/data/task/:id", (req, res) => {
    let sid = req.params.id,
        task = getTask(req.body);
 
    db.query("UPDATE gantt_tasks SET text = ?, start_date = ?, "
        + "duration = ?, progress = ?, parent = ? WHERE id = ?",
        [task.text, task.start_date, task.duration, task.progress, task.parent, sid])
    .then(result => {
        sendResponse(res, "updated");
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
 
// delete a task
app.delete("/data/task/:id", (req, res) => {
    let sid = req.params.id;
    db.query("DELETE FROM gantt_tasks WHERE id = ?", [sid])
    .then(result => {
        sendResponse(res, "deleted");
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
 
// add a link
app.post("/data/link", (req, res) => {
    let link = getLink(req.body);
 
    db.query("INSERT INTO gantt_links(source, target, type) VALUES (?,?,?)",
        [link.source, link.target, link.type])
    .then(result => {
        sendResponse(res, "inserted", result.insertId);
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
 
// update a link
app.put("/data/link/:id", (req, res) => {
    let sid = req.params.id,
        link = getLink(req.body);
 
    db.query("UPDATE gantt_links SET source = ?, target = ?, type = ? WHERE id = ?",
        [link.source, link.target, link.type, sid])
    .then(result => {
        sendResponse(res, "updated");
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
 
// delete a link
app.delete("/data/link/:id", (req, res) => {
    let sid = req.params.id;
    db.query("DELETE FROM gantt_links WHERE id = ?", [sid])
    .then(result => {
        sendResponse(res, "deleted");
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
 
function getTask(data) {
    return {
        text: data.text,
        start_date: data.start_date.date("YYYY-MM-DD"),
        duration: data.duration,
        progress: data.progress || 0,
        parent: data.parent
    };
}
 
function getLink(data) {
    return {
        source: data.source,
        target: data.target,
        type: data.type
    };
}

Dieses Setup umfasst Routen zur Behandlung von Operationen sowohl für Aufgaben als auch für Verknüpfungen. Anfragen, die sich auf Aufgaben beziehen, verwenden die URL "/data/task", während diejenigen für Verknüpfungen die URL "/data/link" verwenden.

Die Operationen sind einfach:

  • POST wird verwendet, um neue Elemente zur Datenbank hinzuzufügen.
  • PUT wird verwendet, um vorhandene Datensätze zu ändern.
  • DELETE wird verwendet, um Elemente zu entfernen.

Antworten werden als JSON-Objekte zurückgesendet, die den Typ der durchgeführten Operation oder einen Fehler angeben, falls etwas schiefgeht. Bei POST-Anfragen enthält die Antwort auch die Datenbank-ID des neu erstellten Datensatzes, was hilft, das neue Element auf der Clientseite zuzuordnen.

Sobald alles eingerichtet ist, sollte das Öffnen von http://127.0.0.1:1337 ein voll funktionsfähiges Gantt-Diagramm anzeigen.

Gantt-Diagramm


Speichern der Aufgabenreihenfolge

Das Gantt-Diagramm auf der Clientseite ermöglicht es Benutzern, Aufgaben durch Ziehen und Ablegen neu zu ordnen. Wenn Sie diese Funktion nutzen möchten, müssen Sie die Aufgabenreihenfolge in der Datenbank speichern. Detaillierte Informationen hierzu finden Sie hier.

So können Sie diese Funktion in der Anwendung aktivieren.

Aktivieren der Aufgabenreihenfolge im Client

Beginnen Sie damit, die Aufgabenreihenfolge in der Benutzeroberfläche zu aktivieren. Aktualisieren Sie die Gantt-Konfiguration in der "Index"-Ansicht wie folgt:

public/index.html

gantt.config.order_branch = true;gantt.config.order_branch_free = true; 
gantt.init("gantt_here");

Nächste, spiegeln Sie diese Änderungen im Backend wider. Die Aufgabenreihenfolge wird in einer Spalte namens sortorder gespeichert. Aktualisieren Sie die Tabelle gantt_tasks wie folgt:

CREATE TABLE `gantt_tasks` (
  `id` int(11) NOT NULL  AUTO_INCREMENT PRIMARY KEY,
  `text` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `start_date` datetime NOT NULL,
  `duration` int(11) NOT NULL,
  `progress` float NOT NULL DEFAULT 0,
  `parent` int(11) NOT NULL,
  `sortorder` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Wenn die Tabelle bereits existiert, können Sie einfach die Spalte hinzufügen:

ALTER TABLE `gantt_tasks` ADD COLUMN `sortorder` int(11) NOT NULL;

Aktualisieren Sie nun die server.js-Datei:

  1. Ändern Sie GET /data, um Aufgaben in der Reihenfolge der sortorder-Spalte zurückzugeben:

server.js

app.get("/data", (req, res) => {
    Promise.all([
        db.query("SELECT * FROM gantt_tasks ORDER BY sortorder ASC"),         db.query("SELECT * FROM gantt_links")
    ]).then(results => {
        let tasks = results[0],
            links = results[1];
 
        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].open = true;
        }
 
        res.send({
            data: tasks,
            collections: { links: links }
        });
 
    }).catch(error => {
        sendResponse(res, "error", null, error);
    });
});
  1. Weisen Sie neu hinzugefügten Aufgaben einen anfänglichen sortorder-Wert zu:

server.js

app.post("/data/task", (req, res) => {
    let task = getTask(req.body);
 
    db.query("SELECT MAX(sortorder) AS maxOrder FROM gantt_tasks")
    .then(result => {          let orderIndex = (result[0].maxOrder || 0) + 1;         return db.query("INSERT INTO gantt_tasks(text, start_date, duration," 
          + "progress, parent, sortorder) VALUES (?,?,?,?,?,?)",
          [task.text, task.start_date, task.duration, task.progress, task.parent, 
            orderIndex]);     })
    .then(result => {
        sendResponse(res, "inserted", result.insertId);
    })
    .catch(error => {
        sendResponse(res, "error", null, error);
    });
});
  1. Aktualisieren Sie die Aufgabenreihenfolge, wenn Benutzer Aufgaben neu ordnen:

server.js

// update task
app.put("/data/task/:id", (req, res) => {
  let sid = req.params.id,
    target = req.body.target,
    task = getTask(req.body);
 
  Promise.all([
    db.query("UPDATE gantt_tasks SET text = ?, start_date = ?," 
      + "duration = ?, progress = ?, parent = ? WHERE id = ?",
      [task.text, task.start_date, task.duration, task.progress, 
        task.parent, sid]),
    updateOrder(sid, target)   ])
    .then(result => {
      sendResponse(res, "updated");
    })
    .catch(error => {
      sendResponse(res, "error", null, error);
    });
});
 
function updateOrder(taskId, target) {
  let nextTask = false;
  let targetOrder;
 
  target = target || "";
 
  if (target.startsWith("next:")) {
    target = target.substr("next:".length);
    nextTask = true;
  }
 
  return db.query("SELECT * FROM gantt_tasks WHERE id = ?", [target])
    .then(result => {
      if (!result[0])
        return Promise.resolve();
 
      targetOrder = result[0].sortorder;
      if (nextTask)
        targetOrder++;
 
      return db.query("UPDATE gantt_tasks SET sortorder"+
        " = sortorder + 1 WHERE sortorder >= ?", [targetOrder])
      .then(result => {
        return db.query("UPDATE gantt_tasks SET sortorder = ? WHERE id = ?",
          [targetOrder, taskId]);
      });
    });
}

Ein gebrauchsfertiges Demo ist auf GitHub verfügbar.


Anwendungssicherheit

Die Gantt-Komponente enthält keine eingebauten Mechanismen zum Schutz vor Bedrohungen wie SQL-Injektionen, XSS oder CSRF-Angriffen. Es liegt in der Verantwortung der Entwickler, sicherzustellen, dass die Anwendung sicher ist. Weitere Details finden Sie im Sicherheitsleitfaden.


Fehlerbehebung

Wenn das Gantt-Diagramm nach Abschluss der Einrichtung keine Aufgaben und Verknüpfungen rendert, schlagen Sie im Fehlerbehebungsleitfaden nach, um Hilfe bei der Diagnose des Problems zu erhalten.


Was kommt als Nächstes?

Jetzt haben Sie ein voll funktionsfähiges Gantt-Diagramm. Der vollständige Code ist auf GitHub verfügbar, wo Sie ihn für Ihre Projekte klonen oder herunterladen können.

Erkunden Sie zusätzliche Anleitungen zu Gantt-Funktionen oder Tutorials zur Integration von Gantt mit anderen Backend-Frameworks.

Zurück nach oben