233 |
233 |
};
|
234 |
234 |
}
|
235 |
235 |
|
236 |
|
ganttEntryClick = function(e){
|
237 |
|
var icon_expander = e.currentTarget;
|
238 |
|
var subject = $(icon_expander.parentElement);
|
239 |
|
var subject_left = parseInt(subject.css('left')) + parseInt(icon_expander.offsetWidth);
|
240 |
|
var target_shown = null;
|
241 |
|
var target_top = 0;
|
242 |
|
var total_height = 0;
|
243 |
|
var out_of_hierarchy = false;
|
244 |
|
var iconChange = null;
|
245 |
|
if(subject.hasClass('open'))
|
246 |
|
iconChange = function(element){
|
247 |
|
var expander = $(element).find('.expander')
|
248 |
|
expander.switchClass('icon-expanded', 'icon-collapsed');
|
249 |
|
$(element).removeClass('open');
|
250 |
|
if (expander.find('svg').length === 1) {
|
251 |
|
updateSVGIcon(expander[0], 'angle-right')
|
|
236 |
ganttEntryClick = function (e) {
|
|
237 |
const iconExpanderElem = e.currentTarget;
|
|
238 |
const subjectElem = iconExpanderElem.parentElement;
|
|
239 |
const expanderWidth = iconExpanderElem.offsetWidth;
|
|
240 |
const recursive = e.ctrlKey;
|
|
241 |
|
|
242 |
function toggleClass(elem, class1, class2) {
|
|
243 |
if (!elem) return;
|
|
244 |
elem.classList.remove(class1);
|
|
245 |
elem.classList.add(class2);
|
|
246 |
}
|
|
247 |
|
|
248 |
function getLeft(elem) {
|
|
249 |
if (!elem) return 0;
|
|
250 |
return elem.offsetLeft || parseInt(elem.style.left);
|
|
251 |
}
|
|
252 |
|
|
253 |
function getTop(elem) {
|
|
254 |
if (!elem) return 0;
|
|
255 |
return elem.offsetTop || parseInt(elem.style.top);
|
|
256 |
}
|
|
257 |
|
|
258 |
function nextAll(elem, tagName) {
|
|
259 |
const nextAllElems = [];
|
|
260 |
let targetElem = elem.nextElementSibling;
|
|
261 |
while (targetElem) {
|
|
262 |
if (!tagName || targetElem.tagName === tagName) {
|
|
263 |
nextAllElems.push(targetElem);
|
252 |
264 |
}
|
253 |
|
};
|
254 |
|
else
|
255 |
|
iconChange = function(element){
|
256 |
|
var expander = $(element).find('.expander')
|
257 |
|
expander.find('.expander').switchClass('icon-collapsed', 'icon-expanded');
|
258 |
|
$(element).addClass('open');
|
259 |
|
if (expander.find('svg').length === 1) {
|
260 |
|
updateSVGIcon(expander[0], 'angle-down')
|
|
265 |
targetElem = targetElem.nextElementSibling;
|
|
266 |
}
|
|
267 |
return nextAllElems;
|
|
268 |
}
|
|
269 |
|
|
270 |
class GanttItem {
|
|
271 |
constructor(elem) {
|
|
272 |
this.elem = elem;
|
|
273 |
this.iconExpander = elem.querySelector(":scope > .icon.expander");
|
|
274 |
this.left =
|
|
275 |
getLeft(this.elem) + (this.iconExpander ? expanderWidth : 0);
|
|
276 |
this.top = getTop(this.elem);
|
|
277 |
this.isShown =
|
|
278 |
this.elem.offsetWidth > 0 || this.elem.offsetHeight > 0;
|
|
279 |
this.json = JSON.parse(this.elem.dataset.collapseExpand);
|
|
280 |
this.numberOfRows = this.elem.dataset.numberOfRows;
|
|
281 |
this.isCollapsed =
|
|
282 |
this.iconExpander?.classList.contains("icon-collapsed");
|
|
283 |
|
|
284 |
const selector =
|
|
285 |
`[data-collapse-expand="${this.json.obj_id}"]` +
|
|
286 |
`[data-number-of-rows="${this.numberOfRows}"]`;
|
|
287 |
this.taskBars = document.querySelectorAll(
|
|
288 |
`#gantt_area form > ${selector}`
|
|
289 |
);
|
|
290 |
this.selectedColumns = document.querySelectorAll(
|
|
291 |
`td.gantt_selected_column ${selector}`
|
|
292 |
);
|
|
293 |
}
|
|
294 |
|
|
295 |
toggleIcon(force = undefined) {
|
|
296 |
if (this.isCollapsed === undefined) return false;
|
|
297 |
|
|
298 |
this.elem.classList.remove("open");
|
|
299 |
const svgIcon = this.iconExpander.getElementsByTagName("svg");
|
|
300 |
|
|
301 |
if ((force === true && !(force === false)) || this.isCollapsed) {
|
|
302 |
toggleClass(this.iconExpander, "icon-collapsed", "icon-expanded");
|
|
303 |
this.elem.classList.add("open");
|
|
304 |
if (svgIcon.length === 1) {
|
|
305 |
updateSVGIcon(this.iconExpander, 'angle-down');
|
|
306 |
}
|
|
307 |
} else {
|
|
308 |
toggleClass(this.iconExpander, "icon-expanded", "icon-collapsed");
|
|
309 |
if (svgIcon.length === 1) {
|
|
310 |
updateSVGIcon(this.iconExpander, 'angle-right');
|
|
311 |
}
|
261 |
312 |
}
|
262 |
|
};
|
263 |
|
iconChange(subject);
|
264 |
|
subject.nextAll('div').each(function(_, element){
|
265 |
|
var el = $(element);
|
266 |
|
var json = el.data('collapse-expand');
|
267 |
|
var number_of_rows = el.data('number-of-rows');
|
268 |
|
var el_task_bars = '#gantt_area form > div[data-collapse-expand="' + json.obj_id + '"][data-number-of-rows="' + number_of_rows + '"]';
|
269 |
|
var el_selected_columns = 'td.gantt_selected_column div[data-collapse-expand="' + json.obj_id + '"][data-number-of-rows="' + number_of_rows + '"]';
|
270 |
|
if(out_of_hierarchy || parseInt(el.css('left')) <= subject_left){
|
271 |
|
out_of_hierarchy = true;
|
272 |
|
if(target_shown == null) return false;
|
273 |
|
|
274 |
|
var new_top_val = parseInt(el.css('top')) + total_height * (target_shown ? -1 : 1);
|
275 |
|
el.css('top', new_top_val);
|
276 |
|
$([el_task_bars, el_selected_columns].join()).each(function(_, el){
|
277 |
|
$(el).css('top', new_top_val);
|
278 |
|
});
|
|
313 |
|
|
314 |
this.isCollapsed = !this.isCollapsed;
|
279 |
315 |
return true;
|
280 |
316 |
}
|
281 |
317 |
|
282 |
|
var is_shown = el.is(':visible');
|
283 |
|
if(target_shown == null){
|
284 |
|
target_shown = is_shown;
|
285 |
|
target_top = parseInt(el.css('top'));
|
286 |
|
total_height = 0;
|
|
318 |
#setDisplayStyle(displayStyle) {
|
|
319 |
this.taskBars.forEach((taskBar) => {
|
|
320 |
taskBar.style.display = displayStyle;
|
|
321 |
});
|
|
322 |
this.selectedColumns.forEach((selectedColumn) => {
|
|
323 |
selectedColumn.style.display = displayStyle;
|
|
324 |
});
|
|
325 |
this.elem.style.display = displayStyle;
|
|
326 |
}
|
|
327 |
|
|
328 |
hide() {
|
|
329 |
this.#setDisplayStyle("none");
|
|
330 |
this.isShown = false;
|
|
331 |
}
|
|
332 |
|
|
333 |
show() {
|
|
334 |
this.#setDisplayStyle("");
|
|
335 |
this.isShown = true;
|
287 |
336 |
}
|
288 |
|
if(is_shown == target_shown){
|
289 |
|
$(el_task_bars).each(function(_, task) {
|
290 |
|
var el_task = $(task);
|
291 |
|
if(!is_shown)
|
292 |
|
el_task.css('top', target_top + total_height);
|
293 |
|
if(!el_task.hasClass('tooltip'))
|
294 |
|
el_task.toggle(!is_shown);
|
|
337 |
|
|
338 |
move(top) {
|
|
339 |
this.top = top;
|
|
340 |
this.taskBars.forEach((taskBar) => {
|
|
341 |
taskBar.style.top = `${top}px`;
|
295 |
342 |
});
|
296 |
|
$(el_selected_columns).each(function (_, attr) {
|
297 |
|
var el_attr = $(attr);
|
298 |
|
if (!is_shown)
|
299 |
|
el_attr.css('top', target_top + total_height);
|
300 |
|
el_attr.toggle(!is_shown);
|
|
343 |
this.selectedColumns.forEach((selectedColumn) => {
|
|
344 |
selectedColumn.style.top = `${top}px`;
|
301 |
345 |
});
|
302 |
|
if(!is_shown)
|
303 |
|
el.css('top', target_top + total_height);
|
304 |
|
iconChange(el);
|
305 |
|
el.toggle(!is_shown);
|
306 |
|
total_height += parseInt(json.top_increment);
|
|
346 |
this.elem.style.top = `${top}px`;
|
307 |
347 |
}
|
308 |
|
});
|
|
348 |
}
|
|
349 |
|
|
350 |
const subject = new GanttItem(subjectElem);
|
|
351 |
subject.toggleIcon();
|
|
352 |
|
|
353 |
let totalHeight = 0;
|
|
354 |
let outOfHierarchyTop = null;
|
|
355 |
let firstItemTop = null;
|
|
356 |
let collapsedStateHierarchy = new Map();
|
|
357 |
collapsedStateHierarchy.set(subject.left, subject.isCollapsed);
|
|
358 |
let prevItemLeft = subject.left;
|
|
359 |
|
|
360 |
function updateGanttItemPositionAndView (ganttItem) {
|
|
361 |
if (outOfHierarchyTop || ganttItem.left <= subject.left) {
|
|
362 |
if (!outOfHierarchyTop) outOfHierarchyTop = ganttItem.top;
|
|
363 |
|
|
364 |
const newTop =
|
|
365 |
ganttItem.top +
|
|
366 |
(subject.isCollapsed
|
|
367 |
? -outOfHierarchyTop + subject.top + subject.json.top_increment
|
|
368 |
: totalHeight);
|
|
369 |
|
|
370 |
ganttItem.move(newTop);
|
|
371 |
return;
|
|
372 |
}
|
|
373 |
|
|
374 |
// Clear the collapsed state for levels deeper than the current hierarchy
|
|
375 |
// level.
|
|
376 |
if (prevItemLeft > ganttItem.left) {
|
|
377 |
for (const left of collapsedStateHierarchy.keys()) {
|
|
378 |
if (left >= ganttItem.left) collapsedStateHierarchy.delete(left);
|
|
379 |
}
|
|
380 |
}
|
|
381 |
|
|
382 |
// Update the stored left value for the next loop
|
|
383 |
prevItemLeft = ganttItem.left;
|
|
384 |
|
|
385 |
if (!firstItemTop) {
|
|
386 |
firstItemTop = subject.top + subject.json.top_increment;
|
|
387 |
}
|
|
388 |
|
|
389 |
if (
|
|
390 |
(recursive && subject.isCollapsed) ||
|
|
391 |
(!recursive && collapsedStateHierarchy.values().some((i) => i))
|
|
392 |
) {
|
|
393 |
if (ganttItem.isShown) ganttItem.hide();
|
|
394 |
} else {
|
|
395 |
if (!ganttItem.isShown) ganttItem.show();
|
|
396 |
ganttItem.move(firstItemTop + totalHeight);
|
|
397 |
totalHeight += ganttItem.json.top_increment;
|
|
398 |
}
|
|
399 |
|
|
400 |
if (ganttItem.iconExpander) {
|
|
401 |
collapsedStateHierarchy.set(ganttItem.left, ganttItem.isCollapsed);
|
|
402 |
if (recursive && ganttItem.isCollapsed !== subject.isCollapsed) {
|
|
403 |
ganttItem.toggleIcon();
|
|
404 |
}
|
|
405 |
}
|
|
406 |
}
|
|
407 |
|
|
408 |
// Get all subsequent DIV elements, convert to GanttItem,
|
|
409 |
// and update their positions and view states.
|
|
410 |
nextAll(subjectElem, "DIV")
|
|
411 |
.map((elem) => new GanttItem(elem))
|
|
412 |
.forEach(updateGanttItemPositionAndView);
|
|
413 |
|
309 |
414 |
drawGanttHandler();
|
310 |
415 |
};
|
311 |
416 |
|