Serverseitige Integration

Die beste Methode, um dhtmlxGantt mit einem Backend zu verbinden, besteht darin, eine RESTful API auf dem Server einzurichten und das Modul dataprocessor auf der Clientseite zu verwenden.

DataProcessor ist eine integrierte Funktion, die Änderungen an den Gantt-Daten überwacht und Aktualisierungen im benötigten Format an die REST API sendet. Dadurch wird die Integration mit serverseitigen Plattformen besonders einfach. Bei der Arbeit mit einer Objekt-Datenquelle kann DataProcessor so eingerichtet werden, dass Callbacks für Datenänderungen bereitgestellt werden, was für das Data Binding nützlich ist.

Es gibt auch ein Video-Tutorial, das zeigt, wie man ein Gantt-Diagramm erstellt und Daten darin lädt. Als Beispiel wird Node.js verwendet.

Vorgehensweise

Im Allgemeinen müssen Sie folgende Schritte ausführen, um Daten mithilfe einer REST API vom Server zu laden:

Client-Seite

1) Verwenden Sie die Methode load, um Gantt-Daten zu laden, indem Sie eine URL angeben, die Daten im JSON-Format zurückgibt.

2) Erstellen Sie eine DataProcessor-Instanz auf eine der folgenden Arten:

  • Initialisieren Sie DataProcessor und verknüpfen Sie ihn mit dem dhtmlxGantt-Objekt:
gantt.init("gantt_here");
gantt.load("apiUrl");
 
// belassen Sie die Reihenfolge der folgenden Zeilen
const dp = new gantt.dataProcessor("apiUrl");
dp.init(gantt);
dp.setTransactionMode("REST");
dp.deleteAfterConfirmation = true;

Es wird empfohlen, die zweite Methode zu verwenden.

  • Verwenden Sie die Methode createDataProcessor, indem Sie ein Objekt mit Konfigurationsoptionen übergeben:
const dp = gantt.createDataProcessor({
    url: "apiUrl",
    mode: "REST",
    deleteAfterConfirmation: true
});

Weitere Details finden Sie im folgenden Abschnitt.

DataProcessor erstellen

Beim Erstellen eines DataProcessor über die API-Methode createDataProcessor gibt es verschiedene Möglichkeiten, Parameter zu übergeben.

1. Verwenden Sie einen der vordefinierten Request-Modi, wie folgt:

const dp = gantt.createDataProcessor({
    url: "/api",
    mode: "REST",
    deleteAfterConfirmation: true
});

wobei:

  • url – das Endpoint auf der Serverseite
  • mode – die Methode zum Senden von Daten an den Server: "GET" | "POST" | "REST" | "JSON" | "REST-JSON"
  • deleteAfterConfirmation – bestimmt, ob eine Aufgabe erst nach Bestätigung der Löschung durch den Server aus dem Gantt entfernt werden soll. Abhängigkeiten und Unteraufgaben werden gelöscht, sobald das Löschen der übergeordneten Aufgabe bestätigt wurde.

2. Übergeben Sie ein benutzerdefiniertes router-Objekt:

const dp = gantt.createDataProcessor(router);
  • wobei router eine Funktion sein kann:
// entity - "task"|"link"|"resource"|"assignment"
// action - "create"|"update"|"delete"
// data - ein Objekt mit Task- oder Link-Daten
// id – die ID des verarbeiteten Objekts (Task oder Link)
const dp = gantt.createDataProcessor((entity, action, data, id) => { 
    switch(action) {
        case "create":
        return gantt.ajax.post(
                server + "/" + entity,
                data
        );
        break;
        case "update":
        return gantt.ajax.put(
                server + "/" + entity + "/" + id,
                data
            );
        break;
        case "delete":
        return gantt.ajax.del(
                server + "/" + entity + "/" + id
        );
        break;
    }
});
  • oder ein Objekt, das wie folgt aufgebaut ist:
