/*
@license
dhtmlxGantt v.4.0.0 Stardard
This software is covered by GPL license. You also can obtain Commercial or Enterprise license to use it in non-GPL project - please contact sales@dhtmlx.com. Usage without proper license is prohibited.
(c) Dinamenta, UAB.
*/
gantt = {
version:"4.0.0"
};
/*jsl:ignore*/
//import from dhtmlxcommon.js
gantt.event = function(el, event, handler){
if (el.addEventListener)
el.addEventListener(event, handler, false);
else if (el.attachEvent)
el.attachEvent("on"+event, handler);
};
gantt.eventRemove = function(el, event, handler){
if (el.removeEventListener)
el.removeEventListener(event, handler, false);
else if (el.detachEvent)
el.detachEvent("on"+event, handler);
};
/** Overrides event functionality.
* Includes all default methods from dhtmlx.common but adds _silentStart, _silendEnd
* @desc:
* @type: private
*/
gantt._eventable=function(obj){
obj._silent_mode = false;
obj._silentStart = function() {
this._silent_mode = true;
};
obj._silentEnd = function() {
this._silent_mode = false;
};
obj.attachEvent=function(name, catcher, callObj){
name='ev_'+name.toLowerCase();
if (!this[name])
this[name]=new this._eventCatcher(callObj||this);
return(name+':'+this[name].addEvent(catcher)); //return ID (event name & event ID)
};
obj.callEvent=function(name, arg0){
if (this._silent_mode) return true;
name='ev_'+name.toLowerCase();
if (this[name])
return this[name].apply(this, arg0);
return true;
};
obj.checkEvent=function(name){
return (!!this['ev_'+name.toLowerCase()]);
};
obj._eventCatcher=function(obj){
var dhx_catch = [];
var z = function(){
var res = true;
for (var i = 0; i < dhx_catch.length; i++){
if (dhx_catch[i]){
var zr = dhx_catch[i].apply(obj, arguments);
res=res&&zr;
}
}
return res;
};
z.addEvent=function(ev){
if (typeof (ev) == "function")
return dhx_catch.push(ev)-1;
return false;
};
z.removeEvent=function(id){
dhx_catch[id]=null;
};
return z;
};
obj.detachEvent=function(id){
if (id){
var list = id.split(':'); //get EventName and ID
this[list[0]].removeEvent(list[1]); //remove event
}
};
obj.detachAllEvents = function(){
for (var name in this){
if (name.indexOf("ev_") === 0)
delete this[name];
}
};
obj = null;
};
/*jsl:end*/
gantt.copy = function(object) {
var i, t, result; // iterator, types array, result
if (object && typeof object == "object") {
result = {};
t = [Array,Date,Number,String,Boolean];
for (i=0; i this.config.sensitivity) {
// real drag starts here,
// when user moves mouse at first time after onmousedown
this.config.started = true;
this.config.ignore = false;
if (this.callEvent("onBeforeDragStart", [obj, this.config.original_target]) === false) {
this.config.ignore = true;
return true;
}
// initialize dnd marker
var marker = this.config.marker = document.createElement("div");
marker.className = "gantt_drag_marker";
marker.innerHTML = "Dragging object";
document.body.appendChild(marker);
this.callEvent("onAfterDragStart", [obj, this.config.original_target]);
} else
this.config.ignore = true;
}
if (!this.config.ignore) {
e.pos = this.getPosition(e);
this.config.marker.style.left = e.pos.x + "px";
this.config.marker.style.top = e.pos.y + "px";
this.callEvent("onDragMove", [obj,e]);
}
},
dragEnd: function(obj) {
if (this.config.marker) {
this.config.marker.parentNode.removeChild(this.config.marker);
this.config.marker = null;
this.callEvent("onDragEnd", []);
}
document.body.className = document.body.className.replace(" gantt_noselect", "");
},
getPosition: function(e) {
var x = 0, y = 0;
e = e || window.event;
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
} else if (e.clientX || e.clientY) {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return { x:x, y:y };
}
};
gantt._init_grid = function () {
this._click.gantt_close = this.bind(function (e, id, trg) {
this.close(id);
return false;
}, this);
this._click.gantt_open = this.bind(function (e, id, trg) {
this.open(id);
return false;
}, this);
this._click.gantt_row = this.bind(function (e, id, trg) {
if (id !== null) {
var task = this.getTask(id);
if(this.config.scroll_on_click)
this.showDate(task.start_date);
this.callEvent("onTaskRowClick", [id, trg]);
}
}, this);
this._click.gantt_grid_head_cell = this.bind(function (e, id, trg) {
var column = trg.getAttribute("column_id");
if (!this.callEvent("onGridHeaderClick", [column, e]))
return;
if (column == "add") {
this._click.gantt_add(e, this.config.root_id);
return;
}
if (this.config.sort) {
var sorting_method = column,
conf;
for(var i = 0; this.config.columns.length; i++){
if(this.config.columns[i].name == column){
conf = this.config.columns[i];
break;
}
}
if(conf && conf.sort !== undefined && conf.sort !== true){
sorting_method = conf.sort;
if(!sorting_method){ // column sort property 'false', no sorting
return;
}
}
var sort = (this._sort && this._sort.direction && this._sort.name == column) ? this._sort.direction : "desc";
// invert sort direction
sort = (sort == "desc") ? "asc" : "desc";
this._sort = {
name: column,
direction: sort
};
this.sort(sorting_method, sort == "desc");
}
}, this);
if (!this.config.sort && this.config.order_branch) {
this._init_dnd();
}
this._click.gantt_add = this.bind(function (e, id, trg) {
if (this.config.readonly) return;
var item = { };
this.createTask(item, id ? id : this.config.root_id);
return false;
}, this);
if(this._init_resize){
this._init_resize();
}
};
gantt._render_grid = function () {
if (this._is_grid_visible()) {
this._calc_grid_width();
this._render_grid_header();
}
};
gantt._calc_grid_width = function () {
var columns = this.getGridColumns();
var cols_width = 0;
var unknown = [];
var width = [];
for (var i = 0; i < columns.length; i++) {
var v = parseInt(columns[i].width, 10);
if (window.isNaN(v)) {
v = 50;
unknown.push(i);
}
width[i] = v;
cols_width += v;
}
if (this.config.autofit || unknown.length) {
var diff = this._get_grid_width() - cols_width;
// TODO: logic may be improved for proportional changing of width
var step = diff / (unknown.length > 0 ? unknown.length : (width.length > 0 ? width.length : 1));
if (unknown.length > 0) {
// there are several columns with undefined width
var delta = diff / (unknown.length ? unknown.length : 1);
for (var i = 0; i < unknown.length; i++) {
var index = unknown[i];
width[index] += delta;
}
} else {
// delta must be added for all columns
var delta = diff / (width.length ? width.length : 1);
for (var i = 0; i < width.length; i++)
width[i] += delta;
}
for (var i = 0; i < width.length; i++) {
columns[i].width = width[i];
}
}else{
this.config.grid_width = cols_width;
}
};
gantt._render_grid_header = function () {
var columns = this.getGridColumns();
var cells = [];
var width = 0,
labels = this.locale.labels;
var lineHeigth = this.config.scale_height - 2;
for (var i = 0; i < columns.length; i++) {
var last = i == columns.length - 1;
var col = columns[i];
var colWidth = col.width*1;
if (last && this._get_grid_width() > width + colWidth)
col.width = colWidth = this._get_grid_width() - width;
width += colWidth;
var sort = (this._sort && col.name == this._sort.name) ? ("") : "";
var cssClass = ["gantt_grid_head_cell",
("gantt_grid_head_" + col.name),
(last ? "gantt_last_cell" : ""),
this.templates.grid_header_class(col.name, col)].join(" ");
var style = "width:" + (colWidth - (last ? 1 : 0)) + "px;";
var label = (col.label || labels["column_" + col.name]);
label = label || "";
var cell = "
" + label + sort + "
";
cells.push(cell);
}
this.$grid_scale.style.height = (this.config.scale_height - 1) + "px";
this.$grid_scale.style.lineHeight = lineHeigth + "px";
this.$grid_scale.style.width = (width - 1) + "px";
this.$grid_scale.innerHTML = cells.join("");
};
gantt._render_grid_item = function (item) {
if (!gantt._is_grid_visible())
return null;
var columns = this.getGridColumns();
var cells = [];
var width = 0;
for (var i = 0; i < columns.length; i++) {
var last = i == columns.length - 1;
var col = columns[i];
var cell;
var value;
if (col.name == "add") {
value = "";
} else {
if (col.template)
value = col.template(item);
else
value = item[col.name];
if (value instanceof Date)
value = this.templates.date_grid(value, item);
value = "
" + value + "
";
}
var css = "gantt_cell" + (last ? " gantt_last_cell" : "");
var tree = "";
if (col.tree) {
for (var j = 0; j < item.$level; j++)
tree += this.templates.grid_indent(item);
var has_child = this._has_children(item.id);
if (has_child) {
tree += this.templates.grid_open(item);
tree += this.templates.grid_folder(item);
} else {
tree += this.templates.grid_blank(item);
tree += this.templates.grid_file(item);
}
}
var style = "width:" + (col.width - (last ? 1 : 0)) + "px;";
if (this.defined(col.align))
style += "text-align:" + col.align + ";";
cell = "
" + tree + value + "
";
cells.push(cell);
}
var css = item.$index % 2 === 0 ? "" : " odd";
css += (item.$transparent) ? " gantt_transparent" : "";
css += (item.$dataprocessor_class ? " " + item.$dataprocessor_class : "");
if (this.templates.grid_row_class) {
var css_template = this.templates.grid_row_class.call(this, item.start_date, item.end_date, item);
if (css_template)
css += " " + css_template;
}
if (this.getState().selected_task == item.id) {
css += " gantt_selected";
}
var el = document.createElement("div");
el.className = "gantt_row" + css;
el.style.height = this.config.row_height + "px";
el.style.lineHeight = (gantt.config.row_height) + "px";
el.setAttribute(this.config.task_attribute, item.id);
el.innerHTML = cells.join("");
return el;
};
gantt.open = function (id) {
gantt._set_item_state(id, true);
this.callEvent("onTaskOpened", [id]);
};
gantt.close = function (id) {
gantt._set_item_state(id, false);
this.callEvent("onTaskClosed", [id]);
};
gantt._set_item_state = function (id, state) {
if (id && this._pull[id]) {
this._pull[id].$open = state;
gantt._refresh_on_toggle_element(id);
}
};
gantt._refresh_on_toggle_element = function(id){
this.refreshData();
};
gantt._is_grid_visible = function () {
return (this.config.grid_width && this.config.show_grid);
};
gantt._get_grid_width = function () {
if (this._is_grid_visible()) {
if (this._is_chart_visible()) {
return this.config.grid_width;
} else {
return this._x;
}
} else {
return 0;
}
};
gantt.moveTask = function (sid, tindex, parent) {
//target id as 4th parameter
var id = arguments[3];
if (id) {
if (id === sid) return;
parent = this.getParent(id);
tindex = this.getTaskIndex(id);
}
if(sid == parent){
return;
}
parent = parent || this.config.root_id;
var source = this.getTask(sid);
var source_pid = this.getParent(source.id);
var sbranch = this.getChildren(this.getParent(source.id));
var tbranch = this.getChildren(parent);
if (tindex == -1)
tindex = tbranch.length + 1;
if (source_pid == parent) {
var sindex = this.getTaskIndex(sid);
if (sindex == tindex) return;
}
/*
prevent moving to another sub-branch:
gantt.attachEvent("onBeforeTaskMove", function(id, parent, tindex){
var task = gantt.getTask(id);
if(task.parent != parent)
return false;
return true;
});
*/
if(this.callEvent("onBeforeTaskMove", [sid, parent, tindex]) === false)
return;
this._replace_branch_child(source_pid, sid);
tbranch = this.getChildren(parent);
var tid = tbranch[tindex];
if (!tid) //adding as last element
tbranch.push(sid);
else
tbranch = tbranch.slice(0, tindex).concat([ sid ]).concat(tbranch.slice(tindex));
this.setParent(source, parent);
this._branches[parent] = tbranch;
//var childTree = this._getTaskTree(sid);
//for(var i = 0; i < childTree.length; i++){
// var item = this._pull[childTree[i]];
// if(item)
source.$level = this.calculateTaskLevel(source);
//}
if(tindex*1 > 0){
if(id){
source.$drop_target = (this.getTaskIndex(sid) > this.getTaskIndex(id) ? "next:" : '') + id;
}else{
source.$drop_target = "next:" + gantt.getPrevSibling(sid);
}
}else if(tbranch[tindex*1 + 1]){
source.$drop_target = tbranch[tindex*1 + 1];
}else{
source.$drop_target = parent;
}
if(!this.callEvent("onAfterTaskMove", [sid, parent, tindex]))
return;
this.refreshData();
};
gantt._init_dnd = function () {
var dnd = new gantt._DnD(this.$grid_data, {updates_per_second: 60});
if (this.defined(this.config.dnd_sensitivity))
dnd.config.sensitivity = this.config.dnd_sensitivity;
dnd.attachEvent("onBeforeDragStart", this.bind(function (obj, e) {
var el = this._locateHTML(e);
if (!el) return false;
if (this.hideQuickInfo) this._hideQuickInfo();
var id = this.locate(e);
var task = gantt.getTask(id);
if(gantt._is_readonly(task))
return false;
dnd.config.initial_open_state = task.$open;
if (!this.callEvent("onRowDragStart", [id, e.target || e.srcElement, e])) {
return false;
}
}, this));
dnd.attachEvent("onAfterDragStart", this.bind(function (obj, e) {
var el = this._locateHTML(e);
dnd.config.marker.innerHTML = el.outerHTML;
dnd.config.id = this.locate(e);
var task = this.getTask(dnd.config.id);
dnd.config.index = this.getTaskIndex(dnd.config.id);
dnd.config.parent = task.parent;
task.$open = false;
task.$transparent = true;
this.refreshData();
}, this));
dnd.lastTaskOfLevel = function (level) {
var ids = gantt._order,
pull = gantt._pull,
last_item = null;
for (var i = 0, len = ids.length; i < len; i++) {
if (pull[ids[i]].$level == level) {
last_item = pull[ids[i]];
}
}
return last_item ? last_item.id : null;
};
dnd._getGridPos = this.bind( function(e){
var pos = this._get_position(this.$grid_data);
// row offset
var x = pos.x;
var y = e.pos.y - 10;
// prevent moving row out of grid_data container
if (y < pos.y) y = pos.y;
if (y > pos.y + this.$grid_data.offsetHeight - this.config.row_height) y = pos.y + this.$grid_data.offsetHeight - this.config.row_height;
pos.x = x;
pos.y = y;
return pos;
}, this);
dnd.attachEvent("onDragMove", this.bind(function (obj, e) {
var dd = dnd.config;
var pos = dnd._getGridPos(e);
// setting position of row
dd.marker.style.left = pos.x + 10 + "px";
dd.marker.style.top = pos.y + "px";
//previous action might cause page scroll appear thus change position of the gantt, need to recalculate
pos = dnd._getGridPos(e);
var x = pos.x,
y = pos.y;
// highlight row when mouseover
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
var target = document.elementFromPoint(pos.x - scrollLeft + 1, y - scrollTop);
var el = this.locate(target);
var item = this.getTask(dnd.config.id);
if (!this.isTaskExists(el)) {
el = dnd.lastTaskOfLevel(gantt.config.order_branch_free ? item.$level : 0);
if (el == dnd.config.id) {
el = null;
}
}
function allowedLevel (next, item){
return (next.$level == item.$level || gantt.config.order_branch_free);
}
if (this.isTaskExists(el)) {
var box = gantt._get_position(target);
var over = this.getTask(el);
if (box.y + target.offsetHeight / 2 < y) {
//hovering over bottom part of item, check can be drop to bottom
var index = this.getGlobalTaskIndex(over.id);
var next = this._pull[this._order[index + 1]]; //adds +1 when hovering over placeholder
if (next) {
if (next.id != item.id)
over = next; //there is a valid target
else
return;
} else {
//we at end of the list, check and drop at the end of list
next = this._pull[this._order[index]];
if (allowedLevel(next, item) && next.id != item.id) {
this.moveTask(item.id, -1, this.getParent(next.id));
return;
}
}
}
//if item is on different level, check the one before it
var index = this.getGlobalTaskIndex(over.id),
prev = this._pull[this._order[index-1]];
var shift = 1;
while((!prev || prev.id == over.id) && index - shift >= 0){
prev = this._pull[this._order[index-shift]];
shift++;
}
if (item.id == over.id) return;
//replacing item under cursor
if (allowedLevel(over, item) && item.id != over.id) {
this.moveTask(item.id, 0, 0, over.id);
}else if(over.$level == item.$level - 1 && !gantt.getChildren(over.id).length){
this.moveTask(item.id, 0, over.id);
} else if(prev && (allowedLevel(prev, item)) && (item.id != prev.id)){
this.moveTask(item.id, -1, this.getParent(prev.id));
}
}
return true;
}, this));
dnd.attachEvent("onDragEnd", this.bind(function () {
var task = this.getTask(dnd.config.id);
if(this.callEvent("onBeforeRowDragEnd",[dnd.config.id, dnd.config.parent, dnd.config.index]) === false) {
this.moveTask(dnd.config.id, dnd.config.index, dnd.config.parent);
task.$drop_target = null;
}else{
//var newLevel = task.$level,
// oldLevel = !this.isTaskExists(dnd.config.parent) ? 0 : (this.getTask(dnd.config.parent).$level + 1);
//var levelChanged = newLevel != oldLevel;
this.callEvent("onRowDragEnd", [dnd.config.id, task.$drop_target]);
}
task.$transparent = false;
task.$open = dnd.config.initial_open_state;
this.refreshData();
}, this));
};
/* will be overwriten in order to provide hide/show column functionality in some editions */
gantt.getGridColumns = function () {
return this.config.columns;
};
gantt._has_children = function(id){
return this.getChildren(id).length > 0;
};
// --#include core/grid_resize.js
// --#include core/dynamic_loading.js
// --#include core/grid_column_api.js
gantt._scale_helpers = {
getSum : function(sizes, from, to){
if(to === undefined)
to = sizes.length - 1;
if(from === undefined)
from = 0;
var summ = 0;
for(var i=from; i <= to; i++)
summ += sizes[i];
return summ;
},
setSumWidth : function(sum_width, scale, from, to){
var parts = scale.width;
if(to === undefined)
to = parts.length - 1;
if(from === undefined)
from = 0;
var length = to - from + 1;
if(from > parts.length - 1 || length <= 0 || to > parts.length - 1)
return;
var oldWidth = this.getSum(parts, from, to);
var diff = sum_width - oldWidth;
this.adjustSize(diff, parts, from, to);
this.adjustSize(- diff, parts, to + 1);
scale.full_width = this.getSum(parts);
},
splitSize : function(width, count){
var arr = [];
for(var i=0; i < count; i++) arr[i] = 0;
this.adjustSize(width, arr);
return arr;
},
adjustSize : function(width, parts, from, to){
if(!from)
from = 0;
if(to === undefined)
to = parts.length - 1;
var length = to - from + 1;
var full = this.getSum(parts, from, to);
var shared = 0;
for(var i = from; i <= to; i++){
var share = Math.floor(width*(full ? (parts[i]/full) : (1/length)));
full -= parts[i];
width -= share;
length--;
parts[i] += share;
shared += share;
}
parts[parts.length - 1] += width;
//parts[parts.length - 1] += width - shared;
},
sortScales : function(scales){
function cellSize(unit, step){
var d = new Date(1970, 0, 1);
return gantt.date.add(d, step, unit) - d;
}
scales.sort(function(a, b){
if(cellSize(a.unit, a.step) < cellSize(b.unit, b.step)){
return 1;
}else if(cellSize(a.unit, a.step) > cellSize(b.unit, b.step)){
return -1;
}else{
return 0;
}
});
},
primaryScale : function(){
gantt._init_template("date_scale");
return {
unit: gantt.config.scale_unit,
step: gantt.config.step,
template : gantt.templates.date_scale,
date : gantt.config.date_scale,
css: gantt.templates.scale_cell_class
};
},
prepareConfigs : function(scales, min_coll_width, container_width, scale_height){
var heights = this.splitSize(scale_height, scales.length);
var full_width = container_width;
var configs = [];
for(var i=scales.length-1; i >= 0; i--){
var main_scale = (i == scales.length - 1);
var cfg = this.initScaleConfig(scales[i]);
if(main_scale){
this.processIgnores(cfg);
}
this.initColSizes(cfg, min_coll_width, full_width, heights[i]);
this.limitVisibleRange(cfg);
if(main_scale){
full_width = cfg.full_width;
}
configs.unshift(cfg);
}
for( var i =0; i < configs.length-1; i++){
this.alineScaleColumns(configs[configs.length-1], configs[i]);
}
for(var i = 0; i < configs.length; i++){
this.setPosSettings(configs[i]);
}
return configs;
},
setPosSettings: function(config){
for(var i = 0, len = config.trace_x.length; i < len; i++){
config.left.push((config.width[i - 1] || 0) + (config.left[i - 1] || 0));
}
},
_ignore_time_config : function(date){
if(this.config.skip_off_time){
return !this.isWorkTime(date);
}
return false;
},
//defined in an extension
processIgnores : function(config){
config.ignore_x = {};
config.display_count = config.count;
},
initColSizes : function(config, min_col_width, full_width, line_height){
var cont_width = full_width;
config.height = line_height;
var column_count = config.display_count === undefined ? config.count : config.display_count;
if(!column_count)
column_count = 1;
config.col_width = Math.floor(cont_width/column_count);
if(min_col_width){
if (config.col_width < min_col_width){
config.col_width = min_col_width;
cont_width = config.col_width * column_count;
}
}
config.width = [];
var ignores = config.ignore_x || {};
for(var i =0; i < config.trace_x.length; i++){
if(ignores[config.trace_x[i].valueOf()] || (config.display_count == config.count)){
config.width[i] = 0;
}else{
config.width[i] = 1;
}
}
this.adjustSize(cont_width - this.getSum(config.width)/* 1 width per column from the code above */, config.width);
config.full_width = this.getSum(config.width);
},
initScaleConfig : function(config){
var cfg = gantt.mixin({
count:0,
col_width:0,
full_width:0,
height:0,
width:[],
left:[],
trace_x:[]
}, config);
this.eachColumn(config.unit, config.step, function(date){
cfg.count++;
cfg.trace_x.push(new Date(date));
});
return cfg;
},
iterateScales : function(lower_scale, upper_scale, from, to, callback){
var upper_dates = upper_scale.trace_x;
var lower_dates = lower_scale.trace_x;
var prev = from || 0;
var end = to || (lower_dates.length - 1);
var prevUpper = 0;
for(var up=1; up < upper_dates.length; up++){
for(var next=prev; next <= end; next++){
if(+lower_dates[next] == +upper_dates[up]){
if(callback){
callback.apply(this, [prevUpper, up, prev, next]);
}
prev = next;
prevUpper = up;
continue;
}
}
}
},
alineScaleColumns : function(lower_scale, upper_scale, from, to){
this.iterateScales(lower_scale, upper_scale, from, to, function(upper_start, upper_end, lower_start, lower_end){
var targetWidth = this.getSum(lower_scale.width, lower_start, lower_end - 1);
var actualWidth = this.getSum(upper_scale.width, upper_start, upper_end - 1);
if(actualWidth != targetWidth){
this.setSumWidth(targetWidth, upper_scale, upper_start, upper_end - 1);
}
});
},
eachColumn : function(unit, step, callback){
var start = new Date(gantt._min_date),
end = new Date(gantt._max_date);
if(gantt.date[unit + "_start"]){
start = gantt.date[unit + "_start"](start);
}
var curr = new Date(start);
if(+curr >= +end){
end = gantt.date.add(curr, step, unit);
}
while(+curr < +end){
callback.call(this, new Date(curr));
var tzOffset = curr.getTimezoneOffset();
curr = gantt.date.add(curr, step, unit);
curr = gantt._correct_dst_change(curr, tzOffset, step, unit);
if(gantt.date[unit + '_start'])
curr = gantt.date[unit + "_start"](curr);
}
},
limitVisibleRange : function(cfg){
var dates = cfg.trace_x;
var left = 0, right = cfg.width.length-1;
var diff = 0;
if(+dates[0] < +gantt._min_date && left != right){
var width = Math.floor(cfg.width[0] * ((dates[1] - gantt._min_date)/ (dates[1] - dates[0])));
diff += cfg.width[0] - width;
cfg.width[0] = width;
dates[0] = new Date(gantt._min_date);
}
var last = dates.length - 1;
var lastDate = dates[last];
var outDate = gantt.date.add(lastDate, cfg.step, cfg.unit);
if(+outDate > +gantt._max_date && last > 0){
var width = cfg.width[last] - Math.floor(cfg.width[last] * ((outDate - gantt._max_date)/(outDate - lastDate)));
diff += cfg.width[last] - width;
cfg.width[last] = width;
}
if(diff){
var full = this.getSum(cfg.width);
var shared = 0;
for(var i =0; i < cfg.width.length; i++){
var share = Math.floor(diff*(cfg.width[i]/full));
cfg.width[i] += share;
shared += share;
}
this.adjustSize(diff - shared, cfg.width);
}
}
};
// --#include core/scales_ignore.js
gantt._tasks_dnd = {
drag : null,
_events:{
before_start:{},
before_finish:{},
after_finish:{}
},
_handlers:{},
init:function(){
this.clear_drag_state();
var drag = gantt.config.drag_mode;
this.set_actions();
var evs = {
"before_start":"onBeforeTaskDrag",
"before_finish":"onBeforeTaskChanged",
"after_finish":"onAfterTaskDrag"
};
//for now, all drag operations will trigger the same events
for(var stage in this._events){
for(var mode in drag){
this._events[stage][mode] = evs[stage];
}
}
this._handlers[drag.move] = this._move;
this._handlers[drag.resize] = this._resize;
this._handlers[drag.progress] = this._resize_progress;
},
set_actions:function(){
var data = gantt.$task_data;
gantt.event(data, "mousemove", gantt.bind(function(e){
this.on_mouse_move(e||event);
}, this));
gantt.event(data, "mousedown", gantt.bind(function(e){
this.on_mouse_down(e||event);
}, this));
gantt.event(data, "mouseup", gantt.bind(function(e){
this.on_mouse_up(e||event);
}, this));
},
clear_drag_state : function(){
this.drag = {
id:null,
mode:null,
pos:null,
start_x:null,
start_y:null,
obj:null,
left:null
};
},
_resize : function(ev, shift, drag){
var cfg = gantt.config;
var coords_x = this._drag_task_coords(ev, drag);
if(drag.left){
ev.start_date = gantt.dateFromPos(coords_x.start + shift);
if(!ev.start_date){
ev.start_date = new Date(gantt.getState().min_date);
}
}else{
ev.end_date =gantt.dateFromPos(coords_x.end + shift);
if(!ev.end_date){
ev.end_date = new Date(gantt.getState().max_date);
}
}
if (ev.end_date - ev.start_date < cfg.min_duration){
if(drag.left)
ev.start_date = gantt.calculateEndDate(ev.end_date, -1);
else
ev.end_date = gantt.calculateEndDate(ev.start_date, 1);
}
gantt._init_task_timing(ev);
},
_resize_progress:function(ev, shift, drag){
var coords_x = this._drag_task_coords(ev, drag);
var diff = Math.max(0, drag.pos.x - coords_x.start);
ev.progress = Math.min(1, diff / (coords_x.end-coords_x.start));
},
_move : function(ev, shift, drag){
var coords_x = this._drag_task_coords(ev, drag);
var new_start = gantt.dateFromPos(coords_x.start + shift),
new_end = gantt.dateFromPos(coords_x.end + shift);
if(!new_start){
ev.start_date = new Date(gantt.getState().min_date);
ev.end_date = gantt.dateFromPos(gantt.posFromDate(ev.start_date) + (coords_x.end - coords_x.start));
}else if(!new_end){
ev.end_date = new Date(gantt.getState().max_date);
ev.start_date = gantt.dateFromPos(gantt.posFromDate(ev.end_date) - (coords_x.end - coords_x.start));
}else{
ev.start_date = new_start;
ev.end_date = new_end;
}
},
_drag_task_coords : function(t, drag){
var start = drag.obj_s_x = drag.obj_s_x || gantt.posFromDate(t.start_date);
var end = drag.obj_e_x = drag.obj_e_x || gantt.posFromDate(t.end_date);
return {
start : start,
end : end
};
},
on_mouse_move : function(e){
if(this.drag.start_drag)
this._start_dnd(e);
var drag = this.drag;
if (drag.mode){
if(!gantt._checkTimeout(this, 40))//limit update frequency
return;
this._update_on_move(e);
}
},
_update_on_move : function(e){
var drag = this.drag;
if (drag.mode){
var pos = gantt._get_mouse_pos(e);
if(drag.pos && drag.pos.x == pos.x)
return;
drag.pos=pos;
var curr_date = gantt.dateFromPos(pos.x);
if(!curr_date || isNaN( curr_date.getTime() ))
return;
var shift = pos.x - drag.start_x;
var ev = gantt.getTask(drag.id);
if(this._handlers[drag.mode]){
var original = gantt.mixin({}, ev);
var copy = gantt.mixin({}, ev);
this._handlers[drag.mode].apply(this, [copy, shift, drag]);
gantt.mixin(ev, copy, true);
//gantt._update_parents(drag.id, true);
gantt.callEvent("onTaskDrag", [ev.id, drag.mode, copy, original, e]);
gantt.mixin(ev, copy, true);
gantt._update_parents(drag.id);
gantt.refreshTask(drag.id);
}
}
},
on_mouse_down : function(e, src){
// on Mac we do not get onmouseup event when clicking right mouse button leaving us in dnd state
// let's ignore right mouse button then
if (e.button == 2)
return;
var id =gantt.locate(e);
var task = null;
if(gantt.isTaskExists(id)){
task = gantt.getTask(id);
}
if (gantt._is_readonly(task) || this.drag.mode) return;
this.clear_drag_state();
src = src||(e.target||e.srcElement);
var className = gantt._getClassName(src);
if(!className || !this._get_drag_mode(className)){
if(src.parentNode)
return this.on_mouse_down(e, src.parentNode);
else
return;
}
var drag = this._get_drag_mode(className);
if(!drag){
if (gantt.checkEvent("onMouseDown") && gantt.callEvent("onMouseDown", [className.split(" ")[0]])) {
if (src.parentNode)
return this.on_mouse_down(e,src.parentNode);
}
}else{
if (drag.mode && drag.mode != gantt.config.drag_mode.ignore && gantt.config["drag_" + drag.mode]){
id = gantt.locate(src);
task = gantt.copy(gantt.getTask(id) || {});
if(gantt._is_readonly(task)){
this.clear_drag_state();
return false;
}
if(gantt._is_flex_task(task) && drag.mode != gantt.config.drag_mode.progress){//only progress drag is allowed for tasks with flexible duration
this.clear_drag_state();
return;
}
drag.id = id;
var pos = gantt._get_mouse_pos(e);
drag.start_x = pos.x;
drag.start_y = pos.y;
drag.obj = task;
this.drag.start_drag = drag;
}else
this.clear_drag_state();
}
},
_fix_dnd_scale_time:function(task, drag){
var unit = gantt._tasks.unit,
step = gantt._tasks.step;
if(!gantt.config.round_dnd_dates){
unit = 'minute';
step = gantt.config.time_step;
}
function fixStart(task){
if(!gantt.isWorkTime(task.start_date))
task.start_date = gantt.calculateEndDate(task.start_date, -1, gantt.config.duration_unit);
}
function fixEnd(task){
if(!gantt.isWorkTime(new Date(task.end_date - 1)))
task.end_date = gantt.calculateEndDate(task.end_date, 1, gantt.config.duration_unit);
}
if(drag.mode == gantt.config.drag_mode.resize){
if(drag.left){
task.start_date = gantt.roundDate({date:task.start_date, unit:unit, step:step});
fixStart(task);
}else{
task.end_date = gantt.roundDate({date:task.end_date, unit:unit, step:step});
fixEnd(task);
}
}else if(drag.mode == gantt.config.drag_mode.move){
task.start_date = gantt.roundDate({date:task.start_date, unit:unit, step:step});
fixStart(task);
task.end_date = gantt.calculateEndDate(task.start_date, task.duration, gantt.config.duration_unit);
}
},
_fix_working_times:function(task, drag){
var drag = drag || {mode : gantt.config.drag_mode.move};
if(gantt.config.work_time && gantt.config.correct_work_time){
if(drag.mode == gantt.config.drag_mode.resize){
if(drag.left){
task.start_date = gantt.getClosestWorkTime({date:task.start_date, dir:'future'});
}else{
task.end_date = gantt.getClosestWorkTime({date:task.end_date, dir:'past'});
}
}else if(drag.mode == gantt.config.drag_mode.move){
gantt.correctTaskWorkTime(task);
}
}
},
on_mouse_up : function(e){
var drag = this.drag;
if (drag.mode && drag.id){
//drop
var ev=gantt.getTask(drag.id);
if(gantt.config.work_time && gantt.config.correct_work_time){
this._fix_working_times(ev, drag);
}
this._fix_dnd_scale_time(ev, drag);
gantt._init_task_timing(ev);
if(!this._fireEvent("before_finish", drag.mode, [drag.id, drag.mode, gantt.copy(drag.obj), e])){
drag.obj._dhx_changed = false;
gantt.mixin(ev, drag.obj, true);
gantt.updateTask(ev.id);
} else {
var drag_id = drag.id;
gantt._init_task_timing(ev);
this.clear_drag_state();
gantt.updateTask(ev.id);
this._fireEvent("after_finish", drag.mode, [drag_id, drag.mode, e]);
}
}
this.clear_drag_state();
},
_get_drag_mode : function(className){
var modes = gantt.config.drag_mode;
var classes = (className || "").split(" ");
var classname = classes[0];
var drag = {mode:null, left:null};
switch (classname) {
case "gantt_task_line":
case "gantt_task_content":
drag.mode = modes.move;
break;
case "gantt_task_drag":
drag.mode = modes.resize;
if(classes[1] && classes[1].indexOf("left", classes[1].length - "left".length) !== -1){
drag.left = true;
}else{
drag.left = false;
}
break;
case "gantt_task_progress_drag":
drag.mode = modes.progress;
break;
case "gantt_link_control":
case "gantt_link_point":
drag.mode = modes.ignore;
break;
default:
drag = null;
break;
}
return drag;
},
_start_dnd : function(e){
var drag = this.drag = this.drag.start_drag;
delete drag.start_drag;
var cfg = gantt.config;
var id = drag.id;
if (!cfg["drag_"+drag.mode] || !gantt.callEvent("onBeforeDrag",[id, drag.mode, e]) || !this._fireEvent("before_start", drag.mode, [id, drag.mode, e])){
this.clear_drag_state();
}else {
delete drag.start_drag;
gantt.callEvent("onTaskDragStart", []);
}
},
_fireEvent:function(stage, mode, params){
gantt.assert(this._events[stage], "Invalid stage:{" + stage + "}");
var trigger = this._events[stage][mode];
gantt.assert(trigger, "Unknown after drop mode:{" + mode + "}");
gantt.assert(params, "Invalid event arguments");
if(!gantt.checkEvent(trigger))
return true;
return gantt.callEvent(trigger, params);
}
};
gantt.roundTaskDates = function(task){
var drag_state = gantt._tasks_dnd.drag;
if(!drag_state){
drag_state = {mode:gantt.config.drag_mode.move};
}
gantt._tasks_dnd._fix_dnd_scale_time(task, drag_state);
};
gantt._render_link = function(id){
var link = this.getLink(id);
var renders = gantt._get_link_renderers();
for(var i = 0; i < renders.length; i++)
renders[i].render_item(link);
};
gantt._get_link_type = function(from_start, to_start){
var type = null;
if(from_start && to_start){
type = gantt.config.links.start_to_start;
}else if(!from_start && to_start){
type = gantt.config.links.finish_to_start;
}else if(!from_start && !to_start){
type = gantt.config.links.finish_to_finish;
}else if(from_start && !to_start){
type = gantt.config.links.start_to_finish;
}
return type;
};
gantt.isLinkAllowed = function(from, to, from_start, to_start){
var link = null;
if(typeof(from) == "object"){
link = from;
}else{
link = {source:from, target:to, type: this._get_link_type(from_start, to_start)};
}
if(!link) return false;
if(!(link.source && link.target && link.type)) return false;
if(link.source == link.target) return false;
var res = true;
//any custom rules
if(this.checkEvent("onLinkValidation"))
res = this.callEvent("onLinkValidation", [link]);
return res;
};
gantt._render_link_element = function(link){
var dots = this._path_builder.get_points(link);
var drawer = gantt._drawer;
var lines = drawer.get_lines(dots);
var div = document.createElement("div");
var css = "gantt_task_link";
if(link.color){
css += " gantt_link_inline_color";
}
var cssTemplate = this.templates.link_class ? this.templates.link_class(link) : "";
if(cssTemplate){
css += " " + cssTemplate;
}
if(this.config.highlight_critical_path && this.isCriticalLink){
if(this.isCriticalLink(link))
css += " gantt_critical_link";
}
div.className = css;
div.setAttribute(gantt.config.link_attribute, link.id);
for(var i=0; i < lines.length; i++){
if(i == lines.length - 1){
lines[i].size -= gantt.config.link_arrow_size;
}
var el = drawer.render_line(lines[i], lines[i+1]);
if(link.color){
el.firstChild.style.backgroundColor = link.color;
}
div.appendChild(el);
}
var direction = lines[lines.length - 1].direction;
var endpoint = gantt._render_link_arrow(dots[dots.length - 1], direction);
if(link.color){
endpoint.style.borderColor = link.color;
}
div.appendChild(endpoint);
return div;
};
gantt._render_link_arrow = function(point, direction){
var div = document.createElement("div");
var drawer = gantt._drawer;
var top = point.y;
var left = point.x;
var size = gantt.config.link_arrow_size;
var line_width = gantt.config.row_height;
var className = "gantt_link_arrow gantt_link_arrow_" + direction;
switch (direction){
case drawer.dirs.right:
top -= (size - line_width)/2;
left -= size;
break;
case drawer.dirs.left:
top -= (size - line_width)/2;
break;
case drawer.dirs.up:
left -= size;
break;
case drawer.dirs.down:
top += size*2;
left -= size;
break;
default:
break;
}
div.style.cssText = [
"top:"+top + "px",
"left:"+left+'px'].join(';');
div.className = className;
return div;
};
gantt._drawer = {
current_pos:null,
dirs:{"left":'left',"right":'right',"up":'up', "down":'down'},
path:[],
clear:function(){
this.current_pos = null;
this.path = [];
},
point:function(pos){
this.current_pos = gantt.copy(pos);
},
get_lines:function(dots){
this.clear();
this.point(dots[0]);
for(var i=1; i from.x){
direction = this.dirs.right;
}else if (to.y > from.y){
direction = this.dirs.down;
}else {
direction = this.dirs.up;
}
return direction;
}
};
gantt._y_from_ind = function(index){
return (index)*gantt.config.row_height;
};
gantt._path_builder = {
path:[],
clear:function(){
this.path = [];
},
current:function(){
return this.path[this.path.length - 1];
},
point:function(next){
if(!next)
return this.current();
this.path.push(gantt.copy(next));
return next;
},
point_to:function(direction, diff, point){
if(!point)
point = gantt.copy(this.point());
else
point = {x:point.x, y:point.y};
var dir = gantt._drawer.dirs;
switch (direction){
case (dir.left):
point.x -= diff;
break;
case (dir.right):
point.x += diff;
break;
case (dir.up):
point.y -= diff;
break;
case (dir.down):
point.y += diff;
break;
default:
break;
}
return this.point(point);
},
get_points:function(link){
var pt = this.get_endpoint(link);
var xy = gantt.config;
var dy = pt.e_y - pt.y;
var dx = pt.e_x - pt.x;
var dir = gantt._drawer.dirs;
this.clear();
this.point({x: pt.x, y : pt.y});
var shiftX = 2*xy.link_arrow_size;//just random size for first line
var forward = (pt.e_x > pt.x);
if(link.type == gantt.config.links.start_to_start){
this.point_to(dir.left, shiftX);
if(forward){
this.point_to(dir.down, dy);
this.point_to(dir.right, dx);
}else{
this.point_to(dir.right, dx);
this.point_to(dir.down, dy);
}
this.point_to(dir.right, shiftX);
}else if(link.type == gantt.config.links.finish_to_start){
forward = (pt.e_x > (pt.x + 2*shiftX));
this.point_to(dir.right, shiftX);
if(forward){
dx -= shiftX;
this.point_to(dir.down, dy);
this.point_to(dir.right, dx);
}else{
dx -= 2*shiftX;
var sign = dy > 0 ? 1 : -1;
this.point_to(dir.down, sign * (xy.row_height/2));
this.point_to(dir.right, dx);
this.point_to(dir.down, sign * ( Math.abs(dy) - (xy.row_height/2)));
this.point_to(dir.right, shiftX);
}
}else if(link.type == gantt.config.links.finish_to_finish){
this.point_to(dir.right, shiftX);
if(forward){
this.point_to(dir.right, dx);
this.point_to(dir.down, dy);
}else{
this.point_to(dir.down, dy);
this.point_to(dir.right, dx);
}
this.point_to(dir.left, shiftX);
}else if(link.type == gantt.config.links.start_to_finish){
forward = (pt.e_x > (pt.x - 2*shiftX));
this.point_to(dir.left, shiftX);
if(!forward){
dx += shiftX;
this.point_to(dir.down, dy);
this.point_to(dir.right, dx);
}else{
dx += 2*shiftX;
var sign = dy > 0 ? 1 : -1;
this.point_to(dir.down, sign * (xy.row_height/2));
this.point_to(dir.right, dx);
this.point_to(dir.down, sign * ( Math.abs(dy) - (xy.row_height/2)));
this.point_to(dir.left, shiftX);
}
}
return this.path;
},
get_endpoint : function(link){
var types = gantt.config.links;
var from_start = false, to_start = false;
if(link.type == types.start_to_start){
from_start = to_start = true;
}else if(link.type == types.finish_to_finish){
from_start = to_start = false;
}else if(link.type == types.finish_to_start){
from_start = false;
to_start = true;
}else if(link.type == types.start_to_finish){
from_start = true;
to_start = false;
}else{
gantt.assert(false, "Invalid link type");
}
var from = gantt._get_task_visible_pos(gantt._pull[link.source], from_start);
var to = gantt._get_task_visible_pos(gantt._pull[link.target], to_start);
return {
x : from.x,
e_x : to.x,
y : from.y ,
e_y : to.y
};
}
};
gantt._init_links_dnd = function() {
var dnd = new gantt._DnD(this.$task_bars, { sensitivity : 0, updates_per_second : 60 }),
start_marker = "task_left",
end_marker = "task_right",
link_edge_marker = "gantt_link_point",
link_landing_hover_area = "gantt_link_control";
dnd.attachEvent("onBeforeDragStart", gantt.bind(function(obj,e) {
var target = (e.target||e.srcElement);
resetDndState();
if(gantt.getState().drag_id)
return false;
if(gantt._locate_css(target, link_edge_marker)){
if(gantt._locate_css(target, start_marker))
gantt._link_source_task_start = true;
var sid = gantt._link_source_task = this.locate(e);
var t = gantt.getTask(sid);
if(gantt._is_readonly(t)){
resetDndState();
return false;
}
var shift = 0;
if(gantt._get_safe_type(t.type) == gantt.config.types.milestone){
shift = (gantt._get_visible_milestone_width() - gantt._get_milestone_width())/2;
}
this._dir_start = getLinePos(t, !!gantt._link_source_task_start, shift);
return true;
}else{
return false;
}
}, this));
dnd.attachEvent("onAfterDragStart", gantt.bind(function(obj,e) {
updateMarkedHtml(dnd.config.marker);
}, this));
function getLinePos(task, to_start, shift){
var pos = gantt._get_task_pos(task, !!to_start);
pos.y += gantt._get_task_height()/2;
shift = shift || 0;
pos.x += (to_start ? -1 : 1)*shift;
return pos;
}
dnd.attachEvent("onDragMove", gantt.bind(function(obj,e) {
var dd = dnd.config;
var pos = dnd.getPosition(e);
advanceMarker(dd.marker, pos);
var landing = gantt._is_link_drop_area(e);
var prevTarget = gantt._link_target_task;
var prevLanding = gantt._link_landing;
var prevToStart = gantt._link_target_task_start;
var targ = gantt.locate(e),
to_start = true;
if(landing){
//refreshTask
to_start = !gantt._locate_css(e, end_marker);
landing = !!targ;
}
gantt._link_target_task = targ;
gantt._link_landing = landing;
gantt._link_target_task_start = to_start;
if(landing){
var t = gantt.getTask(targ);
var node = gantt._locate_css(e, link_landing_hover_area);
var shift = 0;
if(node){
shift = Math.floor(node.offsetWidth / 2);
}
this._dir_end = getLinePos(t, !!gantt._link_target_task_start,shift);
}else{
this._dir_end = gantt._get_mouse_pos(e);
}
var targetChanged = !(prevLanding == landing && prevTarget == targ && prevToStart == to_start);
if(targetChanged){
if(prevTarget)
gantt.refreshTask(prevTarget, false);
if(targ)
gantt.refreshTask(targ, false);
}
if(targetChanged){
updateMarkedHtml(dd.marker);
}
showDirectingLine(this._dir_start.x, this._dir_start.y, this._dir_end.x, this._dir_end.y);
return true;
}, this));
dnd.attachEvent("onDragEnd", gantt.bind(function() {
var drag = getDndState();
if(drag.from && drag.to && drag.from != drag.to){
var type = gantt._get_link_type(drag.from_start, drag.to_start);
var link = {source : drag.from, target: drag.to, type:type};
if(link.type && gantt.isLinkAllowed(link))
gantt.addLink(link);
}
resetDndState();
if(drag.from)
gantt.refreshTask(drag.from, false);
if(drag.to)
gantt.refreshTask(drag.to, false);
removeDirectionLine();
}, this));
function updateMarkedHtml(marker){
var link = getDndState();
var css = ["gantt_link_tooltip"];
if(link.from && link.to){
if(gantt.isLinkAllowed(link.from, link.to, link.from_start, link.to_start)){
css.push("gantt_allowed_link");
}else{
css.push("gantt_invalid_link");
}
}
var className = gantt.templates.drag_link_class(link.from, link.from_start, link.to, link.to_start);
if(className)
css.push(className);
var html = "