gantt/src/Bar.js
2016-12-23 12:42:09 +05:30

483 lines
12 KiB
JavaScript

/* global Snap */
/*
Class: Bar
Opts:
canvas [reqd]
task [reqd]
column_width [reqd]
x
y
*/
export default function Bar(gt, task) {
const self = {};
function init() {
set_defaults();
prepare();
draw();
bind();
}
function set_defaults() {
self.action_completed = false;
self.task = task;
}
function prepare() {
prepare_values();
prepare_plugins();
}
function prepare_values() {
self.invalid = self.task.invalid;
self.height = gt.config.bar.height;
self.x = compute_x();
self.y = compute_y();
self.corner_radius = 3;
self.duration = (self.task._end.diff(self.task._start, 'hours') + 24) / gt.config.step;
self.width = gt.config.column_width * self.duration;
self.progress_width = gt.config.column_width * self.duration * (self.task.progress / 100) || 0;
self.group = gt.canvas.group().addClass('bar-wrapper');
self.bar_group = gt.canvas.group().addClass('bar-group').appendTo(self.group);
self.handle_group = gt.canvas.group().addClass('handle-group').appendTo(self.group);
}
function prepare_plugins() {
Snap.plugin(function (Snap, Element, Paper, global, Fragment) {
Element.prototype.getX = function () {
return +this.attr('x');
};
Element.prototype.getY = function () {
return +this.attr('y');
};
Element.prototype.getWidth = function () {
return +this.attr('width');
};
Element.prototype.getHeight = function () {
return +this.attr('height');
};
Element.prototype.getEndX = function () {
return this.getX() + this.getWidth();
};
});
}
function draw() {
draw_bar();
draw_progress_bar();
draw_label();
draw_resize_handles();
}
function draw_bar() {
self.$bar = gt.canvas.rect(self.x, self.y,
self.width, self.height,
self.corner_radius, self.corner_radius)
.addClass('bar')
.appendTo(self.bar_group);
if (self.invalid) {
self.$bar.addClass('bar-invalid');
}
}
function draw_progress_bar() {
if (self.invalid) return;
self.$bar_progress = gt.canvas.rect(self.x, self.y,
self.progress_width, self.height,
self.corner_radius, self.corner_radius)
.addClass('bar-progress')
.appendTo(self.bar_group);
}
function draw_label() {
gt.canvas.text(self.x + self.width / 2,
self.y + self.height / 2,
self.task.name)
.addClass('bar-label')
.appendTo(self.bar_group);
update_label_position();
}
function draw_resize_handles() {
if (self.invalid) return;
const bar = self.$bar,
bar_progress = self.$bar_progress,
handle_width = 8;
gt.canvas.rect(bar.getX() + bar.getWidth() - 9, bar.getY() + 1,
handle_width, self.height - 2, self.corner_radius, self.corner_radius)
.addClass('handle right')
.appendTo(self.handle_group);
gt.canvas.rect(bar.getX() + 1, bar.getY() + 1,
handle_width, self.height - 2, self.corner_radius, self.corner_radius)
.addClass('handle left')
.appendTo(self.handle_group);
if (self.task.progress && self.task.progress < 100) {
gt.canvas.polygon(
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
)
.addClass('handle progress')
.appendTo(self.handle_group);
}
}
function bind() {
if (self.invalid) return;
show_details();
bind_resize();
bind_drag();
bind_resize_progress();
setup_click_event();
}
function show_details() {
const popover_group = gt.element_groups.details;
let details_box = popover_group.select('.details-wrapper');
if (!details_box) {
details_box = gt.canvas.group()
.addClass('details-wrapper')
.appendTo(popover_group);
gt.canvas.rect(0, 0, 0, 110, 2, 2)
.addClass('details-container')
.appendTo(details_box);
gt.canvas.text(0, 0, '')
.attr({ dx: 10, dy: 30 })
.addClass('details-heading')
.appendTo(details_box);
gt.canvas.text(0, 0, '')
.attr({ dx: 10, dy: 65 })
.addClass('details-body')
.appendTo(details_box);
gt.canvas.text(0, 0, '')
.attr({ dx: 10, dy: 90 })
.addClass('details-body')
.appendTo(details_box);
}
self.group.mouseover((e, x, y) => {
popover_group.removeClass('hide');
const pos = get_details_position();
details_box.transform(`t${pos.x},${pos.y}`);
const start_date = self.task._start.format('MMM D'),
end_date = self.task._end.format('MMM D'),
heading = `${self.task.name}: ${start_date} - ${end_date}`;
const $heading = popover_group
.select('.details-heading')
.attr('text', heading);
const bbox = $heading.getBBox();
details_box.select('.details-container')
.attr({ width: bbox.width + 20 });
const duration = self.task._end.diff(self.task._start, 'days'),
body1 = `Duration: ${duration} days`,
body2 = self.task.progress ?
`Progress: ${self.task.progress}` : '';
const $body = popover_group.selectAll('.details-body');
$body[0].attr('text', body1);
$body[1].attr('text', body2);
});
self.group.mouseout(() => {
setTimeout(() => popover_group.addClass('hide'), 500);
});
}
function get_details_position() {
return {
x: self.$bar.getEndX() + 2,
y: self.$bar.getY() - 10
};
}
function bind_resize() {
const { left, right } = get_handles();
left.drag(onmove_left, onstart, onstop_left);
right.drag(onmove_right, onstart, onstop_right);
function onmove_right(dx, dy) {
onmove_handle_right(dx, dy);
}
function onstop_right() {
onstop_handle_right();
}
function onmove_left(dx, dy) {
onmove_handle_left(dx, dy);
}
function onstop_left() {
onstop_handle_left();
}
}
function get_handles() {
return {
left: self.handle_group.select('.handle.left'),
right: self.handle_group.select('.handle.right')
};
}
function bind_drag() {
self.bar_group.drag(onmove, onstart, onstop);
}
function bind_resize_progress() {
const bar = self.$bar,
bar_progress = self.$bar_progress,
handle = self.group.select('.handle.progress');
handle && handle.drag(on_move, on_start, on_stop);
function on_move(dx, dy) {
if (dx > bar_progress.max_dx) {
dx = bar_progress.max_dx;
}
if (dx < bar_progress.min_dx) {
dx = bar_progress.min_dx;
}
bar_progress.attr('width', bar_progress.owidth + dx);
handle.transform(`t{dx},0`);
bar_progress.finaldx = dx;
}
function on_stop() {
if (!bar_progress.finaldx) return;
progress_changed();
set_action_completed();
}
function on_start() {
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();
}
}
function onstart() {
const bar = self.$bar;
bar.ox = bar.getX();
bar.oy = bar.getY();
bar.owidth = bar.getWidth();
bar.finaldx = 0;
run_method_for_dependencies('onstart');
}
self.onstart = onstart;
function onmove(dx, dy) {
const bar = self.$bar;
bar.finaldx = get_snap_position(dx);
update_bar_position(bar.ox + bar.finaldx);
run_method_for_dependencies('onmove', [dx, dy]);
}
self.onmove = onmove;
function onstop() {
const bar = self.$bar;
if (!bar.finaldx) return;
date_changed();
set_action_completed();
run_method_for_dependencies('onstop');
}
self.onstop = onstop;
function onmove_handle_left(dx, dy) {
const bar = self.$bar;
bar.finaldx = get_snap_position(dx);
update_bar_position(bar.ox + bar.finaldx, bar.owidth - bar.finaldx);
run_method_for_dependencies('onmove_handle_left', [dx, dy]);
}
self.onmove_handle_left = onmove_handle_left;
function onstop_handle_left() {
const bar = self.$bar;
if (bar.finaldx) date_changed();
set_action_completed();
run_method_for_dependencies('onstop_handle_left');
}
self.onstop_handle_left = onstop_handle_left;
function run_method_for_dependencies(fn, args) {
const dm = gt.dependency_map;
if (dm[self.task.id]) {
for (let deptask of dm[self.task.id]) {
const dt = gt.get_bar(deptask);
dt[fn].apply(dt, args);
}
}
}
function onmove_handle_right(dx, dy) {
const bar = self.$bar;
bar.finaldx = get_snap_position(dx);
update_bar_position(null, bar.owidth + bar.finaldx);
}
function onstop_handle_right() {
const bar = self.$bar;
if (bar.finaldx) date_changed();
set_action_completed();
}
function update_bar_position(x, width) {
const bar = self.$bar;
if (x) update_attr(bar, 'x', x);
if (width) update_attr(bar, 'width', width);
update_label_position();
update_handle_position();
update_progressbar_position();
update_arrow_position();
update_details_position();
}
function setup_click_event() {
self.group.click(function () {
if (self.action_completed) {
// just finished a move action, wait for a few seconds
return;
}
if (self.group.hasClass('active')) {
gt.trigger_event('click', [self.task]);
}
unselect_all();
self.group.toggleClass('active');
});
}
function date_changed() {
gt.trigger_event('date_change',
[self.task, compute_start_date(), compute_end_date()]);
}
function progress_changed() {
gt.trigger_event('progress_change',
[self.task, compute_progress()]);
}
function set_action_completed() {
self.action_completed = true;
setTimeout(() => self.action_completed = false, 2000);
}
function compute_start_date() {
const bar = self.$bar,
shift = (bar.getX() - compute_x()) / gt.config.column_width,
new_start_date = self.task._start.clone().add(gt.config.step * shift, 'hours');
return new_start_date;
}
function compute_end_date() {
const bar = self.$bar,
og_x = compute_x() + self.duration * gt.config.column_width,
final_x = bar.getEndX(),
shift = (final_x - og_x) / gt.config.column_width,
new_end_date = self.task._end.clone().add(gt.config.step * shift, 'hours');
return new_end_date;
}
function compute_progress() {
return self.$bar_progress.getWidth() / self.$bar.getWidth() * 100;
}
function compute_x() {
let x = self.task._start.diff(gt.gantt_start, 'hours') /
gt.config.step * gt.config.column_width;
if (gt.view_is('Month')) {
x = self.task._start.diff(gt.config.start, 'days') *
gt.config.column_width / 30;
}
return x;
}
function compute_y() {
return gt.config.header_height + gt.config.padding +
self.task._index * (self.height + gt.config.padding);
}
function get_snap_position(dx) {
let odx = dx, rem, position;
if (gt.view_is('Week')) {
rem = dx % (gt.config.column_width / 7);
position = odx - rem +
((rem < gt.config.column_width / 14) ? 0 : gt.config.column_width / 7);
} else if (gt.view_is('Month')) {
rem = dx % (gt.config.column_width / 30);
position = odx - rem +
((rem < gt.config.column_width / 60) ? 0 : gt.config.column_width / 30);
} else {
rem = dx % gt.config.column_width;
position = odx - rem +
((rem < gt.config.column_width / 2) ? 0 : gt.config.column_width);
}
return position;
}
function update_attr(element, attr, value) {
value = +value;
if (!isNaN(value)) {
element.attr(attr, value);
}
return element;
}
function update_progressbar_position() {
self.$bar_progress.attr('x', self.$bar.getX());
self.$bar_progress.attr('width', self.$bar.getWidth() * (self.task.progress / 100));
}
function update_label_position() {
const bar = self.$bar,
label = self.group.select('.bar-label');
if (label.getBBox().width > bar.getWidth()) {
label.addClass('big').attr('x', bar.getX() + bar.getWidth() + 5);
} else {
label.removeClass('big').attr('x', bar.getX() + bar.getWidth() / 2);
}
}
function update_handle_position() {
const bar = self.$bar;
self.handle_group.select('.handle.left').attr({
'x': bar.getX() + 1
});
self.handle_group.select('.handle.right').attr({
'x': bar.getEndX() - 9
});
}
function update_arrow_position() {
for (let arrow of self.arrows) {
arrow.update();
}
}
function update_details_position() {
const details_box = gt.element_groups.details.select('.details-wrapper');
const pos = get_details_position();
details_box && details_box.transform(`t${pos.x},${pos.y}`);
}
function unselect_all() {
gt.canvas.selectAll('.bar-wrapper').forEach(function (el) {
el.removeClass('active');
});
}
init();
return self;
}