const dp = gantt.createDataProcessor({
    task: {
        create: (data) => {},
        update: (data, id) => {},
        delete: (id) => {}
    },
    link: {
        create: (data) => {},
        update: (data, id) => {},
        delete: (id) => {}
    }
});

Alle Funktionen im router-Objekt sollten entweder ein Promise oder ein Data-Response-Objekt zurückgeben. Dadurch kann der dataProcessor die Datenbank-ID anwenden und das onAfterUpdate-Event auslösen.

const router = (entity, action, data, id) => {
    return new gantt.Promise((resolve, reject) => {
        // … Logik
        return resolve({ tid: databaseId });
    });
};

Diese Flexibilität ermöglicht es, DataProcessor zum Speichern von Daten in localStorage oder jedem anderen Speicher zu verwenden, der nicht an eine bestimmte URL gebunden ist, oder wenn Erstellung und Löschung von verschiedenen Servern bearbeitet werden.

Related sample:  Custom data api - using local storage

Details zu Requests und Responses

Die URLs folgen diesem Muster:

  • api/link/id
  • api/task/id
  • api/resource/id
  • api/assignment/id

wobei "api" die in der DataProcessor-Konfiguration gesetzte URL ist.

Hier eine Liste möglicher Requests und Responses:

AktionHTTP-MethodeURLResponse
Daten laden GET /apiUrl JSON-Format
Tasks
Neue Aufgabe hinzufügen POST /apiUrl/task {"action":"inserted","tid":"id"}
Aufgabe aktualisieren PUT /apiUrl/task/id {"action":"updated"}
Aufgabe löschen DELETE /apiUrl/task/id {"action":"deleted"}
Links
Neuen Link hinzufügen POST /apiUrl/link {"action":"inserted","tid":"id"}
Link aktualisieren PUT /apiUrl/link/id {"action":"updated"}
Link löschen DELETE /apiUrl/link/id {"action":"deleted"}
Ressourcen
Neue Ressource hinzufügen POST /apiUrl/resource {"action":"inserted","tid":"id"}
Ressource aktualisieren PUT /apiUrl/resource/id {"action":"updated"}
Ressource löschen DELETE /apiUrl/resource/id {"action":"deleted"}
Ressourcenzuweisungen
Neue Zuweisung hinzufügen POST /apiUrl/assignment {"action":"inserted","tid":"id"}
Zuweisung aktualisieren PUT /apiUrl/assignment/id {"action":"updated"}
Zuweisung löschen DELETE /apiUrl/assignment/id {"action":"deleted"}

Standardmäßig sind Ressourcen und Ressourcenzuweisungen nicht in DataProcessor-Requests enthalten. Um sie einzubeziehen, müssen Sie dies explizit aktivieren. Weitere Informationen finden Sie hier.

Request-Parameter

Create-, Update- und Delete-Requests beinhalten alle öffentlichen Eigenschaften eines clientseitigen Task- oder Link-Objekts:

Task:

  • start_date: 2025-04-08 00:00:00
  • duration: 4
  • text: Task #2.2
  • parent: 3
  • end_date: 2025-04-12 00:00:00

Link:

  • source: 1
  • target: 2
  • type: 0

Hinweis:

  • Das Format für start_date und end_date wird durch die date_format-Konfiguration festgelegt.
  • Der Client sendet alle öffentlichen Eigenschaften eines Tasks oder Links, daher können Requests zusätzliche Parameter enthalten.
  • Wenn Sie neue Spalten oder Eigenschaften zu Ihrem Datenmodell hinzufügen, sendet gantt diese automatisch an das Backend.

Öffentliche Eigenschaften sind solche, deren Namen nicht mit einem Unterstrich (_) oder einem Dollarzeichen ($) beginnen, Eigenschaften wie task._owner oder link.$state werden also nicht an das Backend gesendet.

REST-JSON-Modus

Neben den Modi "POST", "GET", "REST" und "JSON" unterstützt der Gantt DataProcessor auch den "REST-JSON"-Modus.

