diff --git a/README.md b/README.md index 5195f34d4..2c5b176fb 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,9 @@ Frappe Gantt offers a wide range of options to customize your chart. | `ignore` | Ignored areas in the rendering | `weekend` _or_ Array of strings or date objects (`weekend` can be present to the array also). | `[]` | | `language` | Language for localization. | ISO 639-1 codes like `en`, `fr`, `es`. | `en` | | `lines` | Determines which grid lines to display. | `none` for no lines, `vertical` for only vertical lines, `horizontal` for only horizontal lines, `both` for complete grid. | `both` | +| `maintain_dependency_position`| If `true` moving or changing the end date of a task will result in the equivalent move in all dependent tasks. Also, if `move_precedences` is `true`, then moving or changing the start date of a task will result in an equivalent move for all predecing tasks | `true`, `false` | `true` | | `move_dependencies` | Whether moving a task automatically moves its dependencies. | `true`, `false` | `true` | +| `move_precedences` | Whether moving a task automatically moves its precedent task. | `true`, `false` | `false` | | `padding` | Padding around task bars (in pixels). | Any positive integer. | `18` | | `popup_on` | Event to trigger the popup display. | `click` _or_ `hover` | `click` | | `readonly_progress` | Disables editing task progress. | `true`, `false` | `false` | diff --git a/src/defaults.js b/src/defaults.js index e63e8ef5c..ceaadfee9 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -123,7 +123,9 @@ const DEFAULT_OPTIONS = { ignore: [], language: 'en', lines: 'both', + maintain_dependency_position : false, move_dependencies: true, + move_precedences: false, padding: 18, popup: (ctx) => { ctx.set_title(ctx.task.name); diff --git a/src/index.js b/src/index.js index 054d3e2ce..d8a29022d 100644 --- a/src/index.js +++ b/src/index.js @@ -200,6 +200,7 @@ export default class Gantt { }) .filter((t) => t); this.setup_dependencies(); + this.setup_precedences(); } setup_dependencies() { @@ -212,6 +213,16 @@ export default class Gantt { } } + setup_precedences() { + this.precedence_map = {}; + for (let t of this.tasks) { + for (let d of t.dependencies) { + this.precedence_map[t.id] = this.precedence_map[t.id] || []; + this.precedence_map[t.id].push(d); + } + } + } + refresh(tasks) { this.setup_tasks(tasks); this.change_view_mode(); @@ -1075,6 +1086,7 @@ export default class Gantt { let x_on_start = 0; let x_on_scroll_start = 0; let y_on_start = 0; + let width_on_start = 0; let is_resizing_left = false; let is_resizing_right = false; let parent_bar_id = null; @@ -1115,14 +1127,16 @@ export default class Gantt { y_on_start = e.offsetY || e.layerY; parent_bar_id = bar_wrapper.getAttribute('data-id'); - let ids; - if (this.options.move_dependencies) { - ids = [ - parent_bar_id, - ...this.get_all_dependent_tasks(parent_bar_id), - ]; - } else { - ids = [parent_bar_id]; + let parent_bar = this.get_bar(parent_bar_id); + width_on_start = parent_bar.$bar.getWidth(); + let ids = [parent_bar_id]; + if ((!this.options.maintain_dependency_position || !is_resizing_left) + && this.options.move_dependencies) { + ids.push(...this.get_all_dependent_tasks(parent_bar_id)); + } + if ((!this.options.maintain_dependency_position || !is_resizing_right) + && this.options.move_precedences) { + ids.push(...this.get_all_precedent_tasks(parent_bar_id)); } bars = ids.map((id) => this.get_bar(id)); @@ -1284,21 +1298,36 @@ export default class Gantt { $bar.finaldx = this.get_snap_position(dx, $bar.ox); this.hide_popup(); if (is_resizing_left) { - if (parent_bar_id === bar.task.id) { - bar.update_bar_position({ - x: $bar.ox + $bar.finaldx, - width: $bar.owidth - $bar.finaldx, - }); - } else { - bar.update_bar_position({ - x: $bar.ox + $bar.finaldx, - }); + if ( + !this.options.maintain_dependency_position || + width_on_start - ($bar.finaldx + this.get_unit_length()) > 0 + ) { + if (parent_bar_id === bar.task.id) { + bar.update_bar_position({ + x: $bar.ox + $bar.finaldx, + width: $bar.owidth - $bar.finaldx, + }); + } else { + bar.update_bar_position({ + x: $bar.ox + $bar.finaldx, + }); + } } } else if (is_resizing_right) { - if (parent_bar_id === bar.task.id) { - bar.update_bar_position({ - width: $bar.owidth + $bar.finaldx, - }); + if ( + !this.options.maintain_dependency_position || + width_on_start + ($bar.finaldx - this.get_unit_length()) > 0 + ) { + if (parent_bar_id === bar.task.id) { + bar.update_bar_position({ + width: $bar.owidth + $bar.finaldx, + }); + } + else if (this.options.maintain_dependency_position) { + bar.update_bar_position({ + x: $bar.ox + $bar.finaldx, + }); + } } } else if ( is_dragging && @@ -1433,7 +1462,23 @@ export default class Gantt { return out.filter(Boolean); } - get_snap_position(dx, ox) { + get_all_precedent_tasks(task_id) { + let out = []; + let from_process = [task_id]; + while (from_process.length) { + const preds = from_process.reduce((acc, curr) => { + acc = acc.concat(this.precedence_map[curr]); + return acc; + }, []); + + out = out.concat(preds); + from_process = preds.filter((d) => !from_process.includes(d)); + } + + return out.filter(Boolean); + } + + get_unit_length() { let unit_length = 1; const default_snap = this.options.snap_at || this.config.view_mode.snap_at || '1d'; @@ -1445,6 +1490,12 @@ export default class Gantt { duration; } + return unit_length; + } + + get_snap_position(dx, ox) { + let unit_length = this.get_unit_length(); + const rem = dx % (this.config.column_width / unit_length); let final_dx =