From ec26f4cd73ca8183ca671bd17c37d66115de4cba Mon Sep 17 00:00:00 2001 From: Maksim Lakatkou Date: Mon, 9 Mar 2026 11:18:58 +0100 Subject: [PATCH] [dev] update Dragging Tasks within the Timeline article Improve structure, refactor code examples --- docs/guides/dnd.md | 173 +++++++++++++++++++------------------- static/img/custom_dnd.png | Bin 4021 -> 6165 bytes 2 files changed, 86 insertions(+), 87 deletions(-) diff --git a/docs/guides/dnd.md b/docs/guides/dnd.md index c06bec7e..9b15666a 100644 --- a/docs/guides/dnd.md +++ b/docs/guides/dnd.md @@ -5,20 +5,17 @@ sidebar_label: "Dragging Tasks within the Timeline" # Dragging Tasks within the Timeline -Dragging allows users to quickly change the start (end) dates of the tasks, their duration. - - +Dragging allows users to quickly change the start (end) dates of the tasks, their duration. By default, the drag-and-drop is enabled and the user can drag a task along its row in the timeline. To customize the drag-and-drop behavior, use the following events: - [onBeforeTaskDrag](api/event/onbeforetaskdrag.md) - to deny dragging of specific tasks -- [onTaskDrag](api/event/ontaskdrag.md) - to limit the area for dragging or to provide some other logic when the user drags a task +- [onTaskDrag](api/event/ontaskdrag.md) - to limit the area for dragging or to provide some other logic when the user drags a task - [onAfterTaskDrag](api/event/onaftertaskdrag.md) - to postprocess tasks after they have been dragged to a new place Let's consider typical cases when the default drag behavior needs customization: - 1. [Denying dragging specific tasks](#denying-dragging-of-specific-tasks). 2. [Denying dragging tasks out of specific dates](#denying-dragging-tasks-out-of-specific-dates). 3. [Dragging children together with the parent](#dragging-children-together-with-the-parent). @@ -26,30 +23,28 @@ Let's consider typical cases when the default drag behavior needs customization: 5. [Setting minimal task duration](#setting-minimal-task-duration). 6. [Autoscroll during tasks' dragging](#autoscrollduringtasksdragging). - ## Denying dragging of specific tasks To deny dragging of specific tasks, use the [onBeforeTaskDrag](api/event/onbeforetaskdrag.md) event: ~~~js -gantt.attachEvent("onBeforeTaskDrag", function(id, mode, e){ - if(gantt.getGlobalTaskIndex(id)%2==0){ - return false; //denies dragging if the global task index is odd +gantt.attachEvent("onBeforeTaskDrag", (taskId, dragMode, event) => { + if (gantt.getGlobalTaskIndex(taskId) % 2 === 0) { + return false; // denies dragging if the global task index is even } - return true; //allows dragging if the global task index is even + return true; // allows dragging if the global task index is odd }); ~~~ - ## Denying dragging tasks out of specific dates -To deny dragging tasks out of specific dates, use the [onTaskDrag](api/event/ontaskdrag.md) event. +To deny dragging tasks out of specific dates, use the [onTaskDrag](api/event/ontaskdrag.md) event.

The onTaskDrag event:

