Skip to main content
Back to examples
Popular features/Deadlines and task locking
Loading live demo…from dhtmlxcode.com
Popular features

Deadlines and task locking

Snap a locked task back when a drag would cross its deadline, and mark the overrun on tasks that are free to move.

gantt.plugins({ marker: true });

gantt.message({
	text: "<b>Locked</b> tasks (slate, \uD83D\uDD12) can't be dragged past their deadline; they snap back. <b>Flexible</b> tasks (blue) move freely, but any part left past the deadline turns <b>red</b>.",
	expire: -1
});

gantt.config.date_format      = "%Y-%m-%d";
gantt.config.row_height       = 44;
gantt.config.bar_height       = 28;
gantt.config.scale_height     = 54;
gantt.config.min_column_width = 20;
gantt.config.autoscroll = false;

gantt.config.scales = [
	{ unit: "month", step: 1, format: "%F %Y" },
	{ unit: "week",  step: 1, format: "%d %M" },
	{ unit: "day",  step: 1, format: "%d" },
];

gantt.templates.task_class = function (start, end, task) {
	return task.locked ? "locked_task" : "";
};

/* White padlock drawn on a locked task's bar, reinforcing the grid-column lock. */
let BAR_LOCK = '<svg class="bar_lock" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2"><rect x="5" y="11" width="14" height="10" rx="2"></rect><path d="M8 11V7a4 4 0 0 1 8 0v4"></path></svg>';

gantt.templates.task_text = function (start, end, task) {
	let lock = (task.locked && task.type !== gantt.config.types.milestone) ? BAR_LOCK : "";
	return `${lock}${task.text || ""}`;
};

function lockCell(task) {
	return task.locked ? '<svg class="dhx_ic dhx_ic--sm" viewBox="0 0 24 24" style="stroke:#475569"><rect x="5" y="11" width="14" height="10" rx="2"></rect><path d="M8 11V7a4 4 0 0 1 8 0v4"></path></svg>' : "";
}

gantt.config.columns = [
	{ name: "text",       label: "Task",  tree: true, width: 220, resize: true },
	{ name: "start_date", label: "Start", align: "center", width: 100, resize: true },
	{ name: "duration",   label: "Days",  align: "center", width: 55, resize: true },
	{ name: "lock",       label: "",      align: "center", width: 34, resize: true, template: lockCell },
	{ name: "add", width: 40, resize: true }
];

/* ---------------------------------------------------------------------------
 *  DEADLINES: two fixed dates, one per branch. `taskDeadline` resolves the
 *  right one for a given task, used for the marker lines, the drag guard and
 *  the overlay below.
 * ------------------------------------------------------------------------ */
let CONTRACT_DEADLINE = new Date(2026, 5, 20);   // 20 Jun 2026 (store opening day)
let VENDOR_DEADLINE   = new Date(2026, 5, 1);    // 1 Jun 2026 (procurement must be ready by this date)

function taskDeadline(task) {
	return task.deadlineGroup === "vendor" ? VENDOR_DEADLINE : CONTRACT_DEADLINE;
}

gantt.attachEvent("onGanttReady", function () {
	gantt.addMarker({
		id: "contract_deadline",
		start_date: CONTRACT_DEADLINE,
		css: "deadline_marker",
		text: "Contract deadline"
	});
	gantt.addMarker({
		id: "vendor_deadline",
		start_date: VENDOR_DEADLINE,
		css: "vendor_marker",
		text: "Vendor deadline"
	});

	gantt.addTaskLayer(function (task) {
		let deadline = taskDeadline(task);
		if (+deadline >= +task.end_date) return false;

		let start_date = (+task.start_date > +deadline) ? task.start_date : deadline;
		let over = gantt.getTaskPosition(task, start_date, task.end_date);   // overdue segment
		let full = gantt.getTaskPosition(task, task.start_date, task.end_date); // whole bar

		// getTaskPosition().top is the row's top, but the bar is vertically
		// centred within the row (bar_height < row_height). Offset by that gap
		// so the overlay sits exactly on the bar instead of floating above it.
		let pad = Math.round((gantt.config.row_height - over.height) / 2);

		let el = document.createElement("div");
		el.className   = "deadline_overlap";
		el.style.left   = over.left + "px";
		el.style.top    = (over.top + pad) + "px";
		el.style.width  = over.width + "px";
		el.style.height = over.height + "px";

		// Re-draw the task label in white on top of the solid fill, aligned to the
		// whole bar and clipped to the overdue segment, so it stays readable.
		if (task.text) {
			let label = document.createElement("div");
			label.className        = "deadline_overlap_label";
			label.style.left       = (full.left - over.left) + "px";
			label.style.width      = full.width + "px";
			label.style.lineHeight = over.height + "px";
			label.textContent      = task.text;
			el.appendChild(label);
		}
		return el;
	});
});

