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>