diff --git a/frappe/public/css/gantt.css b/frappe/public/css/gantt.css
index 7b4c87c08b..0163f3c996 100644
--- a/frappe/public/css/gantt.css
+++ b/frappe/public/css/gantt.css
@@ -40,6 +40,15 @@
.gantt .bar-progress {
fill: #a3a3ff;
}
+.gantt .bar-invalid {
+ fill: transparent;
+ stroke: #8D99A6;
+ stroke-width: 1;
+ stroke-dasharray: 5;
+}
+.gantt .bar-invalid ~ .bar-label {
+ fill: #555;
+}
.gantt .bar-label {
fill: #fff;
dominant-baseline: central;
diff --git a/frappe/public/js/frappe/list/doclistview.js b/frappe/public/js/frappe/list/doclistview.js
index 8a2940cdec..3662994560 100644
--- a/frappe/public/js/frappe/list/doclistview.js
+++ b/frappe/public/js/frappe/list/doclistview.js
@@ -437,13 +437,25 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
var gantt_area = $('')
.appendTo(this.wrapper.find('.result-list').css("overflow", "scroll"));
var id = frappe.dom.set_unique_id(gantt_area);
+
var me = this;
var field_map = frappe.views.calendar[this.doctype].field_map;
-
- var view_modes;
+ var tasks = values.map(function(item) {
+ return {
+ start: item[field_map.start],
+ end: item[field_map.end],
+ name: item[field_map.title],
+ id: item[field_map.id],
+ doctype: me.doctype,
+ progress: item.progress,
+ dependent: item.depends_on_tasks || ""
+ };
+ });
frappe.require(["assets/frappe/js/lib/snap.svg-min.js", "assets/frappe/css/gantt.css"], function() {
me.gantt = new Gantt({
parent_selector: '#' + id,
+ tasks: tasks,
+ date_format: "YYYY-MM-DD",
bar: {
height: 20
},
@@ -468,21 +480,9 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
}
}
});
-
- view_modes = me.gantt.opts.valid_view_modes || [];
- values.forEach(function(item) {
- me.gantt.add_task({
- start: item[field_map.start],
- end: item[field_map.end],
- name: item[field_map.title],
- id: item[field_map.id],
- doctype: me.doctype,
- progress: item.progress,
- dependent: item.depends_on_tasks || ""
- });
- })
me.gantt.render();
+ var view_modes = me.gantt.get_view_modes() || [];
var dropdown = "
" +
"
" +
""+__('Day')+"" +
diff --git a/frappe/public/js/frappe/views/gantt.js b/frappe/public/js/frappe/views/gantt.js
index d86e3676a5..8eb940808a 100755
--- a/frappe/public/js/frappe/views/gantt.js
+++ b/frappe/public/js/frappe/views/gantt.js
@@ -15,16 +15,13 @@ var Gantt = Class.extend({
init: function(opts) {
this.opts = opts;
this.events = this.opts.events;
- this.tasks = [];
- this._bars = [];
- this._arrows = [];
this.set_defaults();
- this.groups = {};
- this.make();
+ this.prepare();
+ this.render();
},
set_defaults: function() {
var defaults = {
- label_width: 40,
+ label_width: 38,
header_height: 50,
column_width: 30,
step: 24,
@@ -43,27 +40,48 @@ var Gantt = Class.extend({
},
view_mode: 'Day',
padding: 18,
- date_format: 'YYYY-MM-DD'
+ date_format: 'DD-MM-YYYY'
};
for(var key in defaults) {
if(defaults.hasOwnProperty(key)) {
if(!this.opts[key]) this.opts[key] = defaults[key];
}
}
- },
- make: function() {
- this.canvas = Snap(this.opts.parent_selector);
- this.canvas.addClass("gantt");
- this.prepare_filters();
+
+ this._bars = [];
+ this._arrows = [];
+ this.groups = {};
+
+ //prepare tasks
+ var me = this;
+ this.tasks = this.opts.tasks.map(function(task, i) {
+ // momentify
+ task._start = moment(task.start, me.opts.date_format);
+ task._end = moment(task.end, me.opts.date_format);
+ //index
+ task._index = i;
+ //invalid dates
+ if(!task.start || !task.end) {
+ task._start = moment().startOf('day');
+ task._end = moment().startOf('day').add(2, 'days');
+ task.invalid = true;
+ }
+ return task;
+ });
+ //default view mode
this.set_scale(this.opts.view_mode);
},
+ prepare: function() {
+ //TODO: check for valid dates
+ this.start = this.end = undefined;
+ this.prepare_dates();
+ this.render_canvas();
+ },
render: function() {
this.clear();
-
this.setup_groups();
this.make_grid();
this.make_dates();
- // this.make_label();
this.make_bars();
this.make_arrows();
this.set_arrows_on_bars();
@@ -75,8 +93,9 @@ var Gantt = Class.extend({
bind: function() {
this.bind_grid_click();
},
- prepare_filters: function () {
- this.filters = {};
+ render_canvas: function() {
+ this.canvas = Snap(this.opts.parent_selector);
+ this.canvas.addClass("gantt");
},
clear: function () {
this.canvas.clear();
@@ -86,10 +105,6 @@ var Gantt = Class.extend({
prepare_dates: function() {
var me = this;
this.tasks.forEach(function(task) {
- // momentify
- task._start = moment(task.start, me.opts.date_format);
- task._end = moment(task.end, me.opts.date_format);
-
// set global start and end date
if(!me.start || task._start < me.start) {
me.start = task._start;
@@ -98,19 +113,22 @@ var Gantt = Class.extend({
me.end = task._end;
}
});
- if(me.view_mode === 'Quarter Day' || me.view_mode === 'Half Day') {
- me.start = me.start.clone().subtract(1, 'day');
- me.end = me.end.clone().add(1, 'day');
- } else if(me.view_mode === 'Month') {
+ this.set_gantt_dates();
+ this.setup_dates();
+ },
+ set_gantt_dates: function() {
+ var me = this;
+ if(me.view_is(['Quarter Day','Half Day'])) {
+ me.start = me.start.clone().subtract(7, 'day');
+ me.end = me.end.clone().add(7, 'day');
+ } else if(me.view_is('Month')) {
me.start = me.start.clone().startOf('year');
me.end = me.end.clone().endOf('month').add(1, 'year');
} else {
- me.start = me.start.clone().startOf('month');
- me.end = me.end.clone().endOf('month');
+ me.start = me.start.clone().startOf('month').subtract(1, 'month');
+ me.end = me.end.clone().endOf('month').add(1, 'month');
}
- this.setup_dates();
},
-
setup_dates: function() {
this.dates = [];
var cur_date = null;
@@ -118,21 +136,33 @@ var Gantt = Class.extend({
if(!cur_date) {
cur_date = this.start.clone();
} else {
- cur_date = (this.view_mode === 'Month') ?
+ cur_date = this.view_is('Month') ?
cur_date = cur_date.clone().add(1, 'month'):
cur_date.clone().add(this.opts.step, 'hours');
}
this.dates.push(cur_date);
}
},
+ setup_groups: function() {
+ var me = this;
+ // make group layers
+ ["grid", "date", "arrow",
+ "progress", "bar", "details"].forEach(function(name) {
+ me.groups[name] = me.canvas.group().attr({'id': name});
+ });
+ },
+ get_view_modes: function() {
+ return this.opts.valid_view_modes || [];
+ },
set_view_mode: function(mode) {
this.set_scale(mode);
- this.start = this.end = undefined;
- this.prepare_dates();
+ this.prepare();
this.render();
},
set_scale: function (scale) {
this.view_mode = scale;
+
+ //fire viewmode_change event
this.events.on_viewmode_change(scale);
if(scale === 'Day') {
this.opts.step = 24;
@@ -160,14 +190,6 @@ var Gantt = Class.extend({
this.tasks.push(task);
this.prepare_dates();
},
- setup_groups: function() {
- var me = this;
- // make groups
- ["grid", "controls", "label", "date",
- "arrow", "progress", "bar", "details"].forEach(function(name) {
- me.groups[name] = me.canvas.group().attr({'id': name});
- });
- },
set_width: function () {
var cur_width = this.canvas.node.getBoundingClientRect().width;
var actual_width = this.canvas.getBBox().width;
@@ -401,15 +423,7 @@ var Gantt = Class.extend({
}
});
},
- get_task: function (id) {
- var result = null;
- this.tasks.forEach(function (task) {
- if (task.id === id){
- result = task;
- }
- });
- return result;
- },
+
make_label: function () {
var me = this;
var label_x = me.opts.label_width - me.opts.padding,
@@ -471,6 +485,29 @@ var Gantt = Class.extend({
el.removeClass('active');
});
});
+ },
+ view_is: function(modes) {
+ var me = this;
+ if (typeof modes === 'string') {
+ return me.view_mode === modes;
+ } else {
+ modes.reduce(function(acc, curr) {
+ return (me.view_mode === curr) || acc
+ }, false);
+ // for (var i = 0; i < modes.length; i++) {
+ // if(me.gantt.view_mode === modes[i]) return true;
+ // }
+ // return false;
+ }
+ },
+ get_task: function (id) {
+ var result = null;
+ this.tasks.forEach(function (task) {
+ if (task.id === id){
+ result = task;
+ }
+ });
+ return result;
}
});
@@ -501,12 +538,6 @@ var Bar = Class.extend({
var defaults = {
height: 20,
corner_radius: 3,
- color: {
- bar: "#a3a3ff",
- progress: "#7575ff",
- hover_bar: "#7575ff",
- hover_progress: "#4d4da8"
- },
events: {}
};
for(var key in defaults) {
@@ -519,6 +550,9 @@ var Bar = Class.extend({
this.prepare_plugins();
},
prepare_values: function() {
+ if(!this.task.start || !this.task.end){
+ this.invalid = true;
+ }
this.x = this.compute_x();
this.y = this.compute_y();
this.duration = (this.task._end.diff(this.task._start, 'hours') + 24)/this.gantt.step;
@@ -557,8 +591,12 @@ var Bar = Class.extend({
this.corner_radius, this.corner_radius)
.addClass("bar")
.appendTo(this.bar_group);
+ if(this.invalid) {
+ this.bar.addClass('bar-invalid');
+ }
},
draw_progress_bar: function() {
+ if(this.invalid) return;
this.bar_progress = this.canvas.rect(this.x, this.y,
this.progress_width, this.height,
this.corner_radius, this.corner_radius)
@@ -574,6 +612,7 @@ var Bar = Class.extend({
this.update_label_position(this);
},
draw_resize_handles: function() {
+ if(this.invalid) return;
var bar = this.group.select('.bar');
this.canvas.rect(bar.getX() + bar.getWidth() - 9, bar.getY() + 1,
8, this.height - 2, this.corner_radius, this.corner_radius)
@@ -583,15 +622,37 @@ var Bar = Class.extend({
8, this.height - 2, this.corner_radius, this.corner_radius)
.addClass('handle left')
.appendTo(this.handle_group);
- this.canvas.polygon(
+
+ if(this.task.progress && this.task.progress < 100) {
+ this.canvas.polygon(
bar.getX() + this.progress_width - 5, bar.getY() + bar.get("height"),
bar.getX() + this.progress_width + 5, bar.getY() + bar.get("height"),
bar.getX() + this.progress_width, bar.getY() + bar.get("height") - 8.66
)
.addClass('handle progress')
- .appendTo(this.handle_group);
+ .appendTo(this.handle_group)
+ }
+ },
+ draw_invalid_bar: function() {
+ var x = this.gantt.offset +
+ (moment().startOf('day').diff(this.gantt.start, 'hours') /
+ this.gantt.step *
+ this.gantt.unit_width);
+
+ this.canvas.rect(x, this.y,
+ this.gantt.unit_width*2, this.height,
+ this.corner_radius, this.corner_radius)
+ .addClass("bar-invalid")
+ .appendTo(this.bar_group);
+ //continue here
+ this.canvas.text(x + this.gantt.unit_width,
+ this.y + this.height/2,
+ 'Dates not set')
+ .addClass("bar-label big")
+ .appendTo(this.bar_group);
},
bind: function () {
+ if(this.invalid) return;
// this.show_details();
this.bind_resize();
this.bind_drag();
@@ -728,11 +789,19 @@ var Bar = Class.extend({
},
bind_resize_progress: function() {
var me = this;
+ var bar = me.group.select('.bar');
var bar_progress = me.group.select('.bar-progress');
var handle = me.group.select('.handle.progress');
- handle.drag(onmove, onstart, onstop);
+ handle && handle.drag(onmove, onstart, onstop);
function onmove(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;
@@ -743,8 +812,10 @@ var Bar = Class.extend({
me.set_action_completed();
}
function onstart() {
- bar_progress.owidth = bar_progress.getWidth();
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();
}
},
view_is: function(modes) {
diff --git a/frappe/public/less/gantt.less b/frappe/public/less/gantt.less
index 80edcaca7f..9c1e79d671 100644
--- a/frappe/public/less/gantt.less
+++ b/frappe/public/less/gantt.less
@@ -47,6 +47,16 @@
.bar-progress {
fill: #a3a3ff;
}
+ .bar-invalid {
+ fill: transparent;
+ stroke: #8D99A6;
+ stroke-width: 1;
+ stroke-dasharray: 5;
+
+ &~.bar-label {
+ fill: #555;
+ }
+ }
.bar-label {
fill: #fff;
dominant-baseline: central;