/* ---------------------------------------------------------------------------
 *  DRAG GUARD: a locked task can never cross its own deadline; a drag that
 *  would push it past snaps back to the deadline. Flexible tasks are untouched.
 *
 *  Locked milestones (Store opens, Grand opening event) sit exactly ON their
 *  own deadline day, so there's no "before/after" range to snap within; they
 *  are frozen outright instead of running them through the snap-back math.
 * ------------------------------------------------------------------------ */
gantt.attachEvent("onBeforeTaskDrag", function (id) {
	let task = gantt.getTask(id);
	if (task.locked && task.type === gantt.config.types.milestone) return false;
	return true;
});

gantt.attachEvent("onTaskDrag", function (id, mode, task, original) {
	if (!task.locked) return;
	let deadline = taskDeadline(task);

	if (+original.start_date < +deadline && +deadline <= +task.end_date) {
		task.end_date   = new Date(deadline);
		if (mode == "move"){
			task.start_date = gantt.calculateEndDate(task.end_date, -task.duration);
		}
		showPopupError(task.text);
	} else if (+deadline < +original.end_date && +task.start_date <= +deadline) {
		task.start_date = new Date(deadline);
		task.end_date   = gantt.calculateEndDate(task.start_date, task.duration);
		showPopupError(task.text);
	}
});

gantt.attachEvent("onLightboxSave", function(id, task, is_new){
	if (task.locked){
		let deadline = taskDeadline(task);
		if (deadline < task.end_date){
			showPopupError(task.text);
			return false;
		}
	}
	return true;
})

function showPopupError(taskName){
	gantt.message({ id: "deadline_lock_message", type: "error", text: `${taskName} is locked; it can't move past the deadline.` });
}

/* ---------------------------------------------------------------------------
 *  FOOTER STATUS
 * ------------------------------------------------------------------------ */
function updateFooterStatus() {
	let count = 0;
	gantt.eachTask(function (task) {
		if (task.type !== gantt.config.types.project && +taskDeadline(task) < +task.end_date) count++;
	});
	DHX.ui.setText("footer_status", count
		? `${count} task${count === 1 ? "" : "s"} past its deadline`
		: "All tasks fit within their deadlines");
}
gantt.attachEvent("onAfterTaskDrag", function () { updateFooterStatus(); });

/* ---------------------------------------------------------------------------
 *  DATA
 * ------------------------------------------------------------------------ */
let data = {
	tasks: [
		{ id: 1, text: "Retail Fit-Out: Store #12", type: "project", open: true },

		{ id: 10, text: "Design", type: "project", parent: 1, open: true },
		{ id: 11, text: "Concept design",     start_date: "2026-05-12", duration: 6,  parent: 10, progress: 1,   locked: true },
		{ id: 12, text: "Design development", start_date: "2026-06-09", duration: 10, parent: 10, progress: 0.7 },
		{ id: 13, text: "Lighting plan",       start_date: "2026-06-16", duration: 14,  parent: 10, progress: 0.4 },
		{ id: 14, text: "Landlord sign-off",   start_date: "2026-06-15", duration: 4,  parent: 10, progress: 0,   locked: true },

		{ id: 20, text: "Procurement", type: "project", parent: 1, open: true, deadlineGroup: "vendor" },
		{ id: 21, text: "Fixtures & fittings order", start_date: "2026-05-19", duration: 12, parent: 20, progress: 0.6, locked: true, deadlineGroup: "vendor" },
		{ id: 22, text: "Signage production",        start_date: "2026-05-29", duration: 30, parent: 20, progress: 0.3, deadlineGroup: "vendor" },
		{ id: 23, text: "POS hardware order",        start_date: "2026-05-12", duration: 9,  parent: 20, progress: 0.5, deadlineGroup: "vendor" },
		{ id: 24, text: "Shelving & racking delivery", start_date: "2026-05-25", duration: 14, parent: 20, progress: 0.2, deadlineGroup: "vendor" },

		{ id: 30, text: "Construction", type: "project", parent: 1, open: true },
		{ id: 31, text: "Demolition & prep",          start_date: "2026-05-20", duration: 6,  parent: 30, progress: 1 },
		{ id: 32, text: "MEP rough-in",                start_date: "2026-05-27", duration: 10, parent: 30, progress: 0.5, locked: true },
		{ id: 33, text: "Flooring & fixtures install", start_date: "2026-06-05", duration: 12, parent: 30, progress: 0.2 },
		{ id: 34, text: "Final inspection",            start_date: "2026-06-16", duration: 3,  parent: 30, progress: 0,   locked: true },
		{ id: 35, text: "Store opens", start_date: "2026-06-20", type: "milestone", parent: 30, locked: true },

		{ id: 40, text: "Merchandising & Launch Prep", type: "project", parent: 1, open: true },
		{ id: 41, text: "Planogram setup",     start_date: "2026-06-08", duration: 6,  parent: 40, progress: 0.1 },
		{ id: 42, text: "Stock intake & tagging", start_date: "2026-06-14", duration: 8,  parent: 40, progress: 0 },
		{ id: 43, text: "Staff training",      start_date: "2026-06-17", duration: 5,  parent: 40, progress: 0 },
		{ id: 44, text: "Grand opening event",  start_date: "2026-06-20", type: "milestone", parent: 40, locked: true }
	],
	links: [
		{ id: 1, source: 11, target: 12, type: "0" },
		{ id: 2, source: 12, target: 21, type: "0" },
		{ id: 3, source: 21, target: 22, type: "0" },
		{ id: 4, source: 12, target: 31, type: "0" },
		{ id: 5, source: 31, target: 32, type: "0" },
		{ id: 6, source: 32, target: 33, type: "0" },
		{ id: 7, source: 33, target: 34, type: "0" },
		{ id: 8, source: 34, target: 35, type: "0" },
		{ id: 9, source: 12, target: 13, type: "0" },
		{ id: 10, source: 13, target: 14, type: "0" },
		{ id: 11, source: 21, target: 23, type: "0" },
		{ id: 12, source: 23, target: 24, type: "0" },
		{ id: 13, source: 33, target: 41, type: "0" },
		{ id: 14, source: 41, target: 42, type: "0" },
		{ id: 15, source: 42, target: 43, type: "0" },
		{ id: 16, source: 43, target: 44, type: "0" },
		{ id: 17, source: 35, target: 44, type: "0" }
	]
};

