Working with server
DHTMLX To Do List works with both client and server data. The widget has no special backend requirements and connects to any platform that supports a REST API.
RestDataProvider
The To Do List provides the RestDataProvider service that talks to a REST backend and handles the following data operations:
"add-task"— add a task"update-task"— update a task"delete-task"— delete a task"add-project"— add a project"update-project"— update a project"delete-project"— delete a project"set-project"— set the active project"move-task"— move a task"clone-task"— clone a task
REST methods
The RestDataProvider service includes the following REST methods for dynamic data loading:
getProjects()— get a promise with the projects datagetProjectTasks()— get a promise with the tasks data of the specifiedprojectgetTags()— get a promise with the list of default tagsgetTasks()— get a promise with the tasks datagetUsers()— get a promise with the users datasetAPI()— set the API of the To Do List component into the RestDataProvidersend()— send a request to the server and get a promise with or without data, depending on the request
Interact with the backend
To interact with the server, connect RestDataProvider to the server scripts. The built-in backend is available in two repositories:
You can also create a custom backend.
If you use a custom backend, refer to the REST API routes topic for more information.
To connect RestDataProvider to the backend, call the new RestDataProvider() constructor and pass the corresponding URL as a parameter. The example below initializes the To Do List with data loaded from the server:
const { ToDo, Toolbar, RestDataProvider } = todo;
const activeProject = null;
const url = "https://some_backend_url";
const restProvider = new RestDataProvider(url);
Promise.all([
restProvider.getProjectTasks(activeProject),
restProvider.getUsers(),
restProvider.getProjects(),
restProvider.getTags(),
]).then(([tasks, users, projects, tags]) => {
const list = new ToDo("#root", {
tasks,
users,
projects,
tags,
activeProject,
});
const toolbar = new Toolbar("#toolbar", {
api: list.api,
});
list.api.setNext(restProvider);
restProvider.setAPI(list.api);
});
Include RestDataProvider in the Event Bus order through the api.setNext() method to perform data operations (adding, deleting, and other actions) and send the corresponding requests to the server.
Example
The snippet below connects RestDataProvider to the backend and loads server data dynamically:
Multiuser backend
The multiuser feature lets multiple users manage the same tasks in real time without page reloads. Users see each other's changes immediately.
Before initializing the To Do List, authorize on the server. The example below defines a login() function:
const login = (url) => {
const token = sessionStorage.getItem("login-token");
if (token) {
return Promise.resolve(token);
}
return fetch(url + "/login?id=1")
.then(raw => raw.text())
.then(token => {
sessionStorage.setItem("login-token", token);
return token;
});
}
The login() function returns the server token required for all subsequent interactions with the server. Include the token in each request as the value of the Remote-Token header. The snippet below sets the token header:
login(url).then(token => {
const restProvider = new todo.RestDataProvider(url);
restProvider.setHeaders({
"Remote-Token": token,
});
After initialization, add a WebSocket to listen for events from the server. The example below sets up the WebSocket and handlers:
Promise.all([
restProvider.getProjectTasks(activeProject),
restProvider.getUsers(),
restProvider.getProjects(),
restProvider.getTags(),
]).then(([tasks, users, projects, tags]) => {
const list = new todo.ToDo("#root", {
tasks, users, projects, tags, activeProject,
});
const toolbar = new todo.Toolbar("#toolbar", {
api: list.api,
});
// save data from client to server
list.api.setNext(restProvider);
restProvider.setAPI(list.api);
// get updates from server to client
const events = new todo.RemoteEvents(url + "/api/v1", token);
const handlers = todo.todoUpdates(
list.api,
restProvider.getIDResolver()
);
events.on(handlers);
});
Example
The snippet below configures the multiuser backend to track changes from other users in real time:
Customize server events
Define your own logic for handling server events. Pass the handlers object to the RemoteEvents.on(handlers) method. The handlers object has the following structure:
{
"tasks": tasksHandler: function(obj: any),
"projects": projectsHandler: function(obj: any)
}
When a change occurs on the server, the server returns the name of the modified element. The names can vary depending on the server logic.
The updated data arrives in the handler's obj argument. Its type field specifies the operation:
- for
tasks:"add-task","update-task","delete-task","move-task","clone-task" - for
projects:"add-project","update-project","delete-project","set-sort"
The following code snippet shows the implementation details:
const todoInstance = new todo.ToDo("#root", config);
const restProvider = new todo.RestDataProvider(url);
const idResolver = restProvider.getIDResolver();
const TaskID = 1;
const ProjID = 2;
const resolveTask = (data: any) => {
for (const key in obj) {
if (typeof obj[key] == "object") {
resolveTask(obj[key]);
continue;
}
if (key == "id" || key == "targetId" || key == "parent") {
obj[key] = idResolver(obj[key], TaskID) || null;
}
if (key == "project") {
obj[key] = idResolver(obj[key], ProjID) || null;
}
}
return obj;
};
const tasksHandler = (obj: any) => {
resolveTask(obj); // synchronize client ids with the server ids
switch (obj.type) {
case "add-task":
todoInstance.api.exec("add-task", {
id: obj.data.id,
project: obj.data.project,
parent: obj.data.parent,
targetId: obj.data.targetId,
reverse: obj.data.reverse,
task: { ...obj.data.task },
skipProvider: true // prevent the client from sending the request to the server
})
break;
// other operations
}
};
const handlers = {
tasks: tasksHandler
};
const remoteEvents = new todo.RemoteEvents(remoteEventsURL, token);
remoteEvents.on(handlers);
The RestDataProvider.getIDResolver() method returns a function that synchronizes client ids with server ids. When the client creates a new object (task or project), the received object has a temporary id and a corresponding server id in RestDataProvider. The idResolver() function synchronizes the client id with the server id. The function has the following format: idResolver(id: TID, type: number).
The type argument is the type of model and takes the following values:
TaskID—1ProjID—2
To prevent the request from being sent to the server, pass the skipProvider: true flag to the todoInstance.api.exec() method.