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.
Im Allgemeinen müssen Sie folgende Schritte ausführen, um Daten mithilfe einer REST API vom Server zu laden:
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:
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.
const dp = gantt.createDataProcessor({
url: "apiUrl",
mode: "REST",
deleteAfterConfirmation: true
});
Weitere Details finden Sie im folgenden Abschnitt.
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:
2. Übergeben Sie ein benutzerdefiniertes router-Objekt:
const dp = gantt.createDataProcessor(router);
// 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;
}
});
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
Die URLs folgen diesem Muster:
wobei "api" die in der DataProcessor-Konfiguration gesetzte URL ist.
Hier eine Liste möglicher Requests und Responses:
Aktion | HTTP-Methode | URL | Response |
---|---|---|---|
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.
Create-, Update- und Delete-Requests beinhalten alle öffentlichen Eigenschaften eines clientseitigen Task- oder Link-Objekts:
Task:
Link:
Hinweis:
Ö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.
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
{
"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" }
]
}
{
"source": 1,
"target": 2,
"type": "0"
}
Dieses Format vereinfacht die Verarbeitung komplexer Datensätze auf der Serverseite.
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:
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.
MAX(sortorder) + 1
zu.HTTP-Methode | URL | Parameter | Response |
---|---|---|---|
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:
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.
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);
});
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;
});
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:
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
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)
});
}
});
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;
}
});
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();
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.
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