@@ -59,79 +54,87 @@ To deny dragging tasks out of specific dates, use the [onTaskDrag](api/event/ont
  • The user makes a move.
  • dhtmlxGantt recalculates the task's date according to the new position.
  • dhtmlxGantt fires the [onTaskDrag](api/event/ontaskdrag.md) event.
  • -
  • dhtmlxGantt re-renders the task in the Gantt chart.
    As the [](api/event/ontaskdrag.md) event fires after dhtmlxGantt makes recalculation, +
  • dhtmlxGantt re-renders the task in the Gantt chart.
    As the [onTaskDrag](api/event/ontaskdrag.md) event fires after dhtmlxGantt makes recalculation, you can specify any custom values for the dragged task in the event's handler, without being afraid that these values will be overwritten. As a result, the task will be rendered in the desired position.
  • -Let's assume that you want to forbid users to drag tasks out of the **"31 March, 2020 - 11 April, 2020"** interval. +Let's assume that you want to forbid users to drag tasks out of the **"31 March, 2028 - 11 April, 2028"** interval. ![custom_dnd](/img/custom_dnd.png) Then, you can use the code as in: -[Denying dragging tasks out of interval - [31.03.2020, 11.04.2020]](Denying dragging tasks out of interval - [31.03.2020, 11.04.2020]) ~~~js -var leftLimit = new Date(2020, 2 ,31), rightLimit = new Date(2020, 3 ,12); - -gantt.attachEvent("onTaskDrag", function(id, mode, task, original){ - var modes = gantt.config.drag_mode; - if(mode == modes.move || mode == modes.resize){ - - var diff = original.duration*(1000*60*60*24); - - if(+task.end_date > +rightLimit){ +const leftLimit = new Date(2028, 2, 31); +const rightLimit = new Date(2028, 3, 12); +const millisecondsInDay = 24 * 60 * 60 * 1000; + +gantt.attachEvent("onTaskDrag", (taskId, dragMode, task, originalTask) => { + const dragModes = gantt.config.drag_mode; + + if (dragMode === dragModes.move || dragMode === dragModes.resize) { + const taskDuration = originalTask.duration * millisecondsInDay; + + if (+task.end_date > +rightLimit) { task.end_date = new Date(rightLimit); - if(mode == modes.move) - task.start_date = new Date(task.end_date - diff); + if (dragMode === dragModes.move) { + task.start_date = new Date(task.end_date - taskDuration); } - if(+task.start_date < +leftLimit){ + } + + if (+task.start_date < +leftLimit) { task.start_date = new Date(leftLimit); - if(mode == modes.move) - task.end_date = new Date(+task.start_date + diff); + if (dragMode === dragModes.move) { + task.end_date = new Date(+task.start_date + taskDuration); + } } } }); ~~~ - -[Drag parent task with its children](https://docs.dhtmlx.com/gantt/samples/08_api/05_limit_drag_dates.html) - - ## Dragging children together with the parent To allow dragging children when the user is dragging their parent's task, use the [onTaskDrag](api/event/ontaskdrag.md) event (see more on the event [above](guides/dnd.md#denying-dragging-tasks-out-of-specific-dates)): ~~~js -gantt.attachEvent("onTaskDrag", function(id, mode, task, original){ - var modes = gantt.config.drag_mode; - if(mode == modes.move){ - var diff = task.start_date - original.start_date; - gantt.eachTask(function(child){ - child.start_date = new Date(+child.start_date + diff); - child.end_date = new Date(+child.end_date + diff); +gantt.attachEvent("onTaskDrag", (taskId, dragMode, task, originalTask) => { + const dragModes = gantt.config.drag_mode; + + if (dragMode === dragModes.move) { + const dateShift = task.start_date - originalTask.start_date; + gantt.eachTask((child) => { + child.start_date = new Date(+child.start_date + dateShift); + child.end_date = new Date(+child.end_date + dateShift); gantt.refreshTask(child.id, true); - },id ); + }, taskId); } }); -//rounds positions of the child items to scale -gantt.attachEvent("onAfterTaskDrag", function(id, mode, e){ - var modes = gantt.config.drag_mode; - if(mode == modes.move ){ - var state = gantt.getState(); - gantt.eachTask(function(child){ + +// rounds positions of the child items to scale +gantt.attachEvent("onAfterTaskDrag", (taskId, dragMode, event) => { + const dragModes = gantt.config.drag_mode; + + if (dragMode === dragModes.move) { + const ganttState = gantt.getState(); + gantt.eachTask((child) => { child.start_date = gantt.roundDate({ - date:child.start_date, - unit:state.scale_unit, - step:state.scale_step - }); - child.end_date = gantt.calculateEndDate(child.start_date, - child.duration, gantt.config.duration_unit); - gantt.updateTask(child.id); - },id ); + date: child.start_date, + unit: ganttState.scale_unit, + step: ganttState.scale_step + }); + child.end_date = gantt.calculateEndDate( + child.start_date, + child.duration, + gantt.config.duration_unit + ); + gantt.updateTask(child.id); + }, taskId); } }); ~~~ +**Related sample**: [Drag parent task with its children](https://docs.dhtmlx.com/gantt/samples/08_api/05_limit_drag_dates.html) + ## Dragging projects with subtasks {#draggingprojectswithsubtasks} :::info @@ -145,16 +148,13 @@ You can enable drag and drop of projects using the [drag_project](api/config/dra gantt.config.drag_project = true; ~~~ - -[Draggable projects](https://docs.dhtmlx.com/gantt/samples/08_api/19_draggable_projects.html) - +**Related sample**: [Draggable projects](https://docs.dhtmlx.com/gantt/samples/08_api/19_draggable_projects.html) ## Dragging dependent tasks together with independent tasks There are several ways of implementing tasks moving with their dependent tasks. You can read about all of them in a separate article [Dragging Tasks Together with Their Dependent Tasks](guides/dragging-dependent-tasks.md). - ## Setting minimal task duration Minimal task duration can be specified via the [min_duration](api/config/min_duration.md) setting. @@ -164,19 +164,19 @@ The option defines the minimum size of the task that can be set during resizing The value is set in milliseconds: ~~~js // 1 day -gantt.config.min_duration = 24*60*60*1000; +gantt.config.min_duration = 24 * 60 * 60 * 1000; -//OR +// OR // 1 hour -gantt.config.min_duration = 60*60*1000; +gantt.config.min_duration = 60 * 60 * 1000; ~~~ ## Autoscroll during tasks' dragging {#autoscrollduringtasksdragging} If you have a large dataset in the Gantt chart, you often need to drag a task to a new distant position or set links between tasks located at a significant distance. -In this case the **autoscroll** functionality is of great help. It is enabled by default, but you can manage this behavior via +In this case the **autoscroll** functionality is of great help. It is enabled by default, but you can manage this behavior via the [autoscroll](api/config/autoscroll.md) configuration option. ~~~js @@ -189,7 +189,7 @@ Besides, you can adjust the speed of autoscrolling in milliseconds with the help ~~~js gantt.config.autoscroll = true; gantt.config.autoscroll_speed = 50; - + gantt.init("gantt_here"); ~~~ @@ -201,18 +201,19 @@ If you want to prevent certain tasks from being resized, there are two things yo In order to do this, you need to use the **task_class** template to add an extra CSS class to the required items so that you could locate them via the selector: ~~~js -gantt.templates.task_class = function(start, end, task){ - if(task.no_resize) { // no_resize is a custom property used for the demonstration +gantt.templates.task_class = (startDate, endDate, task) => { + if (task.no_resize) { // no_resize is a custom property used for the demonstration return "no_resize"; } return ""; +}; ~~~ Then, you can hide the resize handles using the following CSS: ~~~css -.no_resize .gantt_task_drag{ - display: none !important; +.no_resize .gantt_task_drag { + display: none !important; } ~~~ @@ -220,8 +221,8 @@ Then, you can hide the resize handles using the following CSS: Returning *false* from the handler will prevent resizing: ~~~js -gantt.attachEvent("onBeforeTaskDrag", function(id, mode, e){ - if(mode === "resize" && gantt.getTask(id).no_resize){ +gantt.attachEvent("onBeforeTaskDrag", (taskId, dragMode, event) => { + if (dragMode === "resize" && gantt.getTask(taskId).no_resize) { return false; } return true; @@ -235,9 +236,9 @@ The ["resize"](api/event/onbeforetaskdrag.md) mode of drag and drop means that t If you need to find out which date the user is modifying by the resize, you can use the **gantt.getState().drag_from_start** flag: ~~~js -gantt.attachEvent("onBeforeTaskDrag", function(id, mode, e){ - if(mode === "resize"){ - if(gantt.getState().drag_from_start === true) { +gantt.attachEvent("onBeforeTaskDrag", (taskId, dragMode, event) => { + if (dragMode === "resize") { + if (gantt.getState().drag_from_start === true) { // changing the start date of a task } else { // changing the end date of a task @@ -251,22 +252,22 @@ gantt.attachEvent("onBeforeTaskDrag", function(id, mode, e){ You can locate resize handles using the following selectors: -- .gantt_task_drag[data-bind-property="start_date"] -- .gantt_task_drag[data-bind-property="end_date"] +- `.gantt_task_drag[data-bind-property="start_date"]` +- `.gantt_task_drag[data-bind-property="end_date"]` The following CSS can be used for disabling resizing of start dates of tasks: ~~~css -.gantt_task_drag[data-bind-property="start_date"]{ - display: none !important; +.gantt_task_drag[data-bind-property="start_date"] { + display: none !important; } ~~~ Similarly, preventing resizing of the end dates looks like this: ~~~css -.gantt_task_drag[data-bind-property="end_date"]{ - display: none !important; +.gantt_task_drag[data-bind-property="end_date"] { + display: none !important; } ~~~ @@ -274,16 +275,14 @@ Another way to do this is use the [onBeforeTaskDrag](api/event/onbeforetaskdrag. Returning *false* from the handler will prevent resizing: ~~~js -gantt.attachEvent("onBeforeTaskDrag", function(id, mode, e){ - if(mode === "resize"){ - if(gantt.getState().drag_from_start === true) { - return false; +gantt.attachEvent("onBeforeTaskDrag", (taskId, dragMode, event) => { + if (dragMode === "resize") { + if (gantt.getState().drag_from_start === true) { + return false; } else { - // changing the end date of a task + // changing the end date of a task } } return true; }); ~~~ - - diff --git a/static/img/custom_dnd.png b/static/img/custom_dnd.png index 5c0c3b21702ee095557593d9f674c7c830fe74b6..ed72db9f2f09b7a9e6efb86253ce5218a4060754 100644 GIT binary patch literal 6165 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 MM=Bx8;6?%e51Df5V*mgE literal 4021 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 DPQIey