Files
crmPRO/libraries/Simple-Gant-master/frappe-gantt.js
2026-02-28 14:48:24 +01:00

2753 lines
77 KiB
JavaScript

var Gantt = (function () {
'use strict';
const YEAR = 'year';
const MONTH = 'month';
const DAY = 'day';
const HOUR = 'hour';
const MINUTE = 'minute';
const SECOND = 'second';
const MILLISECOND = 'millisecond';
const month_names = {
en: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
],
ru: [
'Январь',
'Февраль',
'Март',
'Апрель',
'Май',
'Июнь',
'Июль',
'Август',
'Сентябрь',
'Октябрь',
'Ноябрь',
'Декабрь'
],
pl: [
'Styczeń',
'Luty',
'Marzec',
'Kwiecień',
'Maj',
'Czerwiec',
'Lipiec',
'Sierpień',
'Wrzesień',
'Październik',
'Listopad',
'Grudzień'
]
};
var date_utils = {
parse(date, date_separator = '-', time_separator = /[.:]/) {
if (date instanceof Date) {
return date;
}
if (typeof date === 'string') {
let date_parts, time_parts;
const parts = date.split(' ');
date_parts = parts[0]
.split(date_separator)
.map(val => parseInt(val, 10));
time_parts = parts[1] && parts[1].split(time_separator);
// month is 0 indexed
date_parts[1] = date_parts[1] - 1;
let vals = date_parts;
if (time_parts && time_parts.length) {
if (time_parts.length == 4) {
time_parts[3] = '0.' + time_parts[3];
time_parts[3] = parseFloat(time_parts[3]) * 1000;
}
vals = vals.concat(time_parts);
}
return new Date(...vals);
}
},
to_string(date, with_time = false) {
if (!(date instanceof Date)) {
throw new TypeError('Invalid argument type');
}
const vals = this.get_date_values(date).map((val, i) => {
if (i === 1) {
// add 1 for month
val = val + 1;
}
if (i === 6) {
return padStart(val + '', 3, '0');
}
return padStart(val + '', 2, '0');
});
const date_string = `${vals[0]}-${vals[1]}-${vals[2]}`;
const time_string = `${vals[3]}:${vals[4]}:${vals[5]}.${vals[6]}`;
return date_string + (with_time ? ' ' + time_string : '');
},
format(date, format_string = 'YYYY-MM-DD HH:mm:ss.SSS', lang = 'en') {
const values = this.get_date_values(date).map(d => padStart(d, 2, 0));
const format_map = {
YYYY: values[0],
MM: padStart(+values[1] + 1, 2, 0),
DD: values[2],
HH: values[3],
mm: values[4],
ss: values[5],
SSS:values[6],
D: values[2],
MMMM: month_names[lang][+values[1]],
MMM: month_names[lang][+values[1]]
};
let str = format_string;
const formatted_values = [];
Object.keys(format_map)
.sort((a, b) => b.length - a.length) // big string first
.forEach(key => {
if (str.includes(key)) {
str = str.replace(key, `$${formatted_values.length}`);
formatted_values.push(format_map[key]);
}
});
formatted_values.forEach((value, i) => {
str = str.replace(`$${i}`, value);
});
return str;
},
diff(date_a, date_b, scale = DAY) {
let milliseconds, seconds, hours, minutes, days, months, years;
milliseconds = date_a - date_b;
seconds = milliseconds / 1000;
minutes = seconds / 60;
hours = minutes / 60;
days = hours / 24;
months = days / 30;
years = months / 12;
if (!scale.endsWith('s')) {
scale += 's';
}
return Math.floor(
{
milliseconds,
seconds,
minutes,
hours,
days,
months,
years
}[scale]
);
},
today() {
const vals = this.get_date_values(new Date()).slice(0, 3);
return new Date(...vals);
},
now() {
return new Date();
},
add(date, qty, scale) {
qty = parseInt(qty, 10);
const vals = [
date.getFullYear() + (scale === YEAR ? qty : 0),
date.getMonth() + (scale === MONTH ? qty : 0),
date.getDate() + (scale === DAY ? qty : 0),
date.getHours() + (scale === HOUR ? qty : 0),
date.getMinutes() + (scale === MINUTE ? qty : 0),
date.getSeconds() + (scale === SECOND ? qty : 0),
date.getMilliseconds() + (scale === MILLISECOND ? qty : 0)
];
return new Date(...vals);
},
start_of(date, scale) {
const scores = {
[YEAR]: 6,
[MONTH]: 5,
[DAY]: 4,
[HOUR]: 3,
[MINUTE]: 2,
[SECOND]: 1,
[MILLISECOND]: 0
};
function should_reset(_scale) {
const max_score = scores[scale];
return scores[_scale] <= max_score;
}
const vals = [
date.getFullYear(),
should_reset(YEAR) ? 0 : date.getMonth(),
should_reset(MONTH) ? 1 : date.getDate(),
should_reset(DAY) ? 0 : date.getHours(),
should_reset(HOUR) ? 0 : date.getMinutes(),
should_reset(MINUTE) ? 0 : date.getSeconds(),
should_reset(SECOND) ? 0 : date.getMilliseconds()
];
return new Date(...vals);
},
clone(date) {
return new Date(...this.get_date_values(date));
},
get_date_values(date) {
return [
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
];
},
get_days_in_month(date) {
const no_of_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const month = date.getMonth();
if (month !== 1) {
return no_of_days[month];
}
// Feb
const year = date.getFullYear();
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return 29;
}
return 28;
}
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
function padStart(str, targetLength, padString) {
str = str + '';
targetLength = targetLength >> 0;
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (str.length > targetLength) {
return String(str);
} else {
targetLength = targetLength - str.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + String(str);
}
}
function $(expr, con) {
return typeof expr === 'string'
? (con || document).querySelector(expr)
: expr || null;
}
function createSVG(tag, attrs) {
const elem = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (let attr in attrs) {
if (attr === 'append_to') {
const parent = attrs.append_to;
parent.appendChild(elem);
} else if (attr === 'innerHTML') {
elem.innerHTML = attrs.innerHTML;
} else {
elem.setAttribute(attr, attrs[attr]);
}
}
return elem;
}
function animateSVG(svgElement, attr, from, to) {
const animatedSvgElement = getAnimationElement(svgElement, attr, from, to);
if (animatedSvgElement === svgElement) {
// triggered 2nd time programmatically
// trigger artificial click event
const event = document.createEvent('HTMLEvents');
event.initEvent('click', true, true);
event.eventName = 'click';
animatedSvgElement.dispatchEvent(event);
}
}
function getAnimationElement(
svgElement,
attr,
from,
to,
dur = '0.4s',
begin = '0.1s'
) {
const animEl = svgElement.querySelector('animate');
if (animEl) {
$.attr(animEl, {
attributeName: attr,
from,
to,
dur,
begin: 'click + ' + begin // artificial click
});
return svgElement;
}
const animateElement = createSVG('animate', {
attributeName: attr,
from,
to,
dur,
begin,
calcMode: 'spline',
values: from + ';' + to,
keyTimes: '0; 1',
keySplines: cubic_bezier('ease-out')
});
svgElement.appendChild(animateElement);
return svgElement;
}
function cubic_bezier(name) {
return {
ease: '.25 .1 .25 1',
linear: '0 0 1 1',
'ease-in': '.42 0 1 1',
'ease-out': '0 0 .58 1',
'ease-in-out': '.42 0 .58 1'
}[name];
}
$.on = (element, event, selector, callback) => {
if (!callback) {
callback = selector;
$.bind(element, event, callback);
} else {
$.delegate(element, event, selector, callback);
}
};
$.off = (element, event, handler) => {
element.removeEventListener(event, handler);
};
$.bind = (element, event, callback) => {
event.split(/\s+/).forEach(function(event) {
element.addEventListener(event, callback);
});
};
$.delegate = (element, event, selector, callback) => {
element.addEventListener(event, function(e) {
const delegatedTarget = e.target.closest(selector);
if (delegatedTarget) {
e.delegatedTarget = delegatedTarget;
callback.call(this, e, delegatedTarget);
}
});
};
$.closest = (selector, element) => {
if (!element) return null;
if (element.matches(selector)) {
return element;
}
return $.closest(selector, element.parentNode);
};
$.attr = (element, attr, value) => {
if (!value && typeof attr === 'string') {
return element.getAttribute(attr);
}
if (typeof attr === 'object') {
for (let key in attr) {
$.attr(element, key, attr[key]);
}
return;
}
element.setAttribute(attr, value);
};
class Bar {
constructor(gantt, task) {
this.set_defaults(gantt, task);
this.prepare();
this.draw();
this.bind();
}
set_defaults(gantt, task) {
this.action_completed = false;
this.gantt = gantt;
this.task = task;
}
prepare() {
this.prepare_values();
this.prepare_helpers();
}
prepare_values() {
this.invalid = this.task.invalid;
this.height = this.gantt.options.bar_height;
this.x = this.compute_x();
this.y = this.compute_y();
this.corner_radius = this.gantt.options.bar_corner_radius;
this.duration =
date_utils.diff(this.task._end, this.task._start, 'hour') /
this.gantt.options.step;
this.width = this.gantt.options.column_width * this.duration;
this.progress_width =
this.gantt.options.column_width *
this.duration *
(this.task.progress / 100) || 0;
this.group = createSVG('g', {
class: 'bar-wrapper ' + (this.task.custom_class || ''),
'data-id': this.task.id
});
console.log(this.task);
if(this.task.has.length==0)
this.bar_group = createSVG('g', {
class: 'bar-group',
append_to: this.group
});
else
this.bar_group = createSVG('g', {
class: 'bar-group outline',
append_to: this.group
});
this.handle_group = createSVG('g', {
class: 'handle-group',
append_to: this.group
});
}
prepare_helpers() {
SVGElement.prototype.getX = function() {
return +this.getAttribute('x');
};
SVGElement.prototype.getY = function() {
return +this.getAttribute('y');
};
SVGElement.prototype.getWidth = function() {
return +this.getAttribute('width');
};
SVGElement.prototype.getHeight = function() {
return +this.getAttribute('height');
};
SVGElement.prototype.getEndX = function() {
return this.getX() + this.getWidth();
};
}
draw() {
this.draw_bar();
this.draw_triangle();
this.draw_progress_bar();
this.draw_label();
this.draw_resize_handles();
// this.draw_circle();
}
draw_bar() {
this.$bar = createSVG('rect', {
x: this.x,
y: this.y,
width: this.width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
class: 'bar ' + this.task.custom_class,
append_to: this.bar_group
});
animateSVG(this.$bar, 'width', 0, this.width);
if (this.invalid) {
this.$bar.classList.add('bar-invalid');
}
}
draw_triangle(){
if(this.task.has.length==0){
return;
}
let x= this.x;
let y= this.y;
let point= (x-15) + ',' + (y + 15 )+' '+(x-25)+','+(y)+' '+(x-5)+','+(y);
if(this.task.dependencies.length!=0)
point=(x-30) + ',' + (y + 15 )+' '+(x-40)+','+(y)+' '+(x-20)+','+(y);
this.$triangle = createSVG('polygon', {
points:point,
class: this.getColor(this.task.progress)+" triangle",
'hide':'off',
'triangle-id':this.task.id,
append_to: this.group
});
}
draw_circle(){
let x= this.x+this.width-5;
let y=this.y+5;
this.$circle= createSVG("circle",{
cx:x,
cy:y,
r:10,
class:'circle',
append_to: this.group
});
this.$txt= createSVG("text",{
x:x-3,
y:y+4,
innerHTML: this.task.people? this.task.people.length : 0,
class:'text-circle',
append_to: this.group
});
animateSVG(this.$circle, 'cx', this.x, x);
animateSVG(this.$txt, 'x', this.x, x-3);
}
getColor(progress){
if(progress<=50)
return "red";
if(progress >90)
return "green";
if(progress > 50 && progress <=80)
return "orange";
if(progress > 80 && progress <=90)
return "yellow";
}
draw_progress_bar() {
if (this.invalid) return;
this.$bar_progress = createSVG('rect', {
x: this.x,
y: this.y,
width: this.progress_width,
height: this.height,
rx: this.corner_radius,
ry: this.corner_radius,
//here
class: this.getColor(this.task.progress),
append_to: this.bar_group
});
animateSVG(this.$bar_progress, 'width', 0, this.progress_width);
}
draw_label() {
createSVG('text', {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
innerHTML: this.task.name,
class: 'bar-label',
append_to: this.bar_group
});
// labels get BBox in the next tick
requestAnimationFrame(() => this.update_label_position());
}
draw_resize_handles() {
if (this.invalid) return;
const bar = this.$bar;
const handle_width = 8;
createSVG('rect', {
x: bar.getX() + bar.getWidth() - 9,
y: bar.getY() + 1,
width: handle_width,
height: this.height - 2,
rx: this.corner_radius,
ry: this.corner_radius,
class: 'handle right',
append_to: this.handle_group
});
createSVG('rect', {
x: bar.getX() + 1,
y: bar.getY() + 1,
width: handle_width,
height: this.height - 2,
rx: this.corner_radius,
ry: this.corner_radius,
class: 'handle left',
append_to: this.handle_group
});
if (this.task.progress && this.task.progress < 100) {
this.$handle_progress = createSVG('polygon', {
points: this.get_progress_polygon_points().join(','),
class: 'handle progress',
append_to: this.handle_group
});
}
}
get_progress_polygon_points() {
const bar_progress = this.$bar_progress;
return [
bar_progress.getEndX() - 5,
bar_progress.getY() + bar_progress.getHeight(),
bar_progress.getEndX() + 5,
bar_progress.getY() + bar_progress.getHeight(),
bar_progress.getEndX(),
bar_progress.getY() + bar_progress.getHeight() - 8.66
];
}
bind() {
if (this.invalid) return;
this.setup_click_event();
this.triangle_event();
}
setup_click_event() {
$.on(this.bar_group, 'focus ' + this.gantt.options.popup_trigger, e => {
if (this.action_completed || this.gantt.bar_being_dragged) {
// Jeżeli jest przeciąganie, nie reagujemy na kliknięcia
return;
}
if (e.type === 'click') {
this.gantt.trigger_event('click', [this.task]);
}
this.gantt.unselect_all();
this.group.classList.toggle('active');
this.show_popup();
});
}
triangle_event(){
if(!this.$triangle)
return;
$.on(this.$triangle,'click',e => {
if(e.type=== 'click'){
//console.log(this.$triangle.getAttribute('hide'));
if(this.$triangle.getAttribute('hide')==='off'){
this.$triangle.setAttribute('hide','on');
this.toggle_children("noopacity");
}else{
this.$triangle.setAttribute('hide','off');
this.toggle_children('opacity')
}
this.update_triangle_position();
this.update_circle();
}
});
}
toggle_children(className){
if(!this.$triangle)
return;
let arr= this.task.has;
arr.forEach((taskid)=>{
let g=document.querySelectorAll("[data-id='"+taskid+"']");
g.forEach(i=>{
i.setAttribute('class',className+' bar-wrapper ');
});
let ar=document.querySelectorAll("[data-from='"+taskid+"'],[data-to='"+taskid+"']");
ar.forEach(i=>{
i.setAttribute('class',className);
});
let tr=document.querySelectorAll("[triangle-id='"+taskid+"']");
tr.forEach(i=>{
i.setAttribute('hide','off');
});
});
this.gantt.unselect_all();
this.group.classList.toggle('active');
}
show_popup() {
task_popup( this.task.id );
// if (this.gantt.bar_being_dragged) return;
// const start_date = date_utils.format(this.task._start, 'MMM D');
// const end_date = date_utils.format(
// date_utils.add(this.task._end, -1, 'second'),
// 'MMM D'
// );
// const subtitle = start_date + ' - ' + end_date;
// this.gantt.show_popup({
// target_element: this.$bar,
// title: this.task.name,
// subtitle: subtitle,
// task: this.task
// });
}
update_bar_position({ x = null, width = null }) {
const bar = this.$bar;
if (x) {
// get all x values of parent task
const xs = this.task.dependencies.map(dep => {
return this.gantt.get_bar(dep).$bar.getX();
});
// child task must not go before parent
const valid_x = xs.reduce((prev, curr) => {
return x >= curr;
}, x);
if (!valid_x) {
width = null;
return;
}
this.update_attr(bar, 'x', x);
}
if (width && width >= this.gantt.options.column_width) {
this.update_attr(bar, 'width', width);
}
this.update_label_position();
this.update_handle_position();
this.update_progressbar_position();
this.update_arrow_position();
this.update_triangle_position();
this.update_circle();
}
date_changed() {
let changed = false;
const { new_start_date, new_end_date } = this.compute_start_end_date();
if (Number(this.task._start) !== Number(new_start_date)) {
changed = true;
this.task._start = new_start_date;
}
if (Number(this.task._end) !== Number(new_end_date)) {
changed = true;
this.task._end = new_end_date;
}
if (!changed) return;
this.gantt.trigger_event('date_change', [
this.task,
new_start_date,
date_utils.add(new_end_date, -1, 'second')
]);
}
progress_changed() {
const new_progress = this.compute_progress();
this.task.progress = new_progress;
this.gantt.trigger_event('progress_change', [this.task, new_progress]);
if(this.$triangle)
this.$triangle.setAttribute('class',this.getColor(this.task.progress));
}
set_action_completed() {
this.action_completed = true;
setTimeout(() => (this.action_completed = false), 1000);
}
compute_start_end_date() {
const bar = this.$bar;
const x_in_units = bar.getX() / this.gantt.options.column_width;
const new_start_date = date_utils.add(
this.gantt.gantt_start,
x_in_units * this.gantt.options.step,
'hour'
);
const width_in_units = bar.getWidth() / this.gantt.options.column_width;
const new_end_date = date_utils.add(
new_start_date,
width_in_units * this.gantt.options.step,
'hour'
);
return { new_start_date, new_end_date };
}
compute_progress() {
const progress =
this.$bar_progress.getWidth() / this.$bar.getWidth() * 100;
return parseInt(progress, 10);
}
compute_x() {
const { step, column_width } = this.gantt.options;
const task_start = this.task._start;
const gantt_start = this.gantt.gantt_start;
const diff = date_utils.diff(task_start, gantt_start, 'hour');
let x = diff / step * column_width;
if (this.gantt.view_is('Month')) {
const diff = date_utils.diff(task_start, gantt_start, 'day');
x = diff * column_width / 30;
}
return x;
}
compute_y() {
return (
this.gantt.options.header_height +
this.gantt.options.padding +
this.task._index * (this.height + this.gantt.options.padding)
);
}
get_snap_position(dx) {
let odx = dx,
rem,
position;
if (this.gantt.view_is('Week')) {
rem = dx % (this.gantt.options.column_width / 7);
position =
odx -
rem +
(rem < this.gantt.options.column_width / 14
? 0
: this.gantt.options.column_width / 7);
} else if (this.gantt.view_is('Month')) {
rem = dx % (this.gantt.options.column_width / 30);
position =
odx -
rem +
(rem < this.gantt.options.column_width / 60
? 0
: this.gantt.options.column_width / 30);
} else {
rem = dx % this.gantt.options.column_width;
position =
odx -
rem +
(rem < this.gantt.options.column_width / 2
? 0
: this.gantt.options.column_width);
}
return position;
}
update_attr(element, attr, value) {
value = +value;
if (!isNaN(value)) {
element.setAttribute(attr, value);
}
return element;
}
update_progressbar_position() {
this.$bar_progress.setAttribute('x', this.$bar.getX());
this.$bar_progress.setAttribute(
'width',
this.$bar.getWidth() * (this.task.progress / 100)
);
}
update_triangle_position() {
if(!this.$triangle)
return;
let x= this.$bar.getX();
let y= this.$bar.getY();
let point=(x-15) + ',' + (y + 15 )+' '+(x-25)+','+(y)+' '+(x-5)+','+(y);
if(this.$triangle.getAttribute('hide')==='off'){
if(this.task.dependencies.length!=0)
point=(x-30) + ',' + (y + 15 )+' '+(x-40)+','+(y)+' '+(x-20)+','+(y);
}else{
point= (x-15) + ',' + (y)+' '+(x-25)+','+(y+15)+' '+(x-5)+','+(y+15);
if(this.task.dependencies.length!=0)
point=(x-35) + ',' + (y)+' '+(x-45)+','+(y+15)+' '+(x-25)+','+(y+15);
}
this.$triangle.setAttribute('points', point);
}
update_circle(){
if(!this.$circle)
return;
let x= this.$bar.getX()+this.$bar.getWidth()-5;
let y= this.$bar.getY()+5;
this.$circle.setAttribute('cx',x);
this.$circle.setAttribute('cy',y);
this.$txt.setAttribute('x',x-3);
this.$txt.setAttribute('y',y+4);
}
update_label_position() {
const bar = this.$bar,
label = this.group.querySelector('.bar-label');
if (label.getBBox().width > bar.getWidth()) {
label.classList.add('big');
label.setAttribute('x', bar.getX() + bar.getWidth() + 5);
} else {
label.classList.remove('big');
label.setAttribute('x', bar.getX() + bar.getWidth() / 2);
}
}
update_handle_position() {
const bar = this.$bar;
this.handle_group
.querySelector('.handle.left')
.setAttribute('x', bar.getX() + 1);
this.handle_group
.querySelector('.handle.right')
.setAttribute('x', bar.getEndX() - 9);
const handle = this.group.querySelector('.handle.progress');
handle &&
handle.setAttribute('points', this.get_progress_polygon_points());
}
update_arrow_position() {
this.arrows = this.arrows || [];
for (let arrow of this.arrows) {
arrow.update();
}
}
}
class Arrow {
constructor(gantt, from_task, to_task) {
this.gantt = gantt;
this.from_task = from_task;
this.to_task = to_task;
this.calculate_path();
this.draw();
}
calculate_path() {
let start_x =
this.from_task.$bar.getX() + this.from_task.$bar.getWidth() / 2;
const condition = () =>
this.to_task.$bar.getX() < start_x + this.gantt.options.padding &&
start_x > this.from_task.$bar.getX() + this.gantt.options.padding;
while (condition()) {
start_x -= 10;
}
const start_y =
this.gantt.options.header_height +
this.gantt.options.bar_height +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.from_task.task._index +
this.gantt.options.padding;
const end_x = this.to_task.$bar.getX() - this.gantt.options.padding / 2;
const end_y =
this.gantt.options.header_height +
this.gantt.options.bar_height / 2 +
(this.gantt.options.padding + this.gantt.options.bar_height) *
this.to_task.task._index +
this.gantt.options.padding;
const from_is_below_to =
this.from_task.task._index > this.to_task.task._index;
const curve = this.gantt.options.arrow_curve;
const clockwise = from_is_below_to ? 1 : 0;
const curve_y = from_is_below_to ? -curve : curve;
const offset = from_is_below_to
? end_y + this.gantt.options.arrow_curve
: end_y - this.gantt.options.arrow_curve;
this.path = `
M ${start_x} ${start_y}
V ${offset}
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y}
L ${end_x} ${end_y}
m -5 -5
l 5 5
l -5 5`;
if (
this.to_task.$bar.getX() <
this.from_task.$bar.getX() + this.gantt.options.padding
) {
const down_1 = this.gantt.options.padding / 2 - curve;
const down_2 =
this.to_task.$bar.getY() +
this.to_task.$bar.getHeight() / 2 -
curve_y;
const left = this.to_task.$bar.getX() - this.gantt.options.padding;
this.path = `
M ${start_x} ${start_y}
v ${down_1}
a ${curve} ${curve} 0 0 1 -${curve} ${curve}
H ${left}
a ${curve} ${curve} 0 0 ${clockwise} -${curve} ${curve_y}
V ${down_2}
a ${curve} ${curve} 0 0 ${clockwise} ${curve} ${curve_y}
L ${end_x} ${end_y}
m -5 -5
l 5 5
l -5 5`;
}
}
draw() {
this.element = createSVG('path', {
d: this.path,
'data-from': this.from_task.task.id,
'data-to': this.to_task.task.id
});
}
update() {
this.calculate_path();
this.element.setAttribute('d', this.path);
}
}
class Popup {
constructor(parent,gantt, custom_html) {
this.gantt=gantt;
this.parent = parent;
this.edible=false;
this.add='<svg id="add" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 52 52" style="enable-background:new 0 0 52 52;" xml:space="preserve"><g stroke-width="6"><path d="M26,0C11.664,0,0,11.663,0,26s11.664,26,26,26s26-11.663,26-26S40.336,0,26,0z M26,50C12.767,50,2,39.233,2,26 S12.767,2,26,2s24,10.767,24,24S39.233,50,26,50z"/><path d="M38.5,25H27V14c0-0.553-0.448-1-1-1s-1,0.447-1,1v11H13.5c-0.552,0-1,0.447-1,1s0.448,1,1,1H25v12c0,0.553,0.448,1,1,1 s1-0.447,1-1V27h11.5c0.552,0,1-0.447,1-1S39.052,25,38.5,25z"/></g></svg>';
this.edit='<svg id="edit" xmlns="http://www.w3.org/2000/svg" height="476pt" viewBox="0 0 476.76426 476" width="476pt"><path stroke-width="6" d="m451.023438 26.117188c-34.386719-34.3125-90.058594-34.3125-124.449219 0v.046874l-285.582031 285.550782c-.648438.683594-1.171876 1.472656-1.542969 2.335937-.101563.214844-.195313.433594-.273438.65625-.097656.21875-.1875.4375-.261719.664063l-38.65625 151.761718c-.714843 2.742188.082032 5.660157 2.085938 7.664063s4.921875 2.796875 7.664062 2.085937l151.753907-38.640624c.230469-.0625.429687-.183594.65625-.261719.230469-.078125.457031-.171875.679687-.273438.859375-.371093 1.648438-.894531 2.328125-1.542969l285.542969-285.558593.054688-.042969c34.320312-34.382812 34.320312-90.0625 0-124.445312zm-11.3125 11.308593c25.886718 25.949219 28.1875 67.183594 5.351562 95.851563l-101.191406-101.203125c28.664062-22.835938 69.898437-20.53125 95.839844 5.351562zm-107.472657 5.707031 101.808594 101.757813-28.679687 28.6875-101.808594-101.804687zm-303.445312 376.800782c12.628906 5.671875 22.742187 15.78125 28.414062 28.414062l-38.117187 9.703125zm44 24.421875c-7.367188-18.199219-21.800781-32.632813-40-40l18.144531-71.382813 93.230469 93.238282zm86.976562-25.199219-101.808593-101.785156 234.289062-234.289063 101.804688 101.808594zm0 0"/></svg>';
this.custom_html = custom_html;
this.make();
}
event_edit(parent,options,gantt){
let $edit=parent.querySelector("#edit");
let $add=parent.querySelector("#add");
$add.addEventListener("click",function(){
//init
let size =3;
let drops = [];
let d="";
let p = gantt.people;
let selectedPeople=[];
let unselectedPeople=p;
parent.querySelector(".tools").innerHTML='<svg id="valid" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 406.834 406.834" style="enable-background:new 0 0 406.834 406.834;" xml:space="preserve"><polygon points="385.621,62.507 146.225,301.901 21.213,176.891 0,198.104 146.225,344.327 406.834,83.72 "/></svg>';
parent.querySelector(".title").innerHTML="Assigned to ";
for(let i = 0 ; i<size; i++){
//create drop downs
let drop= '<select id=s'+i+' value="">';
let op ='<option value="">Select User </option> ';
op= op + p.map(p =>{
return '<option value ="'+p+'">'+p+'</option>';
});
drop+=op+'</select>';
d+=drop;
}
const fn=(event)=>{
console.log("in");
let selectedItem =event.target;
let arr=[];
parent.querySelector("#divselect").querySelectorAll("select").forEach(j=>{
if(j.value != "")
arr.push(j.value);
});
selectedPeople=arr;
unselectedPeople= p.filter(pers =>{
return !selectedPeople.includes(pers);
});
parent.querySelector("#divselect").querySelectorAll("select").forEach(j=>{
let v = j.value;
let op ='<option value="">Select User </option>';
if(v != "")
op+='<option value ="'+v+'"selected>'+v+'</option>';
op= op + unselectedPeople.map(p =>{
return '<option value ="'+p+'">'+p+'</option>';
});
j.innerHTML=op;
});
}
const eventSelect=()=> {
for(let i=0;i<size;i++){
parent.querySelector("#divselect").querySelectorAll("select")[i].addEventListener("change",fn);
}
}
parent.querySelector('.subtitle').innerHTML="<div id='divselect'></div> <div id='btn'></div>";
parent.querySelector('#divselect').innerHTML=d;
parent.querySelector('#btn').innerHTML='<svg id="more" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 52 52" style="enable-background:new 0 0 52 52;" xml:space="preserve"><g stroke-width="6"><path d="M26,0C11.664,0,0,11.663,0,26s11.664,26,26,26s26-11.663,26-26S40.336,0,26,0z M26,50C12.767,50,2,39.233,2,26 S12.767,2,26,2s24,10.767,24,24S39.233,50,26,50z"/><path d="M38.5,25H27V14c0-0.553-0.448-1-1-1s-1,0.447-1,1v11H13.5c-0.552,0-1,0.447-1,1s0.448,1,1,1H25v12c0,0.553,0.448,1,1,1 s1-0.447,1-1V27h11.5c0.552,0,1-0.447,1-1S39.052,25,38.5,25z"/></g></svg>';
for(let i=0;i<size;i++){
parent.querySelector("#divselect").querySelectorAll("select")[i].addEventListener("change",fn);
console.log(parent.querySelector("#divselect").querySelectorAll("select")[i]);
}
let more= parent.querySelector("#more");
more.addEventListener("click",function(){
++size;
let selectElement= document.createElement("select");
selectElement.id="s"+size;
selectElement.value="";
parent.querySelector("#divselect").appendChild(selectElement);
let options = document.createElement('option');
options.value="";
options.text="Select User ";
options.selected=true;
selectElement.appendChild(options);
unselectedPeople.map(p =>{
let options = document.createElement('option');
options.value=p;
options.text=p;
selectElement.appendChild(options);
});
eventSelect();
});
let valid = parent.querySelector("#valid");
valid.addEventListener("click",function(){
let task = options.task;
task.people=selectedPeople;
gantt.update_task(task);
parent.style.opacity = 0;
});
});
$edit.addEventListener("click",function(){
parent.querySelector(".tools").innerHTML='<svg id="valid" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 406.834 406.834" style="enable-background:new 0 0 406.834 406.834;" xml:space="preserve"><polygon points="385.621,62.507 146.225,301.901 21.213,176.891 0,198.104 146.225,344.327 406.834,83.72 "/></svg>';
parent.querySelector(".title").innerHTML='<input id="task_name" class="input1" type="text" value="'+options.task.name+'">';
parent.querySelector(".subtitle").innerHTML='<input id="task_start" class="input2" type="date" value="'+options.task.start+'">'+
'<input id="task_end" class="input2" type="date" value="'+options.task.end+'">';
let valid = parent.querySelector("#valid");
valid.addEventListener("click",function(){
let task_name=parent.querySelector("#task_name").value;
let task_start=parent.querySelector("#task_start").value;
let task_end=parent.querySelector("#task_end").value;
let task = options.task;
task.name=task_name;
task.end=task_end;
task.start=task_start;
gantt.update_task(task);
parent.style.opacity = 0;
});
});
}
make() {
this.HtmlTools='<div class="tools"></div> <div class="title"></div><div class="subtitle"></div><div class="pointer"></div>';
console.log(this.edit);
this.parent.innerHTML = this.HtmlTools;
this.hide();
this.tools = this.parent.querySelector('.tools');
this.title = this.parent.querySelector('.title');
this.subtitle = this.parent.querySelector('.subtitle');
this.pointer = this.parent.querySelector('.pointer');
}
show(options) {
this.tools.innerHTML= this.add +''+this.edit;
this.event_edit(this.parent,options,this.gantt);
if (!options.target_element) {
throw new Error('target_element is required to show popup');
}
if (!options.position) {
options.position = 'left';
}
const target_element = options.target_element;
if (this.custom_html) {
let html = this.custom_html(options.task);
html += '<div class="pointer"></div>';
this.parent.innerHTML = html;
this.pointer = this.parent.querySelector('.pointer');
} else {
// set data
this.title.innerHTML = options.title;
this.subtitle.innerHTML = options.subtitle;
this.parent.style.width = this.parent.clientWidth + 'px';
}
// set position
let position_meta;
if (target_element instanceof HTMLElement) {
position_meta = target_element.getBoundingClientRect();
} else if (target_element instanceof SVGElement) {
position_meta = options.target_element.getBBox();
}
if (options.position === 'left') {
this.parent.style.left =
position_meta.x + (position_meta.width + 10) + 'px';
this.parent.style.top = position_meta.y + 'px';
this.pointer.style.transform = 'rotateZ(90deg)';
this.pointer.style.left = '-7px';
this.pointer.style.top = '2px';
}
// show
this.parent.style.opacity = 1;
//event
}
hide() {
this.parent.style.opacity = 0;
}
}
class Gantt {
constructor(wrapper, tasks, options) {
this.people=["Bernadette Young",
"Robert Davis",
"Elena Reyes",
"Evelyn Goodrich",
"Remi Hakimi",
"Asmara Tewolde",
"Cristina Schwarzer",
"Anna Grimsdottir",
"Thomas Zotov",
"Arthur Maclean",
"Mauricio Mirella",
"Anthony Foccacia",
"Mark Waugh",
"Urbain Leverrier",
"John Adams",
"Chris Davis",
"Rania Ali",
"Spiro Agnew",
"Diana Frost",
"David Nixon"];
this.setup_wrapper(wrapper);
this.setup_options(options);
this.setup_tasks(tasks);
// initialize with default view mode
this.synchronizing_date(tasks);
this.setup_tasks(tasks);
// this.our_menu();
this.change_view_mode();
this.bind_events();
}
synchronizing_date(tasks){
this.tasks= tasks.map((task,i)=>{
let arr=this.get_all_dependent_tasks(task.id);
let s=null;
if(arr.length>0){
s=this.get_task_obj(arr[0],this.tasks);
for(let a of arr){
let aa=this.get_task_obj(a,this.tasks);
if(Date.parse(date_utils.parse(aa.end))>Date.parse(date_utils.parse(s.end)))
s=aa;
}
}
if(s && this.options.sync_parent_end_with_children)
task.end=s.end;
task.has=this.get_all_dependent_tasks(task.id);
return task;
});
}
get_task_obj(id,tasks) {
return tasks.find(task =>
task.id === id
);
}
setup_wrapper(element) {
let svg_element, wrapper_element;
// CSS Selector is passed
if (typeof element === 'string') {
element = document.querySelector(element);
}
// get the SVGElement
if (element instanceof HTMLElement) {
wrapper_element = element;
svg_element = element.querySelector('svg');
} else if (element instanceof SVGElement) {
svg_element = element;
} else {
throw new TypeError(
'Frappé Gantt only supports usage of a string CSS selector,' +
" HTML DOM element or SVG DOM element for the 'element' parameter"
);
}
// svg element
if (!svg_element) {
// create it
this.$svg = createSVG('svg', {
append_to: wrapper_element,
class: 'gantt'
});
} else {
this.$svg = svg_element;
this.$svg.classList.add('gantt');
}
// wrapper element
this.$container = document.createElement('div');
this.$container.classList.add('gantt-container');
const parent_element = this.$svg.parentElement;
parent_element.appendChild(this.$container);
this.$container.appendChild(this.$svg);
// popup wrapper
this.popup_wrapper = document.createElement('div');
this.popup_wrapper.classList.add('popup-wrapper');
this.$container.appendChild(this.popup_wrapper);
}
our_menu(){
// context menu
this.contextmenu = document.createElement('div');
this.contextmenu.classList.add('contextmenu');
this.contextmenu.style.display="none";
this.add_task=document.createElement('span');
this.add_sub=document.createElement('span');
this.add_task.appendChild(document.createTextNode("Add Task"));
this.add_sub.appendChild(document.createTextNode("Add Sub-Task"));
this.contextmenu.appendChild(this.add_task);
this.contextmenu.appendChild(this.add_sub);
this.$container.appendChild(this.contextmenu);
//Add Task
this.add_task_container=document.createElement('div');
this.add_task_container.classList.add('add-task');
this.add_task_formulaire=document.createElement('form');
this.add_task_title=document.createElement('input');
this.add_task_title.placeholder="Title...";
this.add_task_title.type="text";
this.add_task_start=document.createElement('input');
this.add_task_start.type="date";
this.add_task_end=document.createElement('input');
this.add_task_end.type="date";
this.add_task_completion=document.createElement('input');
this.add_task_completion.placeholder="Completion %";
this.add_task_completion.type="number";
this.add_task_completion.min="0";
this.add_task_completion.max="100";
this.add_task_people=document.createElement('input');
this.add_task_people.placeholder="Assigned to...";
this.add_task_people.type="text";
this.add_task_submit=document.createElement('input');
this.add_task_submit.value="ADD";
this.add_task_submit.type="submit";
this.add_task_close=document.createElement('input');
this.add_task_close.value="CLOSE";
this.add_task_close.type="reset";
this.add_task_formulaire.appendChild(this.add_task_title);
this.add_task_formulaire.appendChild(this.add_task_start);
this.add_task_formulaire.appendChild(this.add_task_end);
this.add_task_formulaire.appendChild(this.add_task_completion);
this.add_task_formulaire.appendChild(this.add_task_people);
this.add_task_formulaire.appendChild(this.add_task_submit);
this.add_task_formulaire.appendChild(this.add_task_close);
this.add_task_container.appendChild(this.add_task_formulaire);
this.$container.appendChild(this.add_task_container);
//Add Sub-Task
this.sub_task();
}
sub_task(){
this.add_sub_task_container=document.createElement('div');
this.add_sub_task_container.classList.add('add-task');
this.add_sub_task_formulaire=document.createElement('form');
this.add_sub_task_title=document.createElement('input');
this.add_sub_task_title.placeholder="Title...";
this.add_sub_task_title.type="text";
this.add_sub_task_start=document.createElement('input');
this.add_sub_task_start.type="date";
this.add_sub_task_end=document.createElement('input');
this.add_sub_task_end.type="date";
this.add_sub_task_completion=document.createElement('input');
this.add_sub_task_completion.placeholder="Completion %";
this.add_sub_task_completion.type="number";
this.add_sub_task_completion.min="0";
this.add_sub_task_completion.max="100";
this.add_sub_task_dependencies=document.createElement('select');
let mtasks= this.tasks;
let arr=mtasks.map(task =>{
return Object.assign({},{id:task.id,name:task.name});
});
arr.forEach(o => {
let task_option= document.createElement('option');
task_option.value=""+o.id;
task_option.appendChild(document.createTextNode(o.name));
this.add_sub_task_dependencies.appendChild(task_option);
});
this.add_sub_task_close=document.createElement('input');
this.add_sub_task_close.value="CLOSE";
this.add_sub_task_close.type="reset";
this.add_sub_task_submit=document.createElement('input');
this.add_sub_task_submit.value="ADD";
this.add_sub_task_submit.type="submit";
this.add_sub_task_formulaire.appendChild(this.add_sub_task_title);
this.add_sub_task_formulaire.appendChild(this.add_sub_task_start);
this.add_sub_task_formulaire.appendChild(this.add_sub_task_end);
this.add_sub_task_formulaire.appendChild(this.add_sub_task_completion);
this.add_sub_task_formulaire.appendChild(this.add_sub_task_dependencies);
this.add_sub_task_formulaire.appendChild(this.add_sub_task_submit);
this.add_sub_task_formulaire.appendChild(this.add_sub_task_close);
this.add_sub_task_container.appendChild(this.add_sub_task_formulaire);
this.$container.appendChild(this.add_sub_task_container);
}
setup_options(options) {
const default_options = {
header_height: 50,
column_width: 30,
step: 24,
view_modes: [
'Quarter Day',
'Half Day',
'Day',
'Week',
'Month',
'Year'
],
bar_height: 20,
bar_corner_radius: 3,
arrow_curve: 5,
padding: 18,
view_mode: 'Day',
date_format: 'YYYY-MM-DD',
popup_trigger: 'click',
custom_popup_html: null,
language: 'en',
sync_parent_end_with_children: true
};
this.options = Object.assign({}, default_options, options);
}
setup_tasks(tasks) {
// prepare tasks
this.tasks = tasks.map((task, i) => {
// convert to Date objects
task._start = date_utils.parse(task.start);
task._end = date_utils.parse(task.end);
// make task invalid if duration too large
if (date_utils.diff(task._end, task._start, 'year') > 10) {
task.end = null;
}
// cache index
task._index = i;
// invalid dates
if (!task.start && !task.end) {
const today = date_utils.today();
task._start = today;
task._end = date_utils.add(today, 2, 'day');
}
if (!task.start && task.end) {
task._start = date_utils.add(task._end, -2, 'day');
}
if (task.start && !task.end) {
task._end = date_utils.add(task._start, 2, 'day');
}
// if hours is not set, assume the last day is full day
// e.g: 2018-09-09 becomes 2018-09-09 23:59:59
const task_end_values = date_utils.get_date_values(task._end);
if (task_end_values.slice(3).every(d => d === 0)) {
task._end = date_utils.add(task._end, 24, 'hour');
}
// invalid flag
if (!task.start || !task.end) {
task.invalid = true;
}
// dependencies
if (typeof task.dependencies === 'string' || !task.dependencies) {
let deps = [];
if (task.dependencies) {
deps = task.dependencies
.split(',')
.map(d => d.trim())
.filter(d => d);
}
task.dependencies = deps;
}
// uids
if (!task.id) {
task.id = generate_id(task);
}
return task;
});
this.setup_dependencies();
}
setup_dependencies() {
this.dependency_map = {};
for (let t of this.tasks) {
for (let d of t.dependencies) {
this.dependency_map[d] = this.dependency_map[d] || [];
this.dependency_map[d].push(t.id);
}
}
}
update_task(task){
this.tasks[task._index]=task;
this.refresh(this.tasks);
}
refresh(tasks) {
this.setup_tasks(tasks);
this.synchronizing_date(tasks);
this.change_view_mode();
}
change_view_mode(mode = this.options.view_mode) {
this.update_view_scale(mode);
this.setup_dates();
this.render();
// fire viewmode_change event
this.trigger_event('view_change', [mode]);
}
update_view_scale(view_mode) {
this.options.view_mode = view_mode;
if (view_mode === 'Day') {
this.options.step = 24;
this.options.column_width = 38;
} else if (view_mode === 'Half Day') {
this.options.step = 24 / 2;
this.options.column_width = 38;
} else if (view_mode === 'Quarter Day') {
this.options.step = 24 / 4;
this.options.column_width = 38;
} else if (view_mode === 'Week') {
this.options.step = 24 * 7;
this.options.column_width = 140;
} else if (view_mode === 'Month') {
this.options.step = 24 * 30;
this.options.column_width = 120;
} else if (view_mode === 'Year') {
this.options.step = 24 * 365;
this.options.column_width = 120;
}
}
setup_dates() {
this.setup_gantt_dates();
this.setup_date_values();
}
setup_gantt_dates() {
this.gantt_start = this.gantt_end = null;
for (let task of this.tasks) {
// set global start and end date
if (!this.gantt_start || task._start < this.gantt_start) {
this.gantt_start = task._start;
}
if (!this.gantt_end || task._end > this.gantt_end) {
this.gantt_end = task._end;
}
}
this.gantt_start = date_utils.start_of(this.gantt_start, 'day');
this.gantt_end = date_utils.start_of(this.gantt_end, 'day');
// add date padding on both sides
if (this.view_is(['Quarter Day', 'Half Day'])) {
this.gantt_start = date_utils.add(this.gantt_start, -7, 'day');
this.gantt_end = date_utils.add(this.gantt_end, 7, 'day');
} else if (this.view_is('Month')) {
this.gantt_start = date_utils.start_of(this.gantt_start, 'year');
this.gantt_end = date_utils.add(this.gantt_end, 1, 'year');
} else if (this.view_is('Year')) {
this.gantt_start = date_utils.add(this.gantt_start, -2, 'year');
this.gantt_end = date_utils.add(this.gantt_end, 2, 'year');
} else {
this.gantt_start = date_utils.add(this.gantt_start, -1, 'month');
this.gantt_end = date_utils.add(this.gantt_end, 1, 'month');
}
}
setup_date_values() {
this.dates = [];
let cur_date = null;
while (cur_date === null || cur_date < this.gantt_end) {
if (!cur_date) {
cur_date = date_utils.clone(this.gantt_start);
} else {
if (this.view_is('Year')) {
cur_date = date_utils.add(cur_date, 1, 'year');
} else if (this.view_is('Month')) {
cur_date = date_utils.add(cur_date, 1, 'month');
} else {
cur_date = date_utils.add(
cur_date,
this.options.step,
'hour'
);
}
}
this.dates.push(cur_date);
}
}
clear_menu(){
if(this.contextmenu.style.display==="block")
this.contextmenu.style.display="none";
else if(this.add_task_container.style.display==="block")
this.add_task_container.style.display="none";
else if(this.add_sub_task_container.style.display==="block")
this.add_sub_task_container.style.display="none";
}
//this the event listener
bind_events() {
let isOpen=false;
this.bind_grid_click();
this.bind_bar_events();
let y=0,x=0;
/*this.$container.addEventListener('contextmenu',ev=>{
ev.preventDefault();
if(!isOpen)
this.clear_menu();
if(this.tasks.length)
this.add_sub.classList.remove("disabled");
else
this.add_sub.classList.add("disabled");
this.contextmenu.style.display="block";
x=ev.offsetX;y=ev.offsetY;
this.contextmenu.style.top=ev.offsetY+'px';
this.contextmenu.style.left=ev.offsetX+'px';
});
this.$container.addEventListener('click',ev=>{
this.contextmenu.style.display="none";
});
this.add_task.addEventListener('click',ev=>{
this.add_task_container.style.display="block";
this.add_task_container.style.top=y+'px';
this.add_task_container.style.left=x+'px';
});
this.add_sub.addEventListener('click',ev=>{
this.add_sub_task_container.style.display="block";
this.add_sub_task_container.style.top=y+'px';
this.add_sub_task_container.style.left=x+'px';
});
this.add_task_formulaire.addEventListener('submit',ev=>{
ev.preventDefault();
let task={
start: '',
end: '',
name: '',
id: 'Task '+this.tasks.length,
progress: 0,
people:''
}
if(!this.add_task_start.value)
this.add_task_start.classList.add("wrong");
if(!this.add_task_end.value)
this.add_task_end.classList.add("wrong");
if(!this.add_task_title.value)
this.add_task_title.classList.add("wrong");
if(this.add_task_title.value &&
this.add_task_start.value &&
this.add_task_end.value){
task.name=this.add_task_title.value;
task.end=''+this.add_task_end.value;
task.start=''+this.add_task_start.value;
task.progress=this.add_task_completion.value;
task.people=this.add_task_people.value.split(",");
this.tasks.push(task);
this.update_sub_task(task);
this.refresh(this.tasks);
this.add_task_title.classList.remove("wrong");
this.add_task_title.classList.remove("wrong");
this.add_task_title.classList.remove("wrong");
this.add_task_title.value=null;
this.add_task_end.value=null;
this.add_task_start.value=null;
this.add_task_completion.value=null;
this.add_task_container.style.display="none";
}
}
);
this.add_sub_task_formulaire.addEventListener('reset',ev=>{
this.add_sub_task_container.style.display="none";
});
this.add_task_formulaire.addEventListener('reset',ev=>{
this.add_task_container.style.display="none";
});
this.add_sub_task_formulaire.addEventListener('submit',ev=>{
ev.preventDefault();
let task={
start: '',
end: '',
name: '',
id: 'Task '+this.tasks.length,
progress: 0,
dependencies:''
}
if(!this.add_sub_task_start.value)
this.add_sub_task_start.classList.add("wrong");
if(!this.add_task_end.value)
this.add_sub_task_end.classList.add("wrong");
if(!this.add_sub_task_title.value)
this.add_sub_task_title.classList.add("wrong");
if(this.add_sub_task_title.value &&
this.add_sub_task_start.value &&
this.add_sub_task_end.value){
task.name=this.add_sub_task_title.value;
task.end=''+this.add_sub_task_end.value;
task.start=''+this.add_sub_task_start.value;
task.progress=this.add_sub_task_completion.value;
task.dependencies=''+this.add_sub_task_dependencies.value;
this.tasks.push(task);
this.update_sub_task(task);
this.refresh(this.tasks);
this.add_sub_task_title.classList.remove("wrong");
this.add_sub_task_end.classList.remove("wrong");
this.add_sub_task_start.classList.remove("wrong");
this.add_sub_task_title.value=null;
this.add_sub_task_end.value=null;
this.add_sub_task_start.value=null;
this.add_sub_task_completion.value=null;
this.add_sub_task_container.style.display="none";
}
});*/
}
update_sub_task(task){
let task_option= document.createElement('option');
task_option.value=""+task.id;
task_option.appendChild(document.createTextNode(task.name));
this.add_sub_task_dependencies.appendChild(task_option);
}
render() {
this.clear();
this.setup_layers();
this.make_grid();
this.make_dates();
this.make_bars();
this.make_arrows();
this.map_arrows_on_bars();
this.set_width();
this.set_scroll_position();
}
setup_layers() {
this.layers = {};
const layers = ['grid', 'date', 'arrow', 'progress', 'bar', 'details'];
// make group layers
for (let layer of layers) {
this.layers[layer] = createSVG('g', {
class: layer,
append_to: this.$svg
});
}
}
make_grid() {
this.make_grid_background();
this.make_grid_rows();
this.make_grid_header();
this.make_grid_ticks();
this.make_grid_highlights();
}
make_grid_background() {
const grid_width = this.dates.length * this.options.column_width;
const grid_height =
this.options.header_height +
this.options.padding +
(this.options.bar_height + this.options.padding) *
this.tasks.length;
createSVG('rect', {
x: 0,
y: 0,
width: grid_width,
height: grid_height,
class: 'grid-background',
append_to: this.layers.grid
});
$.attr(this.$svg, {
height: grid_height + this.options.padding,
width: '100%'
});
}
make_grid_rows() {
const rows_layer = createSVG('g', { append_to: this.layers.grid });
const lines_layer = createSVG('g', { append_to: this.layers.grid });
const row_width = this.dates.length * this.options.column_width;
const row_height = this.options.bar_height + this.options.padding;
let row_y = this.options.header_height + this.options.padding / 2;
for (let task of this.tasks) {
createSVG('rect', {
x: 0,
y: row_y,
width: row_width,
height: row_height,
class: 'grid-row',
append_to: rows_layer
});
createSVG('line', {
x1: 0,
y1: row_y + row_height,
x2: row_width,
y2: row_y + row_height,
class: 'row-line',
append_to: lines_layer
});
row_y += this.options.bar_height + this.options.padding;
}
}
make_grid_header() {
const header_width = this.dates.length * this.options.column_width;
const header_height = this.options.header_height + 10;
createSVG('rect', {
x: 0,
y: 0,
width: header_width,
height: header_height,
class: 'grid-header',
append_to: this.layers.grid
});
}
make_grid_ticks() {
let tick_x = 0;
let tick_y = this.options.header_height + this.options.padding / 2;
let tick_height =
(this.options.bar_height + this.options.padding) *
this.tasks.length;
for (let date of this.dates) {
let tick_class = 'tick';
// thick tick for monday
if (this.view_is('Day') && date.getDate() === 1) {
tick_class += ' thick';
}
// thick tick for first week
if (
this.view_is('Week') &&
date.getDate() >= 1 &&
date.getDate() < 8
) {
tick_class += ' thick';
}
// thick ticks for quarters
if (this.view_is('Month') && (date.getMonth() + 1) % 3 === 0) {
tick_class += ' thick';
}
createSVG('path', {
d: `M ${tick_x} ${tick_y} v ${tick_height}`,
class: tick_class,
append_to: this.layers.grid
});
if (this.view_is('Month')) {
tick_x +=
date_utils.get_days_in_month(date) *
this.options.column_width /
30;
} else {
tick_x += this.options.column_width;
}
}
}
make_grid_highlights() {
// highlight today's date
if (this.view_is('Day')) {
const x =
date_utils.diff(date_utils.today(), this.gantt_start, 'hour') /
this.options.step *
this.options.column_width;
const y = 0;
const width = this.options.column_width;
const height =
(this.options.bar_height + this.options.padding) *
this.tasks.length +
this.options.header_height +
this.options.padding / 2;
createSVG('rect', {
x,
y,
width,
height,
class: 'today-highlight',
append_to: this.layers.grid
});
}
}
make_dates() {
this.get_dates_to_draw().forEach((date, i) => {
const col_x = i * this.options.column_width;
// Rysujemy górny tekst
if (date.upper_text) {
const original_date = this.dates[i];
const today = date_utils.today();
const isToday =
original_date.getDate() === today.getDate() &&
original_date.getMonth() === today.getMonth() &&
original_date.getFullYear() === today.getFullYear();
console.log(date.upper_text + ' | ' + isToday );
const $upper_text = createSVG('text', {
x: date.upper_x + this.options.column_width,
y: date.upper_y,
innerHTML: date.upper_text,
class: 'upper-text' + (isToday ? ' today-date' : ''),
append_to: this.layers.date
});
// Ramka na końcu kolumny
createSVG('line', {
x1: col_x,
y1: 0,
x2: col_x,
y2: this.options.header_height,
class: 'upper-date-separator',
append_to: this.layers.date
});
}
// Rysujemy dolny tekst
createSVG('text', {
x: date.lower_x,
y: date.lower_y,
innerHTML: date.lower_text == '00' ? '12' : '00',
class: 'lower-text',
append_to: this.layers.date
});
});
}
get_dates_to_draw() {
let last_date = null;
const dates = this.dates.map((date, i) => {
const d = this.get_date_info(date, last_date, i);
last_date = date;
return d;
});
return dates;
}
get_date_info(date, last_date, i) {
if (!last_date) {
last_date = date_utils.add(date, 1, 'year');
}
const date_text = {
'Quarter Day_lower': date_utils.format(
date,
'HH',
this.options.language
),
'Half Day_lower': date_utils.format(
date,
'HH',
this.options.language
),
Day_lower:
date.getDate() !== last_date.getDate()
? date_utils.format(date, 'D', this.options.language)
: '',
Week_lower:
date.getMonth() !== last_date.getMonth()
? date_utils.format(date, 'D MMM', this.options.language)
: date_utils.format(date, 'D', this.options.language),
Month_lower: date_utils.format(date, 'MMMM', this.options.language),
Year_lower: date_utils.format(date, 'YYYY', this.options.language),
'Quarter Day_upper':
date.getDate() !== last_date.getDate()
? date_utils.format(date, 'D MMM', this.options.language)
: '',
'Half Day_upper':
date.getDate() !== last_date.getDate()
? `${date_utils.format(date, 'D', this.options.language)}, ${['nd', 'pn', 'wt', 'śr', 'cz', 'pt', 'sb'][date.getDay()]}`
: '',
Day_upper:
date.getMonth() !== last_date.getMonth()
? date_utils.format(date, 'MMMM', this.options.language)
: '',
Week_upper:
date.getMonth() !== last_date.getMonth()
? date_utils.format(date, 'MMMM', this.options.language)
: '',
Month_upper:
date.getFullYear() !== last_date.getFullYear()
? date_utils.format(date, 'YYYY', this.options.language)
: '',
Year_upper:
date.getFullYear() !== last_date.getFullYear()
? date_utils.format(date, 'YYYY', this.options.language)
: ''
};
const base_pos = {
x: i * this.options.column_width,
lower_y: this.options.header_height,
upper_y: this.options.header_height - 25
};
const x_pos = {
'Quarter Day_lower': this.options.column_width * 4 / 2,
'Quarter Day_upper': 0,
'Half Day_lower': this.options.column_width * 2 / 2,
'Half Day_upper': 0,
Day_lower: this.options.column_width / 2,
Day_upper: this.options.column_width * 30 / 2,
Week_lower: 0,
Week_upper: this.options.column_width * 4 / 2,
Month_lower: this.options.column_width / 2,
Month_upper: this.options.column_width * 12 / 2,
Year_lower: this.options.column_width / 2,
Year_upper: this.options.column_width * 30 / 2
};
return {
upper_text: date_text[`${this.options.view_mode}_upper`],
lower_text: date_text[`${this.options.view_mode}_lower`],
upper_x: base_pos.x + x_pos[`${this.options.view_mode}_upper`],
upper_y: base_pos.upper_y,
lower_x: base_pos.x + x_pos[`${this.options.view_mode}_lower`],
lower_y: base_pos.lower_y
};
}
make_bars() {
this.bars = this.tasks.map(task => {
const bar = new Bar(this, task);
this.layers.bar.appendChild(bar.group);
return bar;
});
}
make_arrows() {
this.arrows = [];
for (let task of this.tasks) {
let arrows = [];
arrows = task.dependencies
.map(task_id => {
const dependency = this.get_task(task_id);
if (!dependency) return;
const arrow = new Arrow(
this,
this.bars[dependency._index], // from_task
this.bars[task._index] // to_task
);
this.layers.arrow.appendChild(arrow.element);
return arrow;
})
.filter(Boolean); // filter falsy values
this.arrows = this.arrows.concat(arrows);
}
}
map_arrows_on_bars() {
for (let bar of this.bars) {
bar.arrows = this.arrows.filter(arrow => {
return (
arrow.from_task.task.id === bar.task.id ||
arrow.to_task.task.id === bar.task.id
);
});
}
}
set_width() {
const cur_width = this.$svg.getBoundingClientRect().width;
const actual_width = this.$svg
.querySelector('.grid .grid-row')
.getAttribute('width');
if (cur_width < actual_width) {
this.$svg.setAttribute('width', actual_width);
}
}
set_scroll_position() {
const parent_element = this.$svg.parentElement;
if (!parent_element) return;
const hours_before_first_task = date_utils.diff(
this.get_oldest_starting_date(),
this.gantt_start,
'hour'
);
const scroll_pos =
hours_before_first_task /
this.options.step *
this.options.column_width -
this.options.column_width;
parent_element.scrollLeft = scroll_pos;
}
bind_grid_click() {
this.hide_popup();
$.on(
this.$svg,
this.options.popup_trigger,
'.grid-row, .grid-header',
() => {
this.unselect_all();
this.hide_popup();
}
);
}
bind_bar_events() {
let is_dragging = false;
let x_on_start = 0;
let y_on_start = 0;
let is_resizing_left = false;
let is_resizing_right = false;
let parent_bar_id = null;
let bars = []; // instanceof Bar
this.bar_being_dragged = null;
function action_in_progress() {
return is_dragging || is_resizing_left || is_resizing_right;
}
$.on(this.$svg, 'mousedown', '.bar-wrapper, .handle', (e, element) => {
const bar_wrapper = $.closest('.bar-wrapper', element);
this.hide_popup();
if (element.classList.contains('left')) {
is_resizing_left = true;
} else if (element.classList.contains('right')) {
is_resizing_right = true;
} else if (element.classList.contains('bar-wrapper')) {
is_dragging = true;
}
bar_wrapper.classList.add('active');
x_on_start = e.offsetX;
y_on_start = e.offsetY;
parent_bar_id = bar_wrapper.getAttribute('data-id');
const ids = [
parent_bar_id,
...this.get_all_dependent_tasks(parent_bar_id)
];
bars = ids.map(id => this.get_bar(id));
this.bar_being_dragged = parent_bar_id;
bars.forEach(bar => {
const $bar = bar.$bar;
$bar.ox = $bar.getX();
$bar.oy = $bar.getY();
$bar.owidth = $bar.getWidth();
$bar.finaldx = 0;
});
});
$.on(this.$svg, 'mousemove', e => {
if (!action_in_progress()) return;
const dx = e.offsetX - x_on_start;
const dy = e.offsetY - y_on_start;
bars.forEach(bar => {
const $bar = bar.$bar;
$bar.finaldx = this.get_snap_position(dx);
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
});
}
} else if (is_resizing_right) {
if (parent_bar_id === bar.task.id) {
bar.update_bar_position({
width: $bar.owidth + $bar.finaldx
});
}
} else if (is_dragging) {
bar.update_bar_position({ x: $bar.ox + $bar.finaldx });
}
});
});
document.addEventListener('mouseup', e => {
if (is_dragging || is_resizing_left || is_resizing_right) {
bars.forEach(bar => bar.group.classList.remove('active'));
}
is_dragging = false;
is_resizing_left = false;
is_resizing_right = false;
//
});
$.on(this.$svg, 'mouseup', e => {
this.bar_being_dragged = null;
bars.forEach(bar => {
const $bar = bar.$bar;
if (!$bar.finaldx) return;
bar.date_changed();
bar.set_action_completed();
});
});
this.bind_bar_progress();
}
getColor(progress){
let colorFilled="green";
if(progress <= 50)
colorFilled="red";
if(progress > 50 && progress <=80)
colorFilled="orange";
if(progress > 80 && progress <=90)
colorFilled="yellow";
return colorFilled;
}
bind_bar_progress() {
let x_on_start = 0;
let y_on_start = 0;
let is_resizing = null;
let bar = null;
let $bar_progress = null;
let $bar = null;
$.on(this.$svg, 'mousedown', '.handle.progress', (e, handle) => {
is_resizing = true;
x_on_start = e.offsetX;
y_on_start = e.offsetY;
const $bar_wrapper = $.closest('.bar-wrapper', handle);
const id = $bar_wrapper.getAttribute('data-id');
bar = this.get_bar(id);
$bar_progress = bar.$bar_progress;
$bar = bar.$bar;
$bar_progress.finaldx = 0;
$bar_progress.owidth = $bar_progress.getWidth();
$bar_progress.min_dx = -$bar_progress.getWidth();
$bar_progress.max_dx = $bar.getWidth() - $bar_progress.getWidth();
});
$.on(this.$svg, 'mousemove', e => {
if (!is_resizing) return;
let dx = e.offsetX - x_on_start;
let dy = e.offsetY - y_on_start;
if (dx > $bar_progress.max_dx) {
dx = $bar_progress.max_dx;
}
if (dx < $bar_progress.min_dx) {
dx = $bar_progress.min_dx;
}
const $handle = bar.$handle_progress;
$.attr($bar_progress, 'width', $bar_progress.owidth + dx);
$.attr($handle, 'points', bar.get_progress_polygon_points());
$bar_progress.finaldx = dx;
});
$.on(this.$svg, 'mouseup', () => {
is_resizing = false;
if (!($bar_progress && $bar_progress.finaldx)) return;
bar.progress_changed();
bar.set_action_completed();
//Here when
bar.$bar_progress.classList=""+this.getColor(bar.task.progress);
});
}
get_all_dependent_tasks(task_id) {
let out = [];
let to_process = [task_id];
while (to_process.length) {
const deps = to_process.reduce((acc, curr) => {
acc = acc.concat(this.dependency_map[curr]);
return acc;
}, []);
out = out.concat(deps);
to_process = deps.filter(d => !to_process.includes(d));
}
return out.filter(Boolean);
}
get_snap_position(dx) {
let odx = dx,
rem,
position;
if (this.view_is('Week')) {
rem = dx % (this.options.column_width / 7);
position =
odx -
rem +
(rem < this.options.column_width / 14
? 0
: this.options.column_width / 7);
} else if (this.view_is('Month')) {
rem = dx % (this.options.column_width / 30);
position =
odx -
rem +
(rem < this.options.column_width / 60
? 0
: this.options.column_width / 30);
} else {
rem = dx % this.options.column_width;
position =
odx -
rem +
(rem < this.options.column_width / 2
? 0
: this.options.column_width);
}
return position;
}
unselect_all() {
[...this.$svg.querySelectorAll('.bar-wrapper')].forEach(el => {
el.classList.remove('active');
});
}
view_is(modes) {
if (typeof modes === 'string') {
return this.options.view_mode === modes;
}
if (Array.isArray(modes)) {
return modes.some(mode => this.options.view_mode === mode);
}
return false;
}
get_task(id) {
return this.tasks.find(task => {
return task.id === id;
});
}
get_bar(id) {
return this.bars.find(bar => {
return bar.task.id === id;
});
}
show_popup(options) {
if (!this.popup) {
this.popup = new Popup(
this.popup_wrapper,
this,
this.options.custom_popup_html
);
}
this.popup.show(options);
}
hide_popup() {
this.popup && this.popup.hide();
}
trigger_event(event, args) {
if (this.options['on_' + event]) {
this.options['on_' + event].apply(null, args);
}
}
/**
* Gets the oldest starting date from the list of tasks
*
* @returns Date
* @memberof Gantt
*/
get_oldest_starting_date() {
return this.tasks
.map(task => task._start)
.reduce(
(prev_date, cur_date) =>
cur_date <= prev_date ? cur_date : prev_date
);
}
/**
* Clear all elements from the parent svg element
*
* @memberof Gantt
*/
clear() {
this.$svg.innerHTML = '';
}
}
function generate_id(task) {
return (
task.name +
'_' +
Math.random()
.toString(36)
.slice(2, 12)
);
}
return Gantt;
}());