DHTMLX Kanban. Backend with comments and votes example
Comments and votes are collaborative features that require user identity. This demo extends the basic backend integration with authentication, allowing each user to post comments and cast votes under their own identity.
Live example
const { Kanban, Toolbar, RestDataProvider, defaultEditorShape, defaultCardShape } = kanban;
// Check Go backend repository here - https://github.com/web-widgets/kanban-go
const url = "https://docs.dhtmlx.com/kanban-backend";
const restProvider = new RestDataProvider(url);
loginWithUser(initKanban, restProvider, url, "auth-container");
function initKanban(restProvider, url, token, userId) {
Promise.all([
restProvider.getCards(),
restProvider.getColumns(),
restProvider.getRows(),
]).then(([cards, columns, rows]) => {
restProvider.setHeaders({
"Remote-Token": token,
});
const cardShape = {
...defaultCardShape,
label: true,
description: true,
progress: true,
start_date: true,
end_date: true,
priority: true,
color: true,
cover: true,
attached: true,
users: {
show: true,
values: users
},
votes: {
show: true,
clickable: true,
},
comments: true,
};
const editorShape = [
...defaultEditorShape,
{
key: "attached",
type: "files",
label: "Files",
uploadURL: url + "/uploads",
},
{
type: "comments",
key: "comments",
label: "Comments",
config: {
placement: "editor",
},
},
];
const board = new Kanban("#root", {
cards,
columns,
rows,
rowKey: "row",
cardShape,
editorShape,
currentUser: parseInt(userId),
});
board.api.setNext(restProvider);
new Toolbar("#toolbar", { api: board.api });
});
}<!-- component containers -->
<div id="auth-container" style="display: flex; justify-content: center; align-items: center; height: 100%">
<div id="auth-content" class="auth-content"></div>
</div>
<div id="toolbar"></div>
<div id="root" style="height: calc(100% - 56px);"></div>
<!-- dataset -->
<style>
.auth-select {
padding: 4px;
border: 1px solid #d2cfcf;
font-size: 14px;
height: 32px;
width: 200px;
margin: 5px 0 10px;
}
.auth-btn {
display: inline-block;
vertical-align: middle;
max-width: 100%;
min-width: 200px;
font-weight: 500;
color: #fff;
cursor: pointer;
font-size: 16px;
padding: 6px 16px;
border: none;
background: #027ABC;
}
.auth-content {
width: 150px;
height: 200px;
}
</style>
<script>
const users = [
{
id: 1,
label: "Steve Smith",
avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-1.jpg",
},
{
id: 2,
label: "Aaron Long",
avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-2.jpg",
},
{
id: 3,
label: "Angela Allen",
avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-3.jpg",
},
{
id: 4,
label: "Angela Long",
avatar: "https://snippet.dhtmlx.com/codebase/data/kanban/01/img/user-4.jpg",
},
{
id: 5,
label: "John Doe",
},
];
const login = (url, id) => {
return fetch(`${url}/login?id=${id}`)
.then(raw => raw.text())
.then(token => {
sessionStorage.setItem("login-token", token);
sessionStorage.setItem("user-id", id);
return token;
});
}
const loginWithUser = (initKanban, restProvider, url, authContainer) => {
// 1) try to get credentials from previous session
const token = sessionStorage.getItem("login-token");
const userId = parseInt(sessionStorage.getItem("user-id"));
if (token && userId) {
if (authContainer)
document.getElementById(authContainer).style.display = "none";
initKanban(restProvider, url, token, userId);
return;
}
// 2) initialize an auth panel and login with exact user
const onAuth = userId => {
login(url, userId).then(token => {
document.getElementById(authContainer).style.display = "none";
initKanban(restProvider, url, token, userId);
});
};
sessionStorage.clear();
restProvider
.getUsers()
.then(data => {
_initAuthContainer(authContainer, data, onAuth);
})
.catch(err => {
alert(err);
throw new Error(err);
});
}
const _initAuthContainer = (containerId, users, onAuth) => {
const container = document.getElementById("auth-content");
if (!container) {
throw new Error("auth container not defined");
}
container.innerHTML = `
<label for="users">Choose user:</label>
<select name="users" id="users-select" class="auth-select"></select>
<button id="login-button" class="auth-btn">Login</button>
`;
const selectNode = document.getElementById("users-select");
for (let i = 0; i < users.length; i++) {
const option = document.createElement("option");
option.text = users[i].label;
option.value = users[i].id;
selectNode.appendChild(option);
}
document.getElementById("login-button").onclick = () => {
const userId = selectNode.value;
if (!userId) {
alert("choose user");
return;
}
onAuth(userId);
};
}
</script>The code adds a login flow before initializing the board. After authentication, the RestDataProvider gets a "Remote-Token" header via setHeaders, and currentUser is set to the logged-in user's ID. The card shape enables votes: { show: true, clickable: true } and comments: true, while the editor includes a type: "comments" field with placement: "editor". The login credentials are stored in sessionStorage to persist across page refreshes within the same session.
Solution overview
- Authenticate the user and obtain a token
- Set the token on the provider:
restProvider.setHeaders({ "Remote-Token": token }) - Initialize the board with
currentUser: parseInt(userId) - Enable
votesandcommentsincardShapeandeditorShape - Connect to the backend with
board.api.setNext(restProvider)
Key points
currentUserties actions to identity: Without it, the board can't track who commented or voted - it's required for both features- Token-based auth: The
Remote-Tokenheader carries a JSON Web Token that the backend decodes on every request to extract the user ID - this is what ties votes and comments to the correct user - Comment placement in editor:
config: { placement: "editor" }renders comments inline in the editor panel
API reference
- RestDataProvider: REST client with
setHeadersfor authentication - currentUser: Identifies the active user for comments and votes
- cardShape: Enables comments and votes display