Skip to main content
Back to examples
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>