gantt.load("apiUrl");
 
const dp = gantt.createDataProcessor({
    url: "/apiUrl",
    mode: "REST-JSON"
});

Er verwendet die gleichen Request-URLs, aber die Übertragung der Parameter unterscheidet sich.

Im REST-Modus werden Daten als Formulardaten gesendet:

Content-Type: application/x-www-form-urlencoded

Im REST-JSON-Modus hingegen werden Daten als JSON gesendet:

Headers

Content-type: application/json

Parameter werden als JSON-Objekt übertragen:

Request Payload

  • Task
{
    "start_date": "20-09-2025 00:00",
    "text": "New task",
    "duration": 1,
    "end_date": "21-09-2025 00:00",
    "parent": 0,
    "usage": [
        { "id": "1", "value": "30" },
        { "id": "2", "value": "20" }
    ]
}
  • Link
{
    "source": 1,
    "target": 2,
    "type": "0"
}

Dieses Format vereinfacht die Verarbeitung komplexer Datensätze auf der Serverseite.

Server-Seite

Immer wenn sich im Gantt etwas ändert (Hinzufügen, Aktualisieren oder Löschen von Tasks oder Links), sendet der dataProcessor eine AJAX-Anfrage an den Server.

Jede Anfrage enthält alle notwendigen Daten, um die Datenbank zu aktualisieren. Da der dataProcessor auf REST-Modus eingestellt ist, werden je nach Operation unterschiedliche HTTP-Verben verwendet.

Mit der REST-API kann die Serverseite mit verschiedenen Frameworks und Sprachen implementiert werden. Hier einige serverseitige Implementierungen, die für die Gantt-Backend-Integration bereit sind:

Speichern der Aufgabenreihenfolge

Gantt zeigt Aufgaben in der Reihenfolge an, in der sie aus der Datenquelle kommen. Falls Benutzer Aufgaben manuell umsortieren können, sollten Sie diese Reihenfolge in Ihrer Datenbank speichern und sicherstellen, dass Ihr Datenfeed die Aufgaben entsprechend sortiert zurückgibt.

Clientseitige Konfiguration:

// Aufgabenreihenfolge im gesamten Gantt umsortieren
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
 
gantt.init("gantt_here");
gantt.load("/api");
 
const dp = gantt.createDataProcessor({
    url: "/api",
    mode: "REST"
});

Es gibt verschiedene Möglichkeiten, die Reihenfolge zu speichern; hier ein Beispiel.

  • Fügen Sie Ihrer Tasks-Tabelle eine numerische Spalte hinzu, z.B. 'sortorder'.
  • Sortieren Sie beim Bearbeiten eines GET-Requests die Aufgaben aufsteigend nach dieser Spalte.
  • Weisen Sie beim Hinzufügen einer neuen Aufgabe den Wert MAX(sortorder) + 1 zu.
  • Wenn sich die Reihenfolge clientseitig ändert, sendet gantt ein PUT (oder POST, falls kein REST-Modus verwendet wird) mit allen Task-Eigenschaften sowie Werten, die die Position der Aufgabe im Projektbaum beschreiben.
HTTP-MethodeURLParameterResponse
PUT /apiUrl/task/taskId target=adjacentTaskId {"action":"updated"}

Der target-Parameter enthält die ID der nächstgelegenen Aufgabe entweder unmittelbar vor oder nach der aktuellen Aufgabe.

Sein Wert kann zwei Formate haben:

  • target=targetId – platziert die aktuelle Aufgabe direkt vor der Aufgabe mit targetId
  • target=next:targetId – platziert die aktuelle Aufgabe direkt nach der Aufgabe mit targetId

Das Anwenden von Änderungen an der Reihenfolge erfordert in der Regel das Aktualisieren mehrerer Aufgaben. Hier ein Pseudocode-Beispiel:

const target = request["target"];
const currentTaskId = request["id"];
let nextTask;
let targetTaskId;
 