/* ---------------------------------------------------------------------------
 *  INIT
 * ------------------------------------------------------------------------ */
gantt.init("gantt_here");
gantt.parse(data);
gantt.showDate(new Date(2026, 4, 1));
updateFooterStatus();
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Deadline lock: 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 { width: 100%; flex: 1 1 auto; min-height: 0; }

		.gantt_task_line { border: none; border-radius: 5px; }

		.gantt_task_line.locked_task {
			background: #475569 !important;
			box-sizing: border-box;
		}
		.bar_lock { vertical-align: -2px; margin-right: 6px; }

		.gantt_marker.deadline_marker,
		.gantt_marker.vendor_marker { z-index: 3; }

		.gantt_marker.deadline_marker { background: #e5484d; }
		.gantt_marker.vendor_marker   { background: #fb7185; }

		.gantt_marker.deadline_marker .gantt_marker_content,
		.gantt_marker.vendor_marker   .gantt_marker_content {
			color: #fff;
			font-weight: 600;
			box-shadow: 0 1px 4px rgba(0, 0, 0, .35);
		}
		.gantt_marker.deadline_marker .gantt_marker_content { background: #e5484d; }
		.gantt_marker.vendor_marker   .gantt_marker_content { background: #fb7185; }

		.deadline_overlap {
			position: absolute;
			background: #dc2626;
			border-radius: 4px;
			overflow: hidden;
			pointer-events: none;
			z-index: 2;
		}
		.deadline_overlap_label {
			position: absolute;
			top: 0;
			color: #fff;
			font-weight: 600;
			text-align: center;
			white-space: nowrap;
			overflow: hidden;
		}

		.gantt-info.gantt-error{
			position: fixed;
		}

		.footer_legend { display: inline-flex; gap: 16px; }
		.footer_legend .lg { display: inline-flex; align-items: center; gap: 6px; }
		.footer_legend .sw { width: 16px; height: 10px; border-radius: 2px; display: inline-block; }
		.footer_legend .sw.locked   { background: #475569; }
		.footer_legend .sw.flexible { background: #2563eb; }
		.footer_legend .sw.overlap  { background: #dc2626; }
		.footer_legend .sw.vendor_dl   { background: #fb7185; }
		.footer_legend .sw.contract_dl { background: #e5484d; }
	</style>
</head>
<body>

<div id="gantt_here"></div>

<div class="dhx_footer">
	<div class="footer_legend">
		<span class="lg"><i class="sw locked"></i> Locked (signed off)</span>
		<span class="lg"><i class="sw flexible"></i> Flexible (draft)</span>
		<span class="lg"><i class="sw overlap"></i> Past deadline</span>
		<span class="lg"><i class="sw vendor_dl"></i> Vendor deadline</span>
		<span class="lg"><i class="sw contract_dl"></i> Contract deadline</span>
	</div>
	<div class="dhx_footer__right"><span id="footer_status"></span></div>
</div>


</body>
</html>