Popular features/Single resource per task
Loading live demo…from dhtmlxcode.com
Popular features
Single resource per task
Give each task one owner from a select editor, color bars by that owner, and filter the chart by clicking a person.
gantt.config.date_format = "%Y-%m-%d";
gantt.config.row_height = 38;
gantt.config.bar_height = 26;
gantt.config.scale_height = 52;
gantt.config.scales = [
{ unit: "month", step: 1, format: "%F %Y" },
{ unit: "week", step: 1, format: "Week %W" }
];
/* team */
const staffMembers = [
{ id: "1", name: "Ava Reed", color: "#7c3aed" },
{ id: "2", name: "Leo Marsh", color: "#2563eb" },
{ id: "3", name: "Mia Fontana", color: "#059669" },
{ id: "4", name: "Noah Kim", color: "#d97706" }
];
const staffOptions = [{ key: "", label: "Unassigned" }].concat(
staffMembers.map(function (staffMember) {
return {
key: staffMember.id,
label: staffMember.name
};
})
);
gantt.serverList("staff", staffOptions);
function getStaffMember(ownerId) {
for (let staffIndex = 0; staffIndex < staffMembers.length; staffIndex++) {
if (staffMembers[staffIndex].id == ownerId) {
return staffMembers[staffIndex];
}
}
return null;
}
function getOwnerClassName(ownerId) {
if (!ownerId) {
return "";
}
const safeOwnerId = String(ownerId).replace(/[^a-zA-Z0-9_-]/g,"-");
return "task-owner-" + safeOwnerId;
}
function addStaffColorStyles() {
const styleElement = document.createElement("style");
styleElement.textContent = staffMembers.map(function (staffMember) {
const ownerClassName = getOwnerClassName(staffMember.id);
return [
".gantt_task_line." + ownerClassName + " {",
" background-color: " + staffMember.color + ";",
" border-color: " + staffMember.color + ";",
"}",
".gantt_task_line." + ownerClassName + " .gantt_task_progress {",
" background-color: " + staffMember.color + ";",
"}",
".gantt_task_line." + ownerClassName + " .gantt_task_content {",
" color: #ffffff;",
"}"
].join("\n");
}).join("\n");
document.head.appendChild(styleElement);
}
addStaffColorStyles();
/* Lightbox assignee picker. */
gantt.locale.labels.section_owner = "Assignee";
gantt.config.lightbox.sections = [
{ name: "description", height: 60, map_to: "text", type: "textarea", focus: true },
{ name: "owner", height: 22, map_to: "owner_id", type: "select", options: staffOptions },
{ name: "time", type: "duration", map_to: "auto" }
];
/* Apply the owner's generated CSS class without changing task properties. */
gantt.templates.task_class = function (startDate, endDate, task) {
if (task.type === gantt.config.types.project) {
return "";
}
return getOwnerClassName(task.owner_id);
};
/* grid & bar templates */
function assigneeCell(task) {
if (task.type === gantt.config.types.project) {
return "";
}
const staffMember = getStaffMember(task.owner_id);
if (!staffMember) {
return '<span class="unassigned">Unassigned</span>';
}
return '<span class="assignee">' +
'<span class="avatar" style="background:' +
staffMember.color +
'">' +
DHX.ui.initials(staffMember.name) +
"</span>" +
staffMember.name +
"</span>";
}
gantt.templates.rightside_text = function (startDate, endDate, task) {
const staffMember = getStaffMember(task.owner_id);
return staffMember ? '<span class="rside_owner">' + staffMember.name + "</span>" : "";
};
gantt.config.columns = [
{ name: "text", label: "Task", tree: true, width: 230, resize: true },
{ name: "owner", label: "Assignee", align: "left", width: 160, template: assigneeCell, resize: true,
editor: { type: "select", map_to: "owner_id", options: staffOptions } },
{ name: "duration", label: "Days", align: "center", width: 58, resize: true },
{ name: "add", width: 40, resize: true }
];
/* team legend / filter */
let activeOwners = { "1": true, "3": true };
function selectedOwnersCount() {
return Object.keys(activeOwners).filter(function (ownerId) {
return activeOwners[ownerId];
}).length;
}
function resetOwnerFilter() {
activeOwners = {};
}
function updateTeamSelection() {
const memberElements = document.querySelectorAll("#team .member");
for (let memberIndex = 0; memberIndex < memberElements.length; memberIndex++) {
const ownerId = memberElements[memberIndex].getAttribute("data-owner");
memberElements[memberIndex].classList.toggle("active", !!activeOwners[ownerId]);
}
}
function renderTeam() {
document.getElementById("team").innerHTML =
staffMembers.map(function (staffMember) {
return '<span class="member" data-owner="' +
staffMember.id +
'" onclick="toggleOwner(\'' +
staffMember.id +
'\')">' +
'<span class="avatar" style="background:' +
staffMember.color +
'">' +
DHX.ui.initials(staffMember.name) +
"</span>" +
staffMember.name.split(" ")[0] +
"</span>";
}).join("");
updateTeamSelection();
}
function toggleOwner(ownerId) {
activeOwners[ownerId] = !activeOwners[ownerId];
if (selectedOwnersCount() === staffMembers.length) {
resetOwnerFilter();
}
updateTeamSelection();
gantt.render();
}
gantt.attachEvent("onBeforeTaskDisplay", function (taskId, task) {
if (!selectedOwnersCount()) {
return true;
}
if (task.type === gantt.config.types.project) {
return true;
}
return !!activeOwners[task.owner_id];
});
// repaint tasks to apply the filter
gantt.ext.inlineEditors.attachEvent("onSave", function (state) {
if (state.columnName === "owner") {
gantt.refreshData();
}
});
/* data */
let data = {
tasks: [
{ id: 1, text: "Checkout revamp", type: "project", open: true, progress: 0.4 },
{ id: 11, text: "Cart service", parent: 1, start_date: "2026-09-01", duration: 6, progress: 1, owner_id: "2" },
{ id: 12, text: "Address form", parent: 1, start_date: "2026-09-05", duration: 5, progress: 0.6, owner_id: "1" },
{ id: 21, text: "Guest checkout flow", parent: 1, start_date: "2026-09-03", duration: 6, progress: 0.8, owner_id: "1" },
{ id: 22, text: "Coupon & promo codes", parent: 1, start_date: "2026-09-08", duration: 5, progress: 0.5, owner_id: "2" },
{ id: 13, text: "Payment sheet", parent: 1, start_date: "2026-09-10", duration: 7, progress: 0.4, owner_id: "3" },
{ id: 23, text: "Saved payment methods", parent: 1, start_date: "2026-09-12", duration: 6, progress: 0.3, owner_id: "3" },
{ id: 14, text: "Order confirmation", parent: 1, start_date: "2026-09-17", duration: 4, progress: 0.1, owner_id: "4" },
{ id: 15, text: "Analytics events", parent: 1, start_date: "2026-09-17", duration: 3, progress: 0, owner_id: "2" },
{ id: 24, text: "Shipping options", parent: 1, start_date: "2026-09-19", duration: 4, progress: 0, owner_id: "4" },
{ id: 16, text: "Tax calculation", parent: 1, start_date: "2026-09-22", duration: 5, progress: 0, owner_id: "1" },
{ id: 17, text: "Fraud checks", parent: 1, start_date: "2026-09-24", duration: 4, progress: 0, owner_id: "3" },
{ id: 25, text: "Invoice generation", parent: 1, start_date: "2026-09-26", duration: 4, progress: 0, owner_id: "1" },
{ id: 18, text: "A/B rollout", parent: 1, start_date: "2026-09-29", duration: 5, progress: 0, owner_id: "4" },
{ id: 19, text: "Support handoff", parent: 1, start_date: "2026-10-02", duration: 3, progress: 0, owner_id: "2" },
{ id: 20, text: "Ship", parent: 1, type: "milestone", start_date: "2026-10-08" },
{ id: 26, text: "Post-launch hardening", type: "project", open: true, progress: 0 },
{ id: 27, text: "Load testing", parent: 26, start_date: "2026-10-09", duration: 5, progress: 0, owner_id: "2" },
{ id: 28, text: "Error monitoring setup", parent: 26, start_date: "2026-10-09", duration: 4, progress: 0, owner_id: "4" },
{ id: 29, text: "Refund workflow", parent: 26, start_date: "2026-10-14", duration: 5, progress: 0, owner_id: "3" },
{ id: 30, text: "Localization pass", parent: 26, start_date: "2026-10-16", duration: 6, progress: 0, owner_id: "1" },
{ id: 31, text: "Accessibility audit", parent: 26, start_date: "2026-10-20", duration: 5, progress: 0, owner_id: "2" },
{ id: 32, text: "Retrospective", parent: 26, type: "milestone", start_date: "2026-10-27" }
],
links: [
{ id: 1, source: 11, target: 13, type: "0" },
{ id: 2, source: 12, target: 13, type: "0" },
{ id: 3, source: 13, target: 14, type: "0" },
{ id: 4, source: 14, target: 18, type: "0" },
{ id: 5, source: 15, target: 19, type: "0" },
{ id: 6, source: 16, target: 18, type: "0" },
{ id: 7, source: 17, target: 18, type: "0" },
{ id: 8, source: 18, target: 20, type: "0" },
{ id: 9, source: 19, target: 20, type: "0" },
{ id: 10, source: 21, target: 13, type: "0" },
{ id: 11, source: 23, target: 13, type: "0" },
{ id: 12, source: 24, target: 18, type: "0" },
{ id: 13, source: 27, target: 32, type: "0" },
{ id: 14, source: 28, target: 32, type: "0" },
{ id: 15, source: 29, target: 32, type: "0" },
{ id: 16, source: 30, target: 32, type: "0" },
{ id: 17, source: 31, target: 32, type: "0" }
]
};
renderTeam();
gantt.init("gantt_here");
gantt.parse(data);<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Single resource per task: dhtmlxGantt</title>
<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
<script src="../common/demo-controls/dhx_controls.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<style>
body { display: flex; flex-direction: column; }
#gantt_here { flex: 1 1 auto; min-height: 0; width: 100%; }
.dhx_header { justify-content: center; gap: 10px; }
.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
.gantt_task_line.gantt_project { border-radius: 5px; }
.assignee { display: inline-flex; align-items: center; gap: 7px; }
.avatar {
width: 22px; height: 22px; border-radius: 50%;
color: #fff; font: 700 10px var(--dhx-font);
display: inline-flex; align-items: center; justify-content: center; flex: none;
}
.rside_owner { font: 600 11px var(--dhx-font); color: #fff; opacity: .95; }
.unassigned { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
.team { display: inline-flex; gap: 6px; }
.member {
display: inline-flex; align-items: center; gap: 7px;
padding: 4px 11px 4px 4px; border-radius: 999px;
border: 1px solid var(--dhx-line); background: var(--dhx-surface); cursor: pointer;
font: 600 12px var(--dhx-font); color: var(--dhx-ink-2);
transition: border-color .12s, box-shadow .12s;
}
.member:hover { border-color: #bcd3fb; }
.member.active { border-color: var(--dhx-accent); box-shadow: 0 0 0 3px rgba(37,99,235,.15); color: var(--dhx-ink); }
:root[data-gantt-theme="dark"] .member:hover { border-color: #2f3947; }
.gantt_cell[data-column-name="owner"] { cursor: pointer; }
.hint { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
.hint b { color: var(--dhx-ink-2); font-weight: 600; }
</style>
</head>
<body>
<div class="dhx_header">
<span class="hint">Filter</span>
<div class="team" id="team"></div>
</div>
<div id="gantt_here"></div>
<div class="dhx_footer">
<span class="hint">The <b>Assignee</b> grid column uses a select editor mapped to <b>owner_id</b>; templates repaint the task when ownership changes.</span>
</div>
</body>
</html>