// Feststellen, ob die aktualisierte Aufgabe vor oder nach der benachbarten Aufgabe platziert wird
if (target.startsWith("next:")) {
  targetTaskId = target.substr("next:".length);
  nextTask = true;
} else {
  targetTaskId = target;
  nextTask = false;
}
 
const currentTask = tasks.getById(currentTaskId);
const targetTask = tasks.getById(targetTaskId);
 
if (!targetTaskId) return;
 
// Weisen Sie der aktualisierten Aufgabe die sortorder der benachbarten Aufgabe zu
let targetOrder = targetTask.sortorder;
 
// Falls nach der benachbarten Aufgabe, sortorder inkrementieren
if (nextTask) targetOrder++;
 
// Erhöhen Sie die sortorder-Werte der nachfolgenden Aufgaben
tasks.where(task => task.sortorder >= targetOrder)
    .update(task => task.sortorder++);
 
// Aktualisieren Sie die Aufgabe mit ihrer neuen sortorder
currentTask.sortorder = targetOrder;
 
tasks.save(currentTask);

Detaillierte Beispiele für das Speichern der Aufgabenreihenfolge auf bestimmten Server-Plattformen finden Sie unter: plain PHP, Laravel, Node.js, ASP.NET Web API, und Rails.

Eigene Request-Header und Parameter

Hinzufügen benutzerdefinierter Request-Header

Es ist möglich, zusätzliche Header in Anfragen an Ihr Backend einzufügen. Beispielsweise möchten Sie vielleicht ein Autorisierungstoken zu Ihren Anfragen hinzufügen:

gantt.init("gantt_here");
gantt.load("/api");
 
const dp = gantt.createDataProcessor({
    url: "/api",
    mode:"REST",
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        "Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
    }
});

Derzeit unterstützt load keine Header- oder Payload-Parameter für GET-Anfragen. Falls Sie diese hinzufügen müssen, senden Sie die XHR-Anfrage manuell und laden dann die Daten mit parse in Gantt, wie folgt:

gantt.ajax.get({
    url: "/api",
    headers: {
        "Authorization": "Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
    }
}).then(xhr => {
    gantt.parse(xhr.responseText);
});

Hinzufügen benutzerdefinierter Parameter zur Anfrage

Es gibt mehrere Möglichkeiten, zusätzliche Parameter zu Ihren Anfragen hinzuzufügen.

Da gantt alle Eigenschaften des Datenobjekts an das Backend sendet, können Sie einfach eine zusätzliche Eigenschaft direkt zum Datenobjekt hinzufügen, die dann in die Anfrage aufgenommen wird:

gantt.attachEvent("onTaskCreated", (task) => {
    task.userId = currentUser;
    return true;
});

Eine weitere Möglichkeit ist, benutzerdefinierte Parameter zu jeder vom DataProcessor gesendeten Anfrage über die payload-Eigenschaft hinzuzufügen:

gantt.init("gantt_here");
gantt.load("/api");
 
const dp = gantt.createDataProcessor({
    url: "/api",
    mode: "REST",
    payload: {
        token: "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
    }
});

Sie können benutzerdefinierte Parameter auch mithilfe des onBeforeUpdate Events von DataProcessor zu Anfragen hinzufügen:

const dp = gantt.createDataProcessor({
    url: "/api",
    mode: "REST",
});
 
dp.attachEvent("onBeforeUpdate", (id, state, data) => {
    data.projectId = "1";
    return true;
});

Auslösen der Datenspeicherung per Skript

Wenn der DataProcessor initialisiert ist, werden alle Änderungen, die von Nutzern oder programmatisch vorgenommen werden, automatisch an die Datenquelle gespeichert.

Um eine bestimmte Aufgabe oder Abhängigkeit programmatisch zu aktualisieren, verwenden Sie in der Regel die Methoden updateTask und updateLink:

gantt.parse([
    { id: 1, text: "Task 1", start_date: "2025-05-13 06:00", duration: 2 },
    { id: 2, text: "Task 2", start_date: "2025-05-19 08:00", duration: 3 }
]);
 
const task = gantt.getTask(1);
task.text = "Task 37"; // Aufgabendaten aktualisieren
gantt.updateTask(1); // Aktualisierte Aufgabe neu rendern

Weitere Methoden, die das Senden von Updates an das Backend auslösen, sind:

Custom Routing

Wenn die RESTful AJAX API nicht zu den Anforderungen Ihres Backends passt oder Sie genau steuern möchten, was an den Server gesendet wird, kann ein benutzerdefiniertes Routing verwendet werden.

In Frameworks wie Angular oder React sendet eine Komponente Änderungen möglicherweise nicht direkt an den Server, sondern gibt sie an eine andere Komponente weiter, die für das Speichern der Daten zuständig ist.

Um ein benutzerdefiniertes Routing für den DataProcessor einzurichten, verwenden Sie die Methode createDataProcessor():

gantt.createDataProcessor(function(entity, action, data, id) {
    const services = {
        "task": this.taskService,
        "link": this.linkService
    };
    const service = services[entity];
 
    switch (action) {
        case "update":
            return service.update(data);
        case "create":
            return service.insert(data);
        case "delete":
            return service.remove(id);
    }
});

Related sample:  Custom data api - using local storage

AJAX zum Einrichten benutzerdefinierter Router verwenden

Das Gantt AJAX Modul kann beim Einrichten benutzerdefinierter Routen hilfreich sein. Gantt erwartet, dass der benutzerdefinierte Router ein Promise-Objekt von einer Operation zurückgibt, wodurch erkannt werden kann, wann die Aktion abgeschlossen ist.
Das AJAX-Modul unterstützt Promises und eignet sich daher für den Einsatz in benutzerdefinierten Routern. Gantt verarbeitet das Promise und dessen Inhalt, sobald es aufgelöst wurde.

Im folgenden Beispiel wird eine neue Aufgabe erstellt. Wenn die Serverantwort die ID der neu erstellten Aufgabe enthält, wird diese von Gantt entsprechend übernommen.

gantt.createDataProcessor((entity, action, data, id) => {
    ...
    switch (action) {
        case "create":
            return gantt.ajax.post({
                headers: {
                    "Content-Type": "application/json"
                },
                url: `${server}/task`,
                data: JSON.stringify(data)
            });
    }
});

Routing von CRUD-Aktionen für Ressourcen und Ressourcenzuweisungen

Ab Version 8.0 können Änderungen an Ressourcenzuweisungen als separate Einträge mit persistierenden IDs an den DataProcessor gesendet werden, was die Integration mit Backend-APIs vereinfacht. Änderungen an Ressourcenobjekten selbst können ebenfalls an den DataProcessor gesendet werden.

Beachten Sie, dass diese Funktion standardmäßig deaktiviert ist. Standardmäßig empfängt der DataProcessor nur Änderungen an Aufgaben und Verknüpfungen. Um die Ressourcenverarbeitung zu aktivieren, stellen Sie Folgendes ein:

gantt.config.resources = {
    dataprocessor_assignments: true,
    dataprocessor_resources: true,
};

Wenn der Ressourcenmodus aktiviert ist und der DataProcessor sich im REST-Modus befindet, werden Ressourcen und Ressourcenzuweisungen in separaten Anfragen an das Backend gesendet.

Wenn der DataProcessor im Custom Routing-Modus verwendet wird, können Sie Änderungen an Ressourcenzuweisungen und Ressourcen in Ihrem Handler erfassen:

gantt.createDataProcessor({
    task: {
        create: (data) => {
            return createRecord({type: "task", ...data}).then((res) => {
                return { tid: res.id, ...res };
            });
        },
        update: (data, id) => {
            return updateRecord({type: "task", ...data}).then(() => ({}));
        },
        delete: (id) => {
            return deleteRecord({type: "task:", id: id}).then(() => ({}));
        }
    },
    link: {
        create: (data) => {
            ...
        },
        update: (data, id) => {
            ...
        },
        delete: (id) => {
            ...
        }
    },
    assignment: {
        create: (data) => {
            ...
        },
        update: (data, id) => {
            ...
        },
        delete: (id) => {
            ...
        }
    },
    resource: {
        create: (data) => {
            ...
        },
        update: (data, id) => {
            ...
        },
        delete: (id) => {
            ...
        }
    }
});

Alternativ mit einer Funktionsdeklaration:

gantt.createDataProcessor((entity, action, data, id) => {
    switch (entity) {
        case "task":
            break;
        case "link":
            break;
        case "resource":
            break;
        case "assignment":
            break;
    }
});

Fehlerbehandlung

Wenn der Server meldet, dass eine Aktion fehlgeschlagen ist, kann er eine Antwort mit "action":"error" zurückgeben:

{"action":"error"}

Solche Antworten können Sie auf der Clientseite mit gantt.dataProcessor abfangen:

const dp = gantt.createDataProcessor({
    url: "/api",
    mode: "REST"
});
 
dp.attachEvent("onAfterUpdate", (id, action, tid, response) => {
    if (action === "error") {
        // Fehler hier behandeln
    }
});

Das Antwortobjekt kann zusätzliche Eigenschaften enthalten, die über das Argument response im onAfterUpdate-Handler zugänglich sind.

Dieses Event wird nur für verwaltete Fehler ausgelöst, die JSON-Antworten wie oben gezeigt zurückgeben. Für die Behandlung von HTTP-Fehlern siehe das API-Event onAjaxError.

Wenn der Server mit einem Fehler antwortet, aber die Änderungen auf dem Client gespeichert wurden, ist es am besten, den Client-Status zu löschen und die korrekten Daten erneut vom Server zu laden:

dp.attachEvent("onAfterUpdate", (id, action, tid, response) => {
    if (action === "error") {
        gantt.clearAll();
        gantt.load("url1");
    }
});

Falls Sie den Status von Client und Server synchronisieren möchten, ohne Serveraufrufe auszuführen, verwenden Sie die Methode silent(), um interne Events oder Serveraufrufe während der Operation zu verhindern:

gantt.silent(() => {
    gantt.deleteTask(item.id);
});
 
gantt.render();

Kaskadierende Löschung

Standardmäßig löst das Löschen einer Aufgabe eine kaskadierende Löschung ihrer verschachtelten Aufgaben und zugehörigen Verknüpfungen aus. Gantt sendet für jede entfernte Aufgabe und Verknüpfung eine delete-Anfrage.
Das bedeutet, dass die Datenintegrität im Backend nicht manuell verwaltet werden muss, da Gantt dies effektiv übernimmt.

Dieses Vorgehen kann jedoch zu vielen AJAX-Anfragen an das Backend führen, da dhtmlxGantt keine Batch-Anfragen unterstützt und die Anzahl der Aufgaben und Verknüpfungen groß sein kann.

Falls erforderlich, kann die kaskadierende Löschung mit der cascade_delete Konfiguration deaktiviert werden.
Ist dies deaktiviert, führt das Löschen eines Projektzweigs nur zu einer Löschanfrage für das oberste Element und das Backend ist für das Löschen der zugehörigen Verknüpfungen und Unteraufgaben verantwortlich.

XSS-, CSRF- und SQL-Injection-Angriffe

Es ist wichtig zu beachten, dass Gantt keinen eingebauten Schutz gegen Bedrohungen wie SQL-Injection, XSS oder CSRF-Angriffe bietet.
Die Sicherstellung der Anwendungssicherheit liegt in der Verantwortung der Entwickler, die das Backend implementieren.

Weitere Informationen zu den verwundbarsten Punkten der Komponente und empfohlenen Maßnahmen zur Verbesserung der Sicherheit Ihrer Anwendung finden Sie im Artikel Application Security.

Zurück nach oben