This repository has been archived on 2025-06-21. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
suitedesk/libraries/dhtmlxgantt/sources/dhtmlxgantt.js

9424 lines
No EOL
260 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
@license
dhtmlxGantt v.3.2.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.
*/
if (typeof(window.dhx4) == "undefined") {
window.dhx4 = {
version: "4.1.3",
skin: null, // allow to be set by user
skinDetect: function(comp) {
return {10:"dhx_skyblue",20:"dhx_web",30:"dhx_terrace"}[this.readFromCss(comp+"_skin_detect")]||null;
},
// read value from css
readFromCss: function(className, property) {
var t = document.createElement("DIV");
t.className = className;
if (document.body.firstChild != null) document.body.insertBefore(t, document.body.firstChild); else document.body.appendChild(t);
var w = t[property||"offsetWidth"];
t.parentNode.removeChild(t);
t = null;
return w;
},
// id manager
lastId: 1,
newId: function() {
return this.lastId++;
},
// z-index manager
zim: {
data: {},
step: 5,
first: function() {
return 100;
},
last: function() {
var t = this.first();
for (var a in this.data) t = Math.max(t, this.data[a]);
return t;
},
reserve: function(id) {
this.data[id] = this.last()+this.step;
return this.data[id];
},
clear: function(id) {
if (this.data[id] != null) {
this.data[id] = null;
delete this.data[id];
}
}
},
// string to boolean
s2b: function(r) {
if (typeof(r) == "string") r = r.toLowerCase();
return (r == true || r == 1 || r == "true" || r == "1" || r == "yes" || r == "y");
},
// string to json
s2j: function(s) {
var obj = null;
dhx4.temp = null;
try { eval("dhx4.temp="+s); } catch(e) { dhx4.temp = null; }
obj = dhx4.temp;
dhx4.temp = null;
return obj;
},
// absolute top/left position on screen
absLeft: function(obj) {
if (typeof(obj) == "string") obj = document.getElementById(obj);
return this.getOffset(obj).left;
},
absTop: function(obj) {
if (typeof(obj) == "string") obj = document.getElementById(obj);
return this.getOffset(obj).top;
},
_aOfs: function(elem) {
var top = 0, left = 0;
while (elem) {
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
}
return {top: top, left: left};
},
_aOfsRect: function(elem) {
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
},
getOffset: function(elem) {
if (elem.getBoundingClientRect) {
return this._aOfsRect(elem);
} else {
return this._aOfs(elem);
}
},
// copy obj
_isObj: function(k) {
return (k != null && typeof(k) == "object" && typeof(k.length) == "undefined");
},
_copyObj: function(r) {
if (this._isObj(r)) {
var t = {};
for (var a in r) {
if (typeof(r[a]) == "object" && r[a] != null) t[a] = this._copyObj(r[a]); else t[a] = r[a];
}
} else {
var t = [];
for (var a=0; a<r.length; a++) {
if (typeof(r[a]) == "object" && r[a] != null) t[a] = this._copyObj(r[a]); else t[a] = r[a];
}
}
return t;
},
// screen dim
screenDim: function() {
var isIE = (navigator.userAgent.indexOf("MSIE") >= 0);
var dim = {};
dim.left = document.body.scrollLeft;
dim.right = dim.left+(window.innerWidth||document.body.clientWidth);
dim.top = Math.max((isIE?document.documentElement:document.getElementsByTagName("html")[0]).scrollTop, document.body.scrollTop);
dim.bottom = dim.top+(isIE?Math.max(document.documentElement.clientHeight||0,document.documentElement.offsetHeight||0):window.innerHeight);
return dim;
},
// input/textarea range selection
selectTextRange: function(inp, start, end) {
inp = (typeof(inp)=="string"?document.getElementById(inp):inp);
var len = inp.value.length;
start = Math.max(Math.min(start, len), 0);
end = Math.min(end, len);
if (inp.setSelectionRange) {
try {inp.setSelectionRange(start, end);} catch(e){}; // combo in grid under IE requires try/catch
} else if (inp.createTextRange) {
var range = inp.createTextRange();
range.moveStart("character", start);
range.moveEnd("character", end-len);
try {range.select();} catch(e){};
}
},
// transition
transData: null,
transDetect: function() {
if (this.transData == null) {
this.transData = {transProp: false, transEv: null};
// transition, MozTransition, WebkitTransition, msTransition, OTransition
var k = {
"MozTransition": "transitionend",
"WebkitTransition": "webkitTransitionEnd",
"OTransition": "oTransitionEnd",
"msTransition": "transitionend",
"transition": "transitionend"
};
for (var a in k) {
if (this.transData.transProp == false && document.documentElement.style[a] != null) {
this.transData.transProp = a;
this.transData.transEv = k[a];
}
}
k = null;
}
return this.transData;
},
// xml parser
_xmlNodeValue: function(node) {
var value = "";
for (var q=0; q<node.childNodes.length; q++) {
value += (node.childNodes[q].nodeValue!=null?node.childNodes[q].nodeValue.toString().replace(/^[\n\r\s]{0,}/,"").replace(/[\n\r\s]{0,}$/,""):"");
}
return value;
}
};
// browser
window.dhx4.isIE = (navigator.userAgent.indexOf("MSIE") >= 0 || navigator.userAgent.indexOf("Trident") >= 0);
window.dhx4.isIE6 = (window.XMLHttpRequest == null && navigator.userAgent.indexOf("MSIE") >= 0);
window.dhx4.isIE7 = (navigator.userAgent.indexOf("MSIE 7.0") >= 0 && navigator.userAgent.indexOf("Trident") < 0);
window.dhx4.isIE8 = (navigator.userAgent.indexOf("MSIE 8.0") >= 0 && navigator.userAgent.indexOf("Trident") >= 0);
window.dhx4.isOpera = (navigator.userAgent.indexOf("Opera") >= 0);
window.dhx4.isChrome = (navigator.userAgent.indexOf("Chrome") >= 0);
window.dhx4.isKHTML = (navigator.userAgent.indexOf("Safari") >= 0 || navigator.userAgent.indexOf("Konqueror") >= 0);
window.dhx4.isFF = (navigator.userAgent.indexOf("Firefox") >= 0);
window.dhx4.isIPad = (navigator.userAgent.search(/iPad/gi) >= 0);
};
if (typeof(window.dhx4.ajax) == "undefined") {
window.dhx4.ajax = {
// if false - dhxr param will added to prevent caching on client side (default),
// if true - do not add extra params
cache: false,
// default method for load/loadStruct, post/get allowed
// get - since 4.1.1, this should fix 412 error for macos safari
method: "get",
parse: function(data) {
if (typeof data !== "string") return data;
data = data.replace(/^[\s]+/,"");
if (window.DOMParser && !dhx4.isIE) { // ff,ie9
var obj = (new window.DOMParser()).parseFromString(data, "text/xml");
} else if (window.ActiveXObject !== window.undefined) {
var obj = new window.ActiveXObject("Microsoft.XMLDOM");
obj.async = "false";
obj.loadXML(data);
}
return obj;
},
xmltop: function(tagname, xhr, obj) {
if (typeof xhr.status == "undefined" || xhr.status < 400) {
var xml = (!xhr.responseXML) ? dhx4.ajax.parse(xhr.responseText || xhr) : (xhr.responseXML || xhr);
if (xml && xml.documentElement !== null && !xml.getElementsByTagName("parsererror").length) {
return xml.getElementsByTagName(tagname)[0];
}
}
if (obj !== -1) dhx4.callEvent("onLoadXMLError",["Incorrect XML", arguments[1], obj]);
return document.createElement("DIV");
},
xpath: function(xpathExp, docObj) {
if (!docObj.nodeName) docObj = docObj.responseXML || docObj;
if (dhx4.isIE) {
return docObj.selectNodes(xpathExp)||[];
} else {
var rows = [];
var first;
var col = (docObj.ownerDocument||docObj).evaluate(xpathExp, docObj, null, XPathResult.ANY_TYPE, null);
while (first = col.iterateNext()) rows.push(first);
return rows;
}
},
query: function(config) {
dhx4.ajax._call(
(config.method || "GET"),
config.url,
config.data || "",
(config.async || true),
config.callback,
null,
config.headers
);
},
get: function(url, onLoad) {
this._call("GET", url, null, true, onLoad);
},
getSync: function(url) {
return this._call("GET", url, null, false);
},
put: function(url, postData, onLoad) {
this._call("PUT", url, postData, true, onLoad);
},
del: function(url, postData, onLoad) {
this._call("DELETE", url, postData, true, onLoad);
},
post: function(url, postData, onLoad) {
if (arguments.length == 1) {
postData = "";
} else if (arguments.length == 2 && (typeof(postData) == "function" || typeof(window[postData]) == "function")) {
onLoad = postData;
postData = "";
} else {
postData = String(postData);
}
this._call("POST", url, postData, true, onLoad);
},
postSync: function(url, postData) {
postData = (postData == null ? "" : String(postData));
return this._call("POST", url, postData, false);
},
getLong: function(url, onLoad) {
this._call("GET", url, null, true, onLoad, {url:url});
},
postLong: function(url, postData, onLoad) {
if (arguments.length == 2 && (typeof(postData) == "function" || typeof(window[postData]))) {
onLoad = postData;
postData = "";
}
this._call("POST", url, postData, true, onLoad, {url:url, postData:postData});
},
_call: function(method, url, postData, async, onLoad, longParams, headers) {
var t = (window.XMLHttpRequest && !dhx4.isIE ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
var isQt = (navigator.userAgent.match(/AppleWebKit/) != null && navigator.userAgent.match(/Qt/) != null && navigator.userAgent.match(/Safari/) != null);
if (async == true) {
t.onreadystatechange = function() {
if ((t.readyState == 4) || (isQt == true && t.readyState == 3)) { // what for long response and status 404?
if (t.status != 200 || t.responseText == "")
if (!dhx4.callEvent("onAjaxError", [t])) return;
window.setTimeout(function(){
if (typeof(onLoad) == "function") {
onLoad.apply(window, [{xmlDoc:t}]); // dhtmlx-compat, response.xmlDoc.responseXML/responseText
}
if (longParams != null) {
if (typeof(longParams.postData) != "undefined") {
dhx4.ajax.postLong(longParams.url, longParams.postData, onLoad);
} else {
dhx4.ajax.getLong(longParams.url, onLoad);
}
}
onLoad = null;
t = null;
},1);
}
}
}
if (method == "GET" && this.cache != true) {
url += (url.indexOf("?")>=0?"&":"?")+"dhxr"+new Date().getTime()+"=1";
}
t.open(method, url, async);
if (headers){
for (var key in headers)
t.setRequestHeader(key, headers[key]);
} else if (method.toUpperCase() == "POST" || method == "PUT" || method == "DELETE") {
t.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
} else if (method == "GET") {
postData = null;
}
t.setRequestHeader("X-Requested-With", "XMLHttpRequest");
t.send(postData);
if (!async) return {xmlDoc:t}; // dhtmlx-compat, response.xmlDoc.responseXML/responseText
}
};
};
if (typeof(window.dhx4._eventable) == "undefined") {
window.dhx4._eventable = function(obj, mode) {
if (mode == "clear") {
obj.detachAllEvents();
obj.dhxevs = null;
obj.attachEvent = null;
obj.detachEvent = null;
obj.checkEvent = null;
obj.callEvent = null;
obj.detachAllEvents = null;
obj = null;
return;
}
obj.dhxevs = { data: {} };
obj.attachEvent = function(name, func) {
name = String(name).toLowerCase();
if (!this.dhxevs.data[name]) this.dhxevs.data[name] = {};
var eventId = window.dhx4.newId();
this.dhxevs.data[name][eventId] = func;
return eventId;
}
obj.detachEvent = function(eventId) {
for (var a in this.dhxevs.data) {
var k = 0;
for (var b in this.dhxevs.data[a]) {
if (b == eventId) {
this.dhxevs.data[a][b] = null;
delete this.dhxevs.data[a][b];
} else {
k++;
}
}
if (k == 0) {
this.dhxevs.data[a] = null;
delete this.dhxevs.data[a];
}
}
}
obj.checkEvent = function(name) {
name = String(name).toLowerCase();
return (this.dhxevs.data[name] != null);
}
obj.callEvent = function(name, params) {
name = String(name).toLowerCase();
if (this.dhxevs.data[name] == null) return true;
var r = true;
for (var a in this.dhxevs.data[name]) {
r = this.dhxevs.data[name][a].apply(this, params) && r;
}
return r;
}
obj.detachAllEvents = function() {
for (var a in this.dhxevs.data) {
for (var b in this.dhxevs.data[a]) {
this.dhxevs.data[a][b] = null;
delete this.dhxevs.data[a][b];
}
this.dhxevs.data[a] = null;
delete this.dhxevs.data[a];
}
}
obj = null;
};
dhx4._eventable(dhx4);
};
if (typeof(window.dhtmlx) == "undefined") {
window.dhtmlx={
extend:function(a, b){
for (var key in b)
if (!a[key])
a[key]=b[key];
return a;
},
extend_api:function(name,map,ext){
var t = window[name];
if (!t) return; //component not defined
window[name]=function(obj){
if (obj && typeof obj == "object" && !obj.tagName){
var that = t.apply(this,(map._init?map._init(obj):arguments));
//global settings
for (var a in dhtmlx)
if (map[a]) this[map[a]](dhtmlx[a]);
//local settings
for (var a in obj){
if (map[a]) this[map[a]](obj[a]);
else if (a.indexOf("on")===0){
this.attachEvent(a,obj[a]);
}
}
} else
var that = t.apply(this,arguments);
if (map._patch) map._patch(this);
return that||this;
};
window[name].prototype=t.prototype;
if (ext)
dhtmlx.extend(window[name].prototype,ext);
},
url:function(str){
if (str.indexOf("?") != -1)
return "&";
else
return "?";
}
};
};
_isFF = false;
_isIE = false;
_isOpera = false;
_isKHTML = false;
_isMacOS = false;
_isChrome = false;
_FFrv = false;
_KHTMLrv = false;
_OperaRv = false;
if (navigator.userAgent.indexOf('Macintosh') != -1)
_isMacOS=true;
if (navigator.userAgent.toLowerCase().indexOf('chrome')>-1)
_isChrome=true;
if ((navigator.userAgent.indexOf('Safari') != -1)||(navigator.userAgent.indexOf('Konqueror') != -1)){
_KHTMLrv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Safari')+7, 5));
if (_KHTMLrv > 525){ //mimic FF behavior for Safari 3.1+
_isFF=true;
_FFrv = 1.9;
} else
_isKHTML=true;
} else if (navigator.userAgent.indexOf('Opera') != -1){
_isOpera=true;
_OperaRv=parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Opera')+6, 3));
}
else if (navigator.appName.indexOf("Microsoft") != -1){
_isIE=true;
if ((navigator.appVersion.indexOf("MSIE 8.0")!= -1 ||
navigator.appVersion.indexOf("MSIE 9.0")!= -1 ||
navigator.appVersion.indexOf("MSIE 10.0")!= -1 ||
document.documentMode > 7) &&
document.compatMode != "BackCompat"){
_isIE=8;
}
} else if (navigator.appName == 'Netscape' && navigator.userAgent.indexOf("Trident") != -1){
//ie11
_isIE=8;
} else {
_isFF=true;
_FFrv = parseFloat(navigator.userAgent.split("rv:")[1])
}
if (typeof(window.dhtmlxEvent) == "undefined") {
function dhtmlxEvent(el, event, handler){
if (el.addEventListener)
el.addEventListener(event, handler, false);
else if (el.attachEvent)
el.attachEvent("on"+event, handler);
}
};
if (dhtmlxEvent.touchDelay == null) {
dhtmlxEvent.touchDelay = 2000;
};
if (typeof(dhtmlxEvent.initTouch) == "undefined") {
dhtmlxEvent.initTouch = function(){
var longtouch;
var target;
var tx, ty;
dhtmlxEvent(document.body, "touchstart", function(ev){
target = ev.touches[0].target;
tx = ev.touches[0].clientX;
ty = ev.touches[0].clientY;
longtouch = window.setTimeout(touch_event, dhtmlxEvent.touchDelay);
});
function touch_event(){
if (target){
var ev = document.createEvent("HTMLEvents"); // for chrome and firefox
ev.initEvent("dblclick", true, true);
target.dispatchEvent(ev);
longtouch = target = null;
}
};
dhtmlxEvent(document.body, "touchmove", function(ev){
if (longtouch){
if (Math.abs(ev.touches[0].clientX - tx) > 50 || Math.abs(ev.touches[0].clientY - ty) > 50 ){
window.clearTimeout(longtouch);
longtouch = target = false;
}
}
});
dhtmlxEvent(document.body, "touchend", function(ev){
if (longtouch){
window.clearTimeout(longtouch);
longtouch = target = false;
}
});
dhtmlxEvent.initTouch = function(){};
};
};
if(!window.dhtmlx)
window.dhtmlx = {};
(function(){
var _dhx_msg_cfg = null;
function callback(config, result){
var usercall = config.callback;
modality(false);
config.box.parentNode.removeChild(config.box);
_dhx_msg_cfg = config.box = null;
if (usercall)
usercall(result);
}
function modal_key(e){
if (_dhx_msg_cfg){
e = e||event;
var code = e.which||event.keyCode;
if (dhtmlx.message.keyboard){
if (code == 13 || code == 32)
callback(_dhx_msg_cfg, true);
if (code == 27)
callback(_dhx_msg_cfg, false);
}
if (e.preventDefault)
e.preventDefault();
return !(e.cancelBubble = true);
}
}
if (document.attachEvent)
document.attachEvent("onkeydown", modal_key);
else
document.addEventListener("keydown", modal_key, true);
function modality(mode){
if(!modality.cover){
modality.cover = document.createElement("DIV");
//necessary for IE only
modality.cover.onkeydown = modal_key;
modality.cover.className = "dhx_modal_cover";
document.body.appendChild(modality.cover);
}
var height = document.body.scrollHeight;
modality.cover.style.display = mode?"inline-block":"none";
}
function button(text, result){
var button_css = "dhtmlx_"+text.toLowerCase().replace(/ /g, "_")+"_button"; // dhtmlx_ok_button, dhtmlx_click_me_button
return "<div class='dhtmlx_popup_button "+button_css+"' result='"+result+"' ><div>"+text+"</div></div>";
}
function info(text){
if (!t.area){
t.area = document.createElement("DIV");
t.area.className = "dhtmlx_message_area";
t.area.style[t.position]="5px";
document.body.appendChild(t.area);
}
t.hide(text.id);
var message = document.createElement("DIV");
message.innerHTML = "<div>"+text.text+"</div>";
message.className = "dhtmlx-info dhtmlx-" + text.type;
message.onclick = function(){
t.hide(text.id);
text = null;
};
if (t.position == "bottom" && t.area.firstChild)
t.area.insertBefore(message,t.area.firstChild);
else
t.area.appendChild(message);
if (text.expire > 0)
t.timers[text.id]=window.setTimeout(function(){
t.hide(text.id);
}, text.expire);
t.pull[text.id] = message;
message = null;
return text.id;
}
function _boxStructure(config, ok, cancel){
var box = document.createElement("DIV");
box.className = " dhtmlx_modal_box dhtmlx-"+config.type;
box.setAttribute("dhxbox", 1);
var inner = '';
if (config.width)
box.style.width = config.width;
if (config.height)
box.style.height = config.height;
if (config.title)
inner+='<div class="dhtmlx_popup_title">'+config.title+'</div>';
inner+='<div class="dhtmlx_popup_text"><span>'+(config.content?'':config.text)+'</span></div><div class="dhtmlx_popup_controls">';
if (ok)
inner += button(config.ok || "OK", true);
if (cancel)
inner += button(config.cancel || "Cancel", false);
if (config.buttons){
for (var i=0; i<config.buttons.length; i++)
inner += button(config.buttons[i],i);
}
inner += '</div>';
box.innerHTML = inner;
if (config.content){
var node = config.content;
if (typeof node == "string")
node = document.getElementById(node);
if (node.style.display == 'none')
node.style.display = "";
box.childNodes[config.title?1:0].appendChild(node);
}
box.onclick = function(e){
e = e ||event;
var source = e.target || e.srcElement;
if (!source.className) source = source.parentNode;
if (source.className.split(" ")[0] == "dhtmlx_popup_button"){
var result = source.getAttribute("result");
result = (result == "true")||(result == "false"?false:result);
callback(config, result);
}
};
config.box = box;
if (ok||cancel)
_dhx_msg_cfg = config;
return box;
}
function _createBox(config, ok, cancel){
var box = config.tagName ? config : _boxStructure(config, ok, cancel);
if (!config.hidden)
modality(true);
document.body.appendChild(box);
var x = Math.abs(Math.floor(((window.innerWidth||document.documentElement.offsetWidth) - box.offsetWidth)/2));
var y = Math.abs(Math.floor(((window.innerHeight||document.documentElement.offsetHeight) - box.offsetHeight)/2));
if (config.position == "top")
box.style.top = "-3px";
else
box.style.top = y+'px';
box.style.left = x+'px';
//necessary for IE only
box.onkeydown = modal_key;
box.focus();
if (config.hidden)
dhtmlx.modalbox.hide(box);
return box;
}
function alertPopup(config){
return _createBox(config, true, false);
}
function confirmPopup(config){
return _createBox(config, true, true);
}
function boxPopup(config){
return _createBox(config);
}
function box_params(text, type, callback){
if (typeof text != "object"){
if (typeof type == "function"){
callback = type;
type = "";
}
text = {text:text, type:type, callback:callback };
}
return text;
}
function params(text, type, expire, id){
if (typeof text != "object")
text = {text:text, type:type, expire:expire, id:id};
text.id = text.id||t.uid();
text.expire = text.expire||t.expire;
return text;
}
dhtmlx.alert = function(){
var text = box_params.apply(this, arguments);
text.type = text.type || "confirm";
return alertPopup(text);
};
dhtmlx.confirm = function(){
var text = box_params.apply(this, arguments);
text.type = text.type || "alert";
return confirmPopup(text);
};
dhtmlx.modalbox = function(){
var text = box_params.apply(this, arguments);
text.type = text.type || "alert";
return boxPopup(text);
};
dhtmlx.modalbox.hide = function(node){
while (node && node.getAttribute && !node.getAttribute("dhxbox"))
node = node.parentNode;
if (node){
node.parentNode.removeChild(node);
modality(false);
}
};
var t = dhtmlx.message = function(text, type, expire, id){
text = params.apply(this, arguments);
text.type = text.type||"info";
var subtype = text.type.split("-")[0];
switch (subtype){
case "alert":
return alertPopup(text);
case "confirm":
return confirmPopup(text);
case "modalbox":
return boxPopup(text);
default:
return info(text);
}
};
t.seed = (new Date()).valueOf();
t.uid = function(){return t.seed++;};
t.expire = 4000;
t.keyboard = true;
t.position = "top";
t.pull = {};
t.timers = {};
t.hideAll = function(){
for (var key in t.pull)
t.hide(key);
};
t.hide = function(id){
var obj = t.pull[id];
if (obj && obj.parentNode){
window.setTimeout(function(){
obj.parentNode.removeChild(obj);
obj = null;
},2000);
obj.className+=" hidden";
if(t.timers[id])
window.clearTimeout(t.timers[id]);
delete t.pull[id];
}
};
})();
gantt = {
version:"3.2.0"
};
/*jsl:ignore*/
//import from dhtmlxcommon.js
function dhtmlxDetachEvent(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
*/
dhtmlxEventable=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")
ev=eval(ev);
if (ev)
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*/
dhtmlx.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<t.length; i++) {
if (object instanceof t[i])
result = i ? new t[i](object) : new t[i](); // first one is array
}
for (i in object) {
if (Object.prototype.hasOwnProperty.apply(object, [i]))
result[i] = dhtmlx.copy(object[i]);
}
}
return result || object;
};
dhtmlx.mixin = function(target, source, force){
for (var f in source)
if ((!target[f] || force)) target[f]=source[f];
return target;
};
dhtmlx.defined = function(obj) {
return typeof(obj) != "undefined";
};
dhtmlx.uid = function() {
if (!this._seed)
this._seed = (new Date()).valueOf();
this._seed++;
return this._seed;
};
//creates function with specified "this" pointer
dhtmlx.bind=function(functor, object){
if(functor.bind)
return functor.bind(object);
else
return function(){ return functor.apply(object,arguments); };
};
//returns position of html element on the page
gantt._get_position = function(elem) {
var top=0, left=0;
if (elem.getBoundingClientRect) { //HTML5 method
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
top = box.top + scrollTop - clientTop;
left = box.left + scrollLeft - clientLeft;
return { y: Math.round(top), x: Math.round(left), width:elem.offsetWidth, height:elem.offsetHeight };
} else { //fallback to naive approach
while(elem) {
top = top + parseInt(elem.offsetTop,10);
left = left + parseInt(elem.offsetLeft,10);
elem = elem.offsetParent;
}
return { y: top, x: left, width:elem.offsetWidth, height: elem.offsetHeight};
}
};
gantt._detectScrollSize = function(){
var div = document.createElement("div");
div.style.cssText="visibility:hidden;position:absolute;left:-1000px;width:100px;padding:0px;margin:0px;height:110px;min-height:100px;overflow-y:scroll;";
document.body.appendChild(div);
var width = div.offsetWidth-div.clientWidth;
document.body.removeChild(div);
return width;
};
if (window.dhtmlx){
if (!dhtmlx.attaches)
dhtmlx.attaches = {};
dhtmlx.attaches.attachGantt=function(start, end, gantt){
var obj = document.createElement("DIV");
obj.id = "gantt_"+dhtmlx.uid();
obj.style.width = "100%";
obj.style.height = "100%";
obj.cmp = "grid";
gantt = gantt || window.gantt;
document.body.appendChild(obj);
this.attachObject(obj.id);
var that = this.vs[this.av];
that.grid = gantt;
gantt.init(obj.id, start, end);
obj.firstChild.style.border = "none";
that.gridId = obj.id;
that.gridObj = obj;
var method_name="_viewRestore";
return this.vs[this[method_name]()].grid;
};
}
if (typeof(window.dhtmlXCellObject) != "undefined") {
dhtmlXCellObject.prototype.attachGantt=function(start, end, gantt){
var obj = document.createElement("DIV");
obj.id = "gantt_"+dhtmlx.uid();
obj.style.width = "100%";
obj.style.height = "100%";
obj.cmp = "grid";
document.body.appendChild(obj);
this.attachObject(obj.id);
gantt = gantt || window.gantt;
gantt.init(obj.id, start, end);
obj.firstChild.style.border = "none";
var method_name="_viewRestore";
obj = null;
this.callEvent("_onContentAttach",[]);
return this.dataObj;
};
}
dhtmlxEventable(gantt);
dhx4.ajax.cache = true;
gantt._click = {};
gantt._dbl_click = {};
gantt._context_menu = {};
gantt._on_click = function(e) {
e = e || window.event;
var trg = e.target || e.srcElement;
var id = gantt.locate(e);
var res = true;
if (id !== null){
res = !gantt.checkEvent("onTaskClick") || gantt.callEvent("onTaskClick", [id, e]);
}else{
gantt.callEvent("onEmptyClick", [e]);
}
if(res){
var default_action = gantt._find_ev_handler(e, trg, gantt._click, id);
if(!default_action)
return;
if(id && gantt.getTask(id) && gantt.config.select_task){
gantt.selectTask(id);
}
}
};
gantt._on_contextmenu = function(e){
e = e || window.event;
var src = e.target||e.srcElement,
taskId = gantt.locate(src),
linkId = gantt.locate(src, gantt.config.link_attribute);
var res = !gantt.checkEvent("onContextMenu") || gantt.callEvent("onContextMenu", [taskId, linkId, e]);
if(!res){
if(e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
}
return res;
};
gantt._find_ev_handler = function(e, trg, hash, id){
var res = true;
while (trg){
var css = trg.className || "";
if (css) {
css = css.split(" ");
for (var i = 0; i < css.length; i++) {
if (!css[i]) continue;
if (hash[css[i]]){
var handler = hash[css[i]].call(gantt, e, id, trg);
res = res && !(typeof handler != "undefined" && handler !== true);
}
}
}
trg=trg.parentNode;
}
return res;
};
gantt._on_dblclick = function(e) {
e = e || window.event;
var trg = e.target || e.srcElement;
var id = gantt.locate(e);
var res = !gantt.checkEvent("onTaskDblClick") || gantt.callEvent("onTaskDblClick", [id, e]);
if(res){
var default_action = gantt._find_ev_handler(e, trg, gantt._dbl_click, id);
if(!default_action)
return;
if (id !== null && gantt.getTask(id)){
if(res && gantt.config.details_on_dblclick){
gantt.showLightbox(id);
}
}
}
};
gantt._on_mousemove = function(e){
if (gantt.checkEvent("onMouseMove")){
var id = gantt.locate(e);
gantt._last_move_event = e;
gantt.callEvent("onMouseMove", [id,e]);
}
};
function dhtmlxDnD(obj, config) {
if(config){
this._settings = config;
}
dhtmlxEventable(this);
dhtmlxEvent(obj, "mousedown", dhtmlx.bind(function(e) {
config.original_target = {target : e.target || e.srcElement};
this.dragStart(obj, e);
}, this));
}
dhtmlxDnD.prototype = {
dragStart: function(obj, e) {
this.config = {
obj: obj,
marker: null,
started: false,
pos: this.getPosition(e),
sensitivity: 4
};
if(this._settings)
dhtmlx.mixin(this.config, this._settings, true);
var mousemove = dhtmlx.bind(function(e) { return this.dragMove(obj, e); }, this);
var scroll = dhtmlx.bind(function(e) { return this.dragScroll(obj, e); }, this);
var limited_mousemove = dhtmlx.bind(function(e) {
if(dhtmlx.defined(this.config.updates_per_second)){
if(!gantt._checkTimeout(this, this.config.updates_per_second))
return true;
}
return mousemove(e);
}, this);
var mouseup = dhtmlx.bind(function(e) {
dhtmlxDetachEvent(document.body, "mousemove", limited_mousemove);
dhtmlxDetachEvent(document.body, "mouseup", mouseup);
return this.dragEnd(obj);
}, this);
dhtmlxEvent(document.body, "mousemove", limited_mousemove);
dhtmlxEvent(document.body, "mouseup", mouseup);
document.body.className += " gantt_noselect";
},
dragMove: function(obj, e) {
if (!this.config.marker && !this.config.started) {
var pos = this.getPosition(e);
var diff_x = pos.x - this.config.pos.x;
var diff_y = pos.y - this.config.pos.y;
var distance = Math.sqrt(Math.pow(Math.abs(diff_x), 2) + Math.pow(Math.abs(diff_y), 2));
if (distance > 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 = dhtmlx.bind(function (e, id, trg) {
this.close(id);
return false;
}, this);
this._click.gantt_open = dhtmlx.bind(function (e, id, trg) {
this.open(id);
return false;
}, this);
this._click.gantt_row = dhtmlx.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 = dhtmlx.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);
} else if (this.config.sort) {
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(column, sort == "desc");
}
}, this);
if (!this.config.sort && this.config.order_branch) {
this._init_dnd();
}
this._click.gantt_add = dhtmlx.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];
if (last && this._get_grid_width() > width + col.width)
col.width = this._get_grid_width() - width;
width += col.width;
var sort = (this._sort && col.name == this._sort.name) ? ("<div class='gantt_sort gantt_" + this._sort.direction + "'></div>") : "";
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:" + (col.width - (last ? 1 : 0)) + "px;";
var label = (col.label || labels["column_" + col.name]);
label = label || "";
var cell = "<div class='" + cssClass + "' style='" + style + "' column_id='" + col.name + "'>" + label + sort + "</div>";
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 = "<div class='gantt_add'></div>";
} else {
if (col.template)
value = col.template(item);
else
value = item[col.name];
if (value instanceof Date)
value = this.templates.date_grid(value);
value = "<div class='gantt_tree_content'>" + value + "</div>";
}
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 (dhtmlx.defined(col.align))
style += "text-align:" + col.align + ";";
cell = "<div class='" + css + "' style='" + style + "'>" + tree + value + "</div>";
cells.push(cell);
}
var css = item.$index % 2 === 0 ? "" : " odd";
css += (item.$transparent) ? " gantt_transparent" : "";
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;
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.getTaskIndex = function (id) {
var branch = this.getChildren(this.getParent(id));
for (var i = 0; i < branch.length; i++)
if (branch[i] == id)
return i;
return -1;
};
gantt.getGlobalTaskIndex = function (id) {
var branch = this._order;
for (var i = 0; i < branch.length; i++)
if (branch[i] == id)
return i;
return -1;
};
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)
item.$level = this.calculateTaskLevel(item);
}
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 dhtmlxDnD(this.$grid_data, {updates_per_second: 60});
if (dhtmlx.defined(this.config.dnd_sensitivity))
dnd.config.sensitivity = this.config.dnd_sensitivity;
dnd.attachEvent("onBeforeDragStart", dhtmlx.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", dhtmlx.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 = dhtmlx.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", dhtmlx.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 target = document.elementFromPoint(pos.x - document.body.scrollLeft + 1, y - document.body.scrollTop);
var el = this.locate(target);
var item = this.getTask(dnd.config.id);
if (!this.isTaskExists(el)) {
el = dnd.lastTaskOfLevel(item.$level);
if (el == dnd.config.id) {
el = null;
}
}
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 (next.$level == item.$level && 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 (over.$level == item.$level && 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 && (prev.$level == item.$level) && (item.id != prev.id)){
this.moveTask(item.id, -1, this.getParent(prev.id));
}
}
return true;
}, this));
dnd.attachEvent("onDragEnd", dhtmlx.bind(function () {
var task = this.getTask(dnd.config.id);
if(this.callEvent("onBeforeRowDragEnd",[dnd.config.id, dnd.config.index, dnd.config.parent]) === false) {
this.moveTask(dnd.config.id, dnd.config.index, dnd.config.parent);
task.$drop_target = null;
}else{
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 = dhtmlx.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));
curr = gantt.date.add(curr, step, unit);
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;
dhtmlxEvent(data, "mousemove", dhtmlx.bind(function(e){
this.on_mouse_move(e||event);
}, this));
dhtmlxEvent(data, "mousedown", dhtmlx.bind(function(e){
this.on_mouse_down(e||event);
}, this));
dhtmlxEvent(data, "mouseup", dhtmlx.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 = dhtmlx.mixin({}, ev);
var copy = dhtmlx.mixin({}, ev);
this._handlers[drag.mode].apply(this, [copy, shift, drag]);
dhtmlx.mixin(ev, copy, true);
gantt._update_parents(drag.id, true);
gantt.callEvent("onTaskDrag", [ev.id, drag.mode, copy, original, e]);
dhtmlx.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._trim(src.className || "");
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 = dhtmlx.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, dhtmlx.copy(drag.obj), e])){
drag.obj._dhx_changed = false;
dhtmlx.mixin(ev, drag.obj, true);
gantt.updateTask(ev.id);
} else {
var drag_id = drag.id;
gantt._init_task_timing(ev);
this._fireEvent("after_finish", drag.mode, [drag_id, drag.mode, e]);
this.clear_drag_state();
gantt.updateTask(ev.id);
}
}
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;
}
},
_fireEvent:function(stage, mode, params){
dhtmlx.assert(this._events[stage], "Invalid stage:{" + stage + "}");
var trigger = this._events[stage][mode];
dhtmlx.assert(trigger, "Unknown after drop mode:{" + mode + "}");
dhtmlx.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 - line_width)/2;
break;
case drawer.dirs.down:
top -= size;
left -= (size - line_width)/2;
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 = dhtmlx.copy(pos);
},
get_lines:function(dots){
this.clear();
this.point(dots[0]);
for(var i=1; i<dots.length ; i++){
this.line_to(dots[i]);
}
return this.get_path();
},
line_to:function(pos){
var next = dhtmlx.copy(pos);
var prev = this.current_pos;
var line = this._get_line(prev, next);
this.path.push(line);
this.current_pos = next;
},
get_path:function(){
return this.path;
},
get_wrapper_sizes :function(v){
var res,
wrapper_size = gantt.config.link_wrapper_width,
line_size = gantt.config.link_line_width,
y = v.y + (gantt.config.row_height - wrapper_size)/2;
switch (v.direction){
case this.dirs.left:
res = { top : y,
height : wrapper_size,
lineHeight : wrapper_size,
left : v.x - v.size - wrapper_size/2 ,
width : v.size +wrapper_size};
break;
case this.dirs.right:
res = { top : y,
lineHeight : wrapper_size,
height : wrapper_size,
left : v.x - wrapper_size/2,
width : v.size + wrapper_size};
break;
case this.dirs.up:
res = { top : y - v.size,
lineHeight: v.size + wrapper_size,
height : v.size + wrapper_size,
left : v.x - wrapper_size/2,
width : wrapper_size};
break;
case this.dirs.down:
res = { top : y,
lineHeight: v.size + wrapper_size,
height : v.size + wrapper_size,
left : v.x - wrapper_size/2,
width : wrapper_size};
break;
default:
break;
}
return res;
},
get_line_sizes : function(v){
var res,
line_size = gantt.config.link_line_width,
wrapper_size = gantt.config.link_wrapper_width,
size = v.size + line_size;
switch (v.direction){
case this.dirs.left:
case this.dirs.right:
res = {
height : line_size,
width : size,
marginTop: (wrapper_size - line_size)/2,
marginLeft: (wrapper_size - line_size)/2
};
break;
case this.dirs.up:
case this.dirs.down:
res = {
height : size,
width : line_size,
marginTop: (wrapper_size - line_size)/2,
marginLeft: (wrapper_size - line_size)/2
};
break;
default:
break;
}
return res;
},
render_line : function(v){
var pos = this.get_wrapper_sizes(v);
var wrapper = document.createElement("div");
wrapper.style.cssText = [
"top:" + pos.top + "px",
"left:" + pos.left + "px",
"height:" + pos.height + "px",
"width:" + pos.width + "px"
].join(';');
wrapper.className = "gantt_line_wrapper";
var innerPos = this.get_line_sizes(v);
var inner = document.createElement("div");
inner.style.cssText = [
"height:" + innerPos.height + "px",
"width:" + innerPos.width + "px",
"margin-top:" + innerPos.marginTop + "px",
"margin-left:" + innerPos.marginLeft + "px"
].join(";");
inner.className = "gantt_link_line_" + v.direction;
wrapper.appendChild(inner);
return wrapper;
},
_get_line:function(from, to){
var direction = this.get_direction(from, to);
var vect = {
x : from.x,
y : from.y,
direction : this.get_direction(from, to)
};
if(direction == this.dirs.left || direction == this.dirs.right){
vect.size = Math.abs(from.x - to.x);
}else{
vect.size = Math.abs(from.y - to.y);
}
return vect;
},
get_direction:function(from, to){
var direction = 0;
if(to.x < from.x){
direction = this.dirs.left;
}else if (to.x > 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(dhtmlx.copy(next));
return next;
},
point_to:function(direction, diff, point){
if(!point)
point = dhtmlx.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{
dhtmlx.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 dhtmlxDnD(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", dhtmlx.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", dhtmlx.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", dhtmlx.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", dhtmlx.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 = "<div class='"+className+ "'>" +
gantt.templates.drag_link(link.from, link.from_start, link.to, link.to_start) +
"</div>";
marker.innerHTML = html;
}
function advanceMarker(marker, pos){
marker.style.left = pos.x + 5 + "px";
marker.style.top = pos.y + 5 + "px";
}
function getDndState(){
return { from : gantt._link_source_task,
to : gantt._link_target_task,
from_start : gantt._link_source_task_start,
to_start : gantt._link_target_task_start};
}
function resetDndState(){
gantt._link_source_task =
gantt._link_source_task_start =
gantt._link_target_task = null;
gantt._link_target_task_start = true;
}
function showDirectingLine(s_x, s_y, e_x, e_y){
var div = getDirectionLine();
var link = getDndState();
var css = ["gantt_link_direction"];
if(gantt.templates.link_direction_class){
css.push(gantt.templates.link_direction_class(link.from, link.from_start, link.to, link.to_start));
}
var dist =Math.sqrt( (Math.pow(e_x - s_x, 2)) + (Math.pow(e_y - s_y, 2)) );
dist = Math.max(0, dist - 3);
if(!dist)
return;
div.className = css.join(" ");
var tan = (e_y - s_y)/(e_x - s_x),
angle = Math.atan(tan);
if(coordinateCircleQuarter(s_x, e_x, s_y, e_y) == 2){
angle += Math.PI;
}else if(coordinateCircleQuarter(s_x, e_x, s_y, e_y) == 3){
angle -= Math.PI;
}
var sin = Math.sin(angle),
cos = Math.cos(angle),
top = Math.round(s_y),
left = Math.round(s_x);
var style = [
"-webkit-transform: rotate("+angle+"rad)",
"-moz-transform: rotate("+angle+"rad)",
"-ms-transform: rotate("+angle+"rad)",
"-o-transform: rotate("+angle+"rad)",
"transform: rotate("+angle+"rad)",
"width:" + Math.round(dist) + "px"
];
if(window.navigator.userAgent.indexOf("MSIE 8.0") != -1){
//ms-filter breaks styles in ie9, so add it only for 8th
style.push("-ms-filter: \"" + ieTransform(sin, cos) + "\"");
var shiftLeft = Math.abs(Math.round(s_x - e_x)),
shiftTop = Math.abs(Math.round(e_y - s_y));
//fix rotation axis
switch(coordinateCircleQuarter(s_x, e_x, s_y, e_y)){
case 1:
top -= shiftTop;
break;
case 2:
left -= shiftLeft;
top -= shiftTop;
break;
case 3:
left -= shiftLeft;
break;
default:
break;
}
}
style.push("top:" + top + "px");
style.push("left:" + left + "px");
div.style.cssText = style.join(";");
}
function ieTransform(sin, cos){
return "progid:DXImageTransform.Microsoft.Matrix("+
"M11 = "+cos+","+
"M12 = -"+sin+","+
"M21 = "+sin+","+
"M22 = "+cos+","+
"SizingMethod = 'auto expand'"+
")";
}
function coordinateCircleQuarter(sX, eX, sY, eY){
if(eX >= sX){
if(eY <= sY){
return 1;
}else{
return 4;
}
}else{
if(eY <= sY){
return 2;
}else{
return 3;
}
}
}
function getDirectionLine(){
if(!dnd._direction){
dnd._direction = document.createElement("div");
gantt.$task_links.appendChild(dnd._direction);
}
return dnd._direction;
}
function removeDirectionLine(){
if(dnd._direction){
if (dnd._direction.parentNode) //the event line can be detached because of data refresh
dnd._direction.parentNode.removeChild(dnd._direction);
dnd._direction = null;
}
}
gantt._is_link_drop_area = function(e){
return !!gantt._locate_css(e, link_landing_hover_area);
};
};
gantt._get_link_state = function(){
return {
link_landing_area : this._link_landing,
link_target_id : this._link_target_task,
link_target_start : this._link_target_task_start,
link_source_id : this._link_source_task,
link_source_start : this._link_source_task_start
};
};
gantt._init_tasks = function(){
//store temporary configs
this._tasks = {
col_width:this.config.columnWidth,
width: [], // width of each column
full_width: 0, // width of all columns
trace_x:[],
rendered:{}
};
this._click.gantt_task_link = dhtmlx.bind(function(e, trg){
var id = this.locate(e, gantt.config.link_attribute);
if(id){
this.callEvent("onLinkClick", [id, e]);
}
}, this);
this._click.gantt_scale_cell = dhtmlx.bind(function(e, trg){
var pos = gantt._get_mouse_pos(e);
var date = gantt.dateFromPos(pos.x);
var coll = Math.floor(gantt._day_index_by_date(date));
var coll_date = gantt._tasks.trace_x[coll];
gantt.callEvent("onScaleClick", [e, coll_date]);
}, this);
this._dbl_click.gantt_task_link = dhtmlx.bind(function(e, id, trg){
var id = this.locate(e, gantt.config.link_attribute);
this._delete_link_handler(id, e);
}, this);
this._dbl_click.gantt_link_point = dhtmlx.bind(function(e, id, trg){
var id = this.locate(e),
task = this.getTask(id);
var link = null;
if(trg.parentNode && trg.parentNode.className){
if(trg.parentNode.className.indexOf("_left") > -1){
link = task.$target[0];
}else{
link = task.$source[0];
}
}
if(link)
this._delete_link_handler(link, e);
return false;
}, this);
this._tasks_dnd.init();
this._init_links_dnd();
this._link_layers.clear();
var links_layer = this.addLinkLayer({
renderer: this._render_link_element,
container: this.$task_links,
filter: gantt._create_filter(['_filter_link', '_is_chart_visible'])
});
this._linkRenderer = this._link_layers.getRenderer(links_layer);
this._task_layers.clear();
var bar_layer = this.addTaskLayer({
renderer: this._render_task_element,
container: this.$task_bars,
filter: gantt._create_filter(['_filter_task', '_is_chart_visible'])
});
this._taskRenderer = this._task_layers.getRenderer(bar_layer);
this.addTaskLayer({
renderer: this._render_grid_item,
container: this.$grid_data,
filter: gantt._create_filter(['_filter_task', '_is_grid_visible'])
});
this.addTaskLayer({
renderer: this._render_bg_line,
container: this.$task_bg,
filter: gantt._create_filter(['_filter_task', '_is_chart_visible', '_is_std_background'])
});
function refreshId(renders, oldId, newId, item){
for(var i =0; i < renders.length; i++){
renders[i].change_id(oldId, newId);
renders[i].render_item(item);
}
}
if(this._onTaskIdChange)
this.detachEvent(this._onTaskIdChange);
this._onTaskIdChange = this.attachEvent("onTaskIdChange", function(oldId, newId){
var render = this._get_task_renderers();
refreshId(render, oldId, newId, this.getTask(newId));
});
if(this._onLinkIdChange)
this.detachEvent(this._onLinkIdChange);
this._onLinkIdChange = this.attachEvent("onLinkIdChange", function(oldId, newId){
var render = this._get_link_renderers();
refreshId(render, oldId, newId, this.getLink(newId));
});
};
gantt._create_filter = function(filter_methods){
if(!(filter_methods instanceof Array)){
filter_methods = Array.prototype.slice.call(arguments, 0);
}
return function(obj){
var res = true;
for(var i = 0, len = filter_methods.length; i < len; i++){
var filter_method = filter_methods[i];
if(gantt[filter_method]){
res = res && (gantt[filter_method].apply(gantt, [obj.id, obj]) !== false);
}
}
return res;
};
};
gantt._is_chart_visible = function(){
return !!this.config.show_chart;
};
gantt._filter_task = function(id, task){
var min = null, max = null;
if(this.config.start_date && this.config.end_date){
min = this.config.start_date.valueOf();
max = this.config.end_date.valueOf();
if(+task.start_date > max || +task.end_date < +min)
return false;
}
return true;
};
gantt._filter_link = function(id, link){
if(!this.config.show_links){
return false;
}
if(!(gantt.isTaskVisible(link.source) && gantt.isTaskVisible(link.target)))
return false;
return this.callEvent("onBeforeLinkDisplay", [id, link]);
};
gantt._is_std_background = function(){
return !this.config.static_background;
};
gantt._delete_link_handler = function(id, e){
if(id && this.callEvent("onLinkDblClick", [id, e])){
var link = gantt.getLink(id);
if(gantt._is_readonly(link)) return;
var title = "";
var question = gantt.locale.labels.link + " " +this.templates.link_description(this.getLink(id)) + " " + gantt.locale.labels.confirm_link_deleting;
window.setTimeout(function(){
gantt._dhtmlx_confirm(question, title, function(){
gantt.deleteLink(id);
});
},(gantt.config.touch ? 300 : 1));
}
};
gantt.getTaskNode = function(id){
return this._taskRenderer.rendered[id];
};
gantt.getLinkNode = function(id){
return this._linkRenderer.rendered[id];
};
gantt._get_tasks_data = function(){
var rows = [];
for(var i=0; i < this._order.length; i++){
var item = this._pull[this._order[i]];
item.$index = i;
//this._update_parents(item.id, true);
this.resetProjectDates(item);
rows.push(item);
}
return rows;
};
gantt._get_links_data = function(){
var links = [];
for(var i in this._lpull)
links.push(this._lpull[i]);
return links;
};
gantt._render_data = function(){
this.callEvent("onBeforeDataRender", []);
if(!this._is_render_active())
return;
this._sync_order();
this._update_layout_sizes();
if(this.config.static_background)
this._render_bg_canvas();
var data = this._get_tasks_data();
var renderers = this._get_task_renderers();
for(var i=0; i < renderers.length; i++){
renderers[i].render_items(data);
}
var links = gantt._get_links_data();
renderers = this._get_link_renderers();
for(var i=0; i < renderers.length; i++)
renderers[i].render_items(links);
this.callEvent("onDataRender", []);
};
gantt._update_layout_sizes = function(){
var cfg = this._tasks;
cfg.bar_height = this._get_task_height();
//task bars layer
this.$task_data.style.height = Math.max(this.$task.offsetHeight - this.config.scale_height, 0) + 'px';
this.$task_bg.style.height = "";
this.$task_bg.style.backgroundImage = "";
//timeline area layers
var data_els = this.$task_data.childNodes;
for(var i= 0, len = data_els.length; i < len; i++){
var el = data_els[i];
if(this._is_layer(el) && el.style)
el.style.width = cfg.full_width + "px";
}
//grid area
if(this._is_grid_visible()){
var columns = this.getGridColumns();
var width = 0;
for (var i = 0; i < columns.length; i++)
width += columns[i].width;
this.$grid_data.style.width = Math.max(width-1, 0) + "px";
}
};
gantt._scale_range_unit = function(){
var unit = this.config.scale_unit;
if(this.config.scale_offset_minimal){
var scales = this._get_scales();
unit = scales[scales.length - 1].unit;
}
return unit;
};
gantt._init_tasks_range = function(){
var unit = this._scale_range_unit();
if(this.config.start_date && this.config.end_date){
this._min_date = this.date[unit + "_start"]( new Date(this.config.start_date));
this._max_date = this.date[unit + "_start"]( new Date(this.config.end_date));
return;
}
//reset project timing
this._get_tasks_data();
var range = this.getSubtaskDates();
this._min_date = range.start_date;
this._max_date = range.end_date;
if(!(this._max_date && this._max_date)){
this._min_date = new Date();
this._max_date = new Date(this._min_date);
}
this._min_date = this.date[unit + "_start"](this._min_date);
this._min_date = this.calculateEndDate(this.date[unit + "_start"](this._min_date), -1, unit);//one free column before first task
this._max_date = this.date[unit + "_start"](this._max_date);
this._max_date = this.calculateEndDate(this._max_date, 2, unit);//one free column after last task
};
gantt._prepare_scale_html = function(config){
var cells = [];
var date = null, content = null, css = null;
if(config.template || config.date){
content = config.template || this.date.date_to_str(config.date);
}
css = config.css || function(){};
if(!config.css && this.config.inherit_scale_class){
css = gantt.templates.scale_cell_class;
}
for (var i = 0; i < config.count; i++) {
date = new Date(config.trace_x[i]);
var value = content.call(this, date),
width = config.width[i],
style = "",
template = "",
cssclass = "";
if(width){
style = "width:"+(width)+"px;";
cssclass = "gantt_scale_cell" + (i == config.count-1 ? " gantt_last_cell" : "");
template = css.call(this, date);
if(template) cssclass += " " + template;
var cell = "<div class='" + cssclass + "' style='" + style + "'>" + value + "</div>";
cells.push(cell);
}else{
//do not render ignored cells
}
}
return cells.join("");
};
gantt._get_scales = function(){
var helpers = this._scale_helpers;
var scales = [helpers.primaryScale()].concat(this.config.subscales);
helpers.sortScales(scales);
return scales;
};
gantt._render_tasks_scales = function() {
this._init_tasks_range();
this._scroll_resize();
this._set_sizes();
var scales_html = "",
outer_width = 0,
data_width = 0,
scale_height = 0;
if(this._is_chart_visible()){
var helpers = this._scale_helpers;
var scales = this._get_scales();
scale_height = (this.config.scale_height-1);
var resize = this._get_resize_options();
var avail_width = resize.x ? Math.max(this.config.autosize_min_width, 0) : this.$task.offsetWidth;
var cfgs = helpers.prepareConfigs(scales,this.config.min_column_width, avail_width, scale_height);
var cfg = this._tasks = cfgs[cfgs.length - 1];
var html = [];
var css = this.templates.scale_row_class;
for(var i=0; i < cfgs.length; i++){
var cssClass = "gantt_scale_line";
var tplClass = css(cfgs[i]);
if(tplClass){
cssClass += " " + tplClass;
}
html.push("<div class=\""+cssClass+"\" style=\"height:"+(cfgs[i].height)+"px;line-height:"+(cfgs[i].height)+"px\">" + this._prepare_scale_html(cfgs[i]) + "</div>");
}
scales_html = html.join("");
outer_width = cfg.full_width + this.$scroll_ver.offsetWidth + "px";
data_width = cfg.full_width + "px";
scale_height += "px";
}
if(this._is_chart_visible()){
this.$task.style.display = "";
}else{
this.$task.style.display = "none";
}
this.$task_scale.style.height = scale_height;
this.$task_data.style.width =
this.$task_scale.style.width = outer_width;
this.$task_scale.innerHTML = scales_html;
};
gantt._render_bg_line = function(item){
var cfg = gantt._tasks;
var count = cfg.count;
var row = document.createElement("div");
if(gantt.config.show_task_cells){
for (var j = 0; j < count; j++) {
var width = cfg.width[j],
cssclass = "";
if(width > 0){//do not render skipped columns
var cell = document.createElement("div");
cell.style.width = (width)+"px";
cssclass = "gantt_task_cell" + (j == count-1 ? " gantt_last_cell" : "");
cssTemplate = this.templates.task_cell_class(item, cfg.trace_x[j]);
if(cssTemplate)
cssclass += " " + cssTemplate;
cell.className = cssclass;
row.appendChild(cell);
}
}
}
var odd = item.$index%2 !== 0;
var cssTemplate = gantt.templates.task_row_class(item.start_date, item.end_date, item);
var css = "gantt_task_row" + (odd ? " odd" : "") + (cssTemplate ? ' '+cssTemplate : '');
if(this.getState().selected_task == item.id){
css += " gantt_selected";
}
//var row = "<div class='" + css + "' " + this.config.task_attribute + "='" + item.id + "'>" + cells.join("") + "</div>";
row.className = css;
row.style.height = (gantt.config.row_height)+"px";
row.setAttribute(this.config.task_attribute, item.id);
return row;
};
//defined in an extension
gantt._render_bg_canvas = function(){};
gantt._adjust_scales = function(){
if(this.config.fit_tasks){
var old_min = +this._min_date,
old_max = +this._max_date;
this._init_tasks_range();
if(+this._min_date != old_min || +this._max_date != old_max){
this.render();
this.callEvent("onScaleAdjusted", []);
return true;
}
}
return false;
};
//refresh task and related links
gantt.refreshTask = function(taskId, refresh_links){
if(!this._is_render_active())
return;
var renders = this._get_task_renderers();
var task = this.getTask(taskId);
if(task && this.isTaskVisible(taskId)){
for(var i =0; i < renders.length; i++)
renders[i].render_item(task);
if(refresh_links !== undefined && !refresh_links)
return;
for(var i=0; i < task.$source.length; i++){
gantt.refreshLink(task.$source[i]);
}
for(var i=0; i < task.$target.length; i++){
gantt.refreshLink(task.$target[i]);
}
}else{
this._render_data();
}
};
gantt.refreshLink = function(linkId){
if(!this._is_render_active())
return;
if(this.isLinkExists(linkId)){
this._render_link(linkId);
}else{
var renders = this._get_link_renderers();
for(var i =0; i < renders.length; i++)
renders[i].remove_item(linkId);
}
};
gantt._combine_item_class = function(basic, template, itemId){
var css = [basic];
if(template)
css.push(template);
var state = gantt.getState();
var task = this.getTask(itemId);
if(this._get_safe_type(task.type) == this.config.types.milestone){
css.push("gantt_milestone");
}
if(this._get_safe_type(task.type) == this.config.types.project){
css.push("gantt_project");
}
if(this._is_flex_task(task))
css.push("gantt_dependent_task");
if(this.config.select_task && itemId == state.selected_task)
css.push("gantt_selected");
if(itemId == state.drag_id){
css.push("gantt_drag_" + state.drag_mode);
if(state.touch_drag){
css.push("gantt_touch_" + state.drag_mode);
}
}
var links = gantt._get_link_state();
if(links.link_source_id == itemId)
css.push("gantt_link_source");
if(links.link_target_id == itemId)
css.push("gantt_link_target");
if(this.config.highlight_critical_path && this.isCriticalTask){
if(this.isCriticalTask(task))
css.push("gantt_critical_task");
}
if(links.link_landing_area &&
(links.link_target_id && links.link_source_id) &&
(links.link_target_id != links.link_source_id)){
var from_id = links.link_source_id;
var from_start = links.link_source_start;
var to_start = links.link_target_start;
var allowDrag = gantt.isLinkAllowed(from_id, itemId, from_start, to_start);
var dragClass = "";
if(allowDrag){
if(to_start)
dragClass = "link_start_allow";
else
dragClass = "link_finish_allow";
}else{
if(to_start)
dragClass = "link_start_deny";
else
dragClass = "link_finish_deny";
}
css.push(dragClass);
}
return css.join(" ");
};
gantt._render_pair = function(parent, css, task, content){
var state = gantt.getState();
if(+task.end_date <= +state.max_date)
parent.appendChild(content(css+" task_right"));
if(+task.start_date >= +state.min_date)
parent.appendChild(content(css+" task_left"));
};
gantt._get_task_height = function(){
// height of the bar item
var height = this.config.task_height;
if(height == "full")
height = this.config.row_height - 5;
//item height cannot be bigger than row height
height = Math.min(height, this.config.row_height);
return Math.max(height, 0);
};
gantt._get_milestone_width = function(){
return this._get_task_height();
};
gantt._get_visible_milestone_width = function(){
var origWidth = gantt._get_task_height();//m-s have square shape
return Math.sqrt(2*origWidth*origWidth);
};
// TODO: remove reduntant methods for task positioning
gantt.getTaskPosition = function(task, start_date, end_date){
var x = this.posFromDate(start_date || task.start_date);
var x2 = this.posFromDate(end_date || task.end_date);
x2 = Math.max(x, x2);
var y = this.getTaskTop(task.id);
var height = this.config.task_height;
return {
left:x,
top:y,
height : height,
width: Math.max((x2 - x), 0)
};
};
gantt._get_task_width = function(task, start, end ){
return Math.round(this._get_task_pos(task, false).x - this._get_task_pos(task, true).x);
};
gantt._is_readonly = function(item){
if(item && item[this.config.editable_property]){
return false;
}else{
return (item && item[this.config.readonly_property]) || this.config.readonly;
}
};
gantt._task_default_render = function(task){
var pos = this._get_task_pos(task);
var cfg = this.config;
var height = this._get_task_height();
var padd = Math.floor((this.config.row_height - height)/2);
if(this._get_safe_type(task.type) == cfg.types.milestone && cfg.link_line_width > 1){
//little adjust milestone position, so horisontal corners would match link arrow when thickness of link line is more than 1px
padd += 1;
}
var div = document.createElement("div");
var width = gantt._get_task_width(task);
var type = this._get_safe_type(task.type);
div.setAttribute(this.config.task_attribute, task.id);
if(cfg.show_progress && type != this.config.types.milestone){
this._render_task_progress(task,div, width);
}
//use separate div to display content above progress bar
var content = gantt._render_task_content(task, width);
if(task.textColor){
content.style.color = task.textColor;
}
div.appendChild(content);
var css = this._combine_item_class("gantt_task_line",
this.templates.task_class(task.start_date, task.end_date, task),
task.id);
if(task.color || task.progressColor || task.textColor){
css += " gantt_task_inline_color";
}
div.className = css;
var styles = [
"left:" + pos.x + "px",
"top:" + (padd + pos.y) + 'px',
"height:" + height + 'px',
"line-height:" + height + 'px',
"width:" + width + 'px'
];
if(task.color){
styles.push("background-color:" + task.color);
}
if(task.textColor){
styles.push("color:" + task.textColor);
}
div.style.cssText = styles.join(";");
var side = this._render_leftside_content(task);
if(side) div.appendChild(side);
side = this._render_rightside_content(task);
if(side) div.appendChild(side);
if(!this._is_readonly(task)){
if(cfg.drag_resize && !this._is_flex_task(task) && type != this.config.types.milestone){
gantt._render_pair(div, "gantt_task_drag", task, function(css){
var el = document.createElement("div");
el.className = css;
return el;
});
}
if(cfg.drag_links && this.config.show_links){
gantt._render_pair(div, "gantt_link_control", task, function(css){
var outer = document.createElement("div");
outer.className = css;
outer.style.cssText = [
"height:" + height + 'px',
"line-height:" + height + 'px'
].join(";");
var inner = document.createElement("div");
inner.className = "gantt_link_point";
outer.appendChild(inner);
return outer;
});
}
}
return div;
};
gantt._render_task_element = function(task){
var painters = this.config.type_renderers;
var renderer = painters[this._get_safe_type(task.type)],
defaultRenderer = this._task_default_render;
if(!renderer){
renderer = defaultRenderer;
}
return renderer.call(this, task, dhtmlx.bind(defaultRenderer, this));
};
gantt._render_side_content = function(task, template, cssClass){
if(!template) return null;
var text = template(task.start_date, task.end_date, task);
if(!text) return null;
var content = document.createElement("div");
content.className = "gantt_side_content " + cssClass;
content.innerHTML = text;
return content;
};
gantt._render_leftside_content = function(task){
var css = "gantt_left " + gantt._get_link_crossing_css(true, task);
return gantt._render_side_content(task, this.templates.leftside_text, css);
};
gantt._render_rightside_content = function(task){
var css = "gantt_right " + gantt._get_link_crossing_css(false, task);
return gantt._render_side_content(task, this.templates.rightside_text, css);
};
gantt._get_conditions = function(leftside){
if(leftside){
return {
$source : [
gantt.config.links.start_to_start
],
$target : [
gantt.config.links.start_to_start,
gantt.config.links.finish_to_start
]
};
}else{
return {
$source : [
gantt.config.links.finish_to_start,
gantt.config.links.finish_to_finish
],
$target : [
gantt.config.links.finish_to_finish
]
};
}
};
gantt._get_link_crossing_css = function(left, task){
var cond = gantt._get_conditions(left);
for(var i in cond){
var links = task[i];
for(var ln =0; ln < links.length; ln++){
var link = gantt.getLink(links[ln]);
for(var tp =0; tp < cond[i].length; tp++){
if(link.type == cond[i][tp]){
return "gantt_link_crossing";
}
}
}
}
return "";
};
gantt._render_task_content = function(task, width){
var content = document.createElement("div");
if(this._get_safe_type(task.type) != this.config.types.milestone)
content.innerHTML = this.templates.task_text(task.start_date, task.end_date, task);
content.className = "gantt_task_content";
//content.style.width = width + 'px';
return content;
};
gantt._render_task_progress = function(task, element, maxWidth){
var done = task.progress*1 || 0;
maxWidth = Math.max(maxWidth - 2, 0);//2px for borders
var pr = document.createElement("div");
var width = Math.round(maxWidth*done);
width = Math.min(maxWidth, width);
if(task.progressColor){
pr.style.backgroundColor = task.progressColor;
pr.style.opacity = 1;
}
pr.style.width = width + 'px';
pr.className = "gantt_task_progress";
pr.innerHTML = this.templates.progress_text(task.start_date, task.end_date, task);
element.appendChild(pr);
if(this.config.drag_progress && !gantt._is_readonly(task)){
var drag = document.createElement("div");
drag.style.left = width + 'px';
drag.className = "gantt_task_progress_drag";
pr.appendChild(drag);
element.appendChild(drag);
}
};
gantt._get_line = function(step) {
var steps = {
"second": 1,
"minute": 60,
"hour": 60*60,
"day": 60*60*24,
"week": 60*60*24*7,
"month": 60*60*24*30,
"year": 60*60*24*365
};
return steps[step] || 0;
};
gantt.dateFromPos = function(x){
var scale = this._tasks;
if(x < 0 || x > scale.full_width || !scale.full_width){
return null;
}
var ind = this._findBinary(this._tasks.left, x);
var summ = this._tasks.left[ind];
var col_width = scale.width[ind] || scale.col_width;
var part = 0;
if(col_width)
part = (x - summ)/col_width;
var unit = 0;
if(part){
unit = gantt._get_coll_duration(scale, scale.trace_x[ind]);
}
var date = new Date(scale.trace_x[ind].valueOf() + Math.round(part*unit));
return date;
};
gantt.posFromDate = function(date){
var ind = gantt._day_index_by_date(date);
dhtmlx.assert(ind >= 0, "Invalid day index");
var wholeCells = Math.floor(ind);
var partCell = ind % 1;
var pos = gantt._tasks.left[Math.min(wholeCells, gantt._tasks.width.length - 1)];
if(wholeCells == gantt._tasks.width.length)
pos += gantt._tasks.width[gantt._tasks.width.length - 1];
//for(var i=1; i <= wholeCells; i++)
// pos += gantt._tasks.width[i-1];
if(partCell){
if(wholeCells < gantt._tasks.width.length){
pos += gantt._tasks.width[wholeCells]*(partCell % 1);
}else{
pos += 1;
}
}
return pos;
};
gantt._day_index_by_date = function(date){
var pos = new Date(date).valueOf();
var days = gantt._tasks.trace_x,
ignores = gantt._tasks.ignore_x;
if(pos <= this._min_date)
return 0;
if(pos >= this._max_date)
return days.length;
/*var day = null;
for (var xind = 0, length = days.length-1; xind < length; xind++) {
// | 8:00, 8:30 | 8:15 should be checked against 8:30
// clicking at the most left part of the cell, say 8:30 should create event in that cell, not previous one
day = +days[xind+1];
if (pos < day && !ignores[day])
break;
}*/
var day_ind = gantt._findBinary(days, pos);
var day = +gantt._tasks.trace_x[day_ind];
while(ignores[day]){
day = gantt._tasks.trace_x[++day_ind];
}
if(!day) return 0;
return day_ind + ((date - days[day_ind]) / gantt._get_coll_duration(gantt._tasks, days[day_ind]));
};
gantt._findBinary = function(array, target) {
// modified binary search, target value not exactly match array elements, looking for closest one
var low = 0, high = array.length - 1, i, item, prev;
while (low <= high) {
i = Math.floor((low + high) / 2);
item = +array[i];
prev = +array[i - 1];
if (item < target){
low = i + 1; continue;
}
if (item > target){
if(!(!isNaN(prev) && prev < target)) {
high = i - 1; continue;
}else{
// if target is between 'i' and 'i-1' return 'i - 1'
return i - 1;
}
}
return i;
}
return array.length - 1;
};
gantt._get_coll_duration = function(scale, date){
return gantt.date.add(date, scale.step, scale.unit) - date;
};
gantt._get_x_pos = function(task, to_start){
to_start = to_start !== false;
var x = gantt.posFromDate(to_start ? task.start_date : task.end_date);
};
gantt.getTaskTop = function(task_id){
return this._y_from_ind(this._get_visible_order(task_id));
};
gantt._get_task_coord = function(task, to_start, x_correction){
to_start = to_start !== false;
x_correction = x_correction || 0;
var isMilestone = (this._get_safe_type(task.type) == this.config.types.milestone);
var date = null;
if(to_start || isMilestone){
date = (task.start_date || this._default_task_date(task));
}else{
date = (task.end_date || this.calculateEndDate(this._default_task_date(task)));
}
var x = this.posFromDate(date),
y = this.getTaskTop(task.id);
if(isMilestone){
if(to_start){
x -= x_correction;
}else{
x += x_correction;
}
}
return {x:x, y:y};
};
gantt._get_task_pos = function(task, to_start){
to_start = to_start !== false;
var mstoneCorrection = gantt._get_milestone_width()/2;
return this._get_task_coord(task, to_start, mstoneCorrection);
};
gantt._get_task_visible_pos = function(task, to_start){
to_start = to_start !== false;
var mstoneCorrection = gantt._get_visible_milestone_width()/2;
return this._get_task_coord(task, to_start, mstoneCorrection);
};
gantt._correct_shift=function(start, back){
return start-=((new Date(gantt._min_date)).getTimezoneOffset()-(new Date(start)).getTimezoneOffset())*60000*(back?-1:1);
};
gantt._get_mouse_pos = function(ev){
if (ev.pageX || ev.pageY)
var pos = {x:ev.pageX, y:ev.pageY};
var d = _isIE ? document.documentElement : document.body;
var pos = {
x:ev.clientX + d.scrollLeft - d.clientLeft,
y:ev.clientY + d.scrollTop - d.clientTop
};
var box = gantt._get_position(gantt.$task_data);
pos.x = pos.x - box.x + gantt.$task_data.scrollLeft;
pos.y = pos.y - box.y + gantt.$task_data.scrollTop;
return pos;
};
gantt._is_layer = function(dom_element){
return (dom_element && dom_element.hasAttribute && dom_element.hasAttribute(this.config.layer_attribute));
};
//helper for rendering bars and links
gantt._task_renderer = function(id, render_one, node, filter){
//hash of dom elements is needed to redraw single bar/link
if(!this._task_area_pulls)
this._task_area_pulls = {};
if(!this._task_area_renderers)
this._task_area_renderers = {};
if(this._task_area_renderers[id])
return this._task_area_renderers[id];
if(!render_one)
dhtmlx.assert(false, "Invalid renderer call");
if(node)
node.setAttribute(this.config.layer_attribute, true);
this._task_area_renderers[id] = {
render_item : function(item, container){
var pull = gantt._task_area_pulls[id];
container = container || node;
if(filter){
if(!filter(item)){
this.remove_item(item.id);
return;
}
}
var dom = render_one.call(gantt, item);
if(!dom) return;
if(pull[item.id]){
this.replace_item(item.id, dom);
}else{
pull[item.id] = dom;
container.appendChild(dom);
}
},
clear : function(container){
this.rendered = gantt._task_area_pulls[id] = {};
container = container || node;
if(container)
container.innerHTML = "";
},
render_items : function(items, container){
container = container || node;
this.clear(container);
var buffer = document.createDocumentFragment();
for(var i= 0, vis = items.length; i < vis; i++){
this.render_item(items[i], buffer);
}
container.appendChild(buffer);
},
replace_item: function(item_id, newNode){
var item = this.rendered[item_id];
if(item && item.parentNode){
item.parentNode.replaceChild(newNode, item);
}
this.rendered[item_id] = newNode;
},
remove_item:function(item_id){
var item = this.rendered[item_id];
if(item && item.parentNode){
item.parentNode.removeChild(item);
}
delete this.rendered[item_id];
},
change_id: function(oldid, newid) {
this.rendered[newid] = this.rendered[oldid];
delete this.rendered[oldid];
},
rendered : this._task_area_pulls[id],
node: node,
unload : function(){
this.clear();
delete gantt._task_area_renderers[id];
delete gantt._task_area_pulls[id];
}
};
return this._task_area_renderers[id];
};
gantt._clear_renderers = function(){
for(var i in this._task_area_renderers){
this._task_renderer(i).unload();
}
};
// --#include core/tasks_canvas_render.js
gantt.attachEvent("onGanttReady", function(){
gantt._task_layers.add();
gantt._link_layers.add();
});
gantt._layers = {
prepareConfig: function(config){
if(typeof config == "function"){
config = {renderer: config};
}
var id = config.id = dhtmlx.uid();
if(!config.container)
config.container = document.createElement("div");
return config;
},
create: function(get_container, rel_root){
return {
tempCollection:[],
renderers:{},
container: get_container,
getRenderers: function(){
var res = [];
for (var i in this.renderers){
res.push(this.renderers[i]);
}
return res;
},
getRenderer: function(id){
return this.renderers[id];
},
add: function(layer){
if(layer)
this.tempCollection.push(layer);
if(!this.container()) return;
var container = this.container();
var pending = this.tempCollection;
for(var i =0; i < pending.length; i++){
var layer = pending[i];
var node = layer.container,
id = layer.id,
topmost = layer.topmost;
if(!node.parentNode){
//insert on top or below the tasks
if(topmost){
container.appendChild(node);
}else{
var rel = rel_root ? rel_root() : container.firstChild;
if(rel)
container.insertBefore(node, rel);
else
container.appendChild(node);
}
}
this.renderers[id] = gantt._task_renderer(id, layer.renderer, node, layer.filter);
this.tempCollection.splice(i,1);
i--;
}
},
remove: function(id){
this.renderers[id].unload();
delete this.renderers[id];
},
clear: function(){
for(var i in this.renderers){
this.renderers[i].unload();
}
this.renderers = {};
}
};
}
};
gantt._create_filter = function(filter_methods){
if(!(filter_methods instanceof Array)){
filter_methods = Array.prototype.slice.call(arguments, 0);
}
return function(obj){
var res = true;
for(var i = 0, len = filter_methods.length; i < len; i++){
var filter_method = filter_methods[i];
if(gantt[filter_method]){
res = res && (gantt[filter_method].call(gantt, obj.id, obj) !== false);
}
}
return res;
};
};
gantt._add_generic_layer = function(layersManager, filters){
return function(config){
if(config.filter === undefined){
config.filter = gantt._create_filter(filters);
}
config = gantt._layers.prepareConfig(config);
layersManager.add(config);
return config.id;
};
};
gantt._task_layers = gantt._layers.create(function(){return gantt.$task_data; }, function(){return gantt.$task_links;});
gantt._link_layers = gantt._layers.create(function(){return gantt.$task_data; });
gantt.addTaskLayer = gantt._add_generic_layer(gantt._task_layers, ['_filter_task', '_is_chart_visible']);
gantt.removeTaskLayer = function(id){
gantt._task_layers.remove(id);
};
gantt.addLinkLayer = gantt._add_generic_layer(gantt._link_layers, ['_filter_link', '_is_chart_visible']);
gantt.removeLinkLayer = function(id){
gantt._link_layers.remove(id);
};
gantt._get_task_renderers = function(){
return this._task_layers.getRenderers();
};
gantt._get_link_renderers = function(){
return this._link_layers.getRenderers();
};
gantt._pull = {};
gantt._branches = {};
gantt._order = [];
gantt._lpull = {};
gantt.load = function(url, type, callback){
this._load_url = url;
dhtmlx.assert(arguments.length, "Invalid load arguments");
this.callEvent("onLoadStart", []);
var tp = 'json', cl = null;
if(arguments.length >= 3){
tp = type;
cl = callback;
}else{
if(typeof arguments[1] == "string")
tp = arguments[1];
else if(typeof arguments[1] == "function")
cl = arguments[1];
}
this._load_type = tp;
dhx4.ajax.get(url, dhtmlx.bind(function(l) {
this.on_load(l, tp);
this.callEvent("onLoadEnd", []);
if(typeof cl == "function")
cl.call(this);
}, this));
};
gantt.parse = function(data, type) {
this.on_load({xmlDoc: {responseText: data}}, type);
};
gantt.serialize = function(type){
type = type || "json";
return this[type].serialize();
};
/*
tasks and relations
{
data:[
{
"id":"string",
"text":"...",
"start_date":"Date or string",
"end_date":"Date or string",
"duration":"number",
"progress":"0..1",
"parent_id":"string",
"order":"number"
},...],
links:[
{
id:"string",
source:"string",
target:"string",
type:"string"
},...],
collections:{
collectionName:[
{key:, label:, optional:...},...
],...
}
}
gantt._pull - id to object hash
gantt._branch - array of per branch arrays of objects|ids
gantt._order - array of visible elements
gantt._order_full - array of all elements
gantt._links
* */
gantt.on_load = function(resp, type){
this.callEvent("onBeforeParse", []);
if(!type)
type = "json";
dhtmlx.assert(this[type], "Invalid data type:'" + type + "'");
var raw = resp.xmlDoc.responseText;
var data = this[type].parse(raw, resp);
this._process_loading(data);
};
gantt._process_loading = function(data){
if(data.collections)
this._load_collections(data.collections);
var tasks = data.data;
var task;
for (var i = 0; i < tasks.length; i++) {
task = tasks[i];
this._init_task(task);
if (!this.callEvent("onTaskLoading", [task])) continue;
this._pull[task.id] = task;
}
for (var i in this._pull){
task = this._pull[i];
this.setParent(task, this.getParent(task) || this.config.root_id);
}
// calculating $level for each item
for (var i in this._pull){
task = this._pull[i];
this._add_branch(task, true);
task.$level = this.calculateTaskLevel(task);
}
this._sync_order();
this._init_links(data.links || (data.collections ? data.collections.links : []));
this.callEvent("onParse", []);
this.render();
if(this.config.initial_scroll){
var id = (this._order[0] || this.config.root_id);
if(id)
this.showTask(id);
}
};
gantt._init_links = function(links){
if (links)
for(var i=0; i < links.length; i++){
if(links[i]){
var link = this._init_link(links[i]);
this._lpull[link.id] = link;
}
}
this._sync_links();
};
gantt._load_collections = function(collections){
var collections_loaded = false;
for (var key in collections) {
if (collections.hasOwnProperty(key)) {
collections_loaded = true;
var collection = collections[key];
var arr = this.serverList[key];
if (!arr) continue;
arr.splice(0, arr.length); //clear old options
for (var j = 0; j < collection.length; j++) {
var option = collection[j];
var obj = dhtmlx.copy(option);
obj.key = obj.value;// resulting option object
for (var option_key in option) {
if (option.hasOwnProperty(option_key)) {
if (option_key == "value" || option_key == "label")
continue;
obj[option_key] = option[option_key]; // obj['value'] = option['value']
}
}
arr.push(obj);
}
}
}
if (collections_loaded)
this.callEvent("onOptionsLoad", []);
};
gantt._sync_order = function(silent) {
this._order = [];
this._sync_order_item({parent:this.config.root_id, $open:true, $ignore:true, id:this.config.root_id});
if(!silent){
this._scroll_resize();
this._set_sizes();
}
};
gantt.attachEvent("onBeforeTaskDisplay", function(id, task){
return !task.$ignore;
});
gantt._sync_order_item = function(item) {
if(item.id && //do not trigger event for virtual root
this._filter_task(item.id, item) &&
this.callEvent("onBeforeTaskDisplay", [item.id, item])){
this._order.push(item.id);
}
if (item.$open) {
var children = this.getChildren(item.id);
if (children)
for (var i = 0; i < children.length; i++)
this._sync_order_item(this._pull[children[i]]);
}
};
gantt._get_visible_order = function(id){
dhtmlx.assert(id, "Invalid argument");
var ord = this._order;
for(var i= 0, count = ord.length; i < count; i++)
if(ord[i] == id) return i;
return -1;
};
gantt.eachTask = function(code, parent, master){
parent = parent || this.config.root_id;
master = master || this;
var branch = this.getChildren(parent);
if (branch)
for (var i=0; i<branch.length; i++){
var item = this._pull[branch[i]];
code.call(master, item);
if (this.hasChild(item.id))
this.eachTask(code, item.id, master);
}
};
gantt.json = {
parse : function(data){
dhtmlx.assert(data, "Invalid data");
if (typeof data == "string") {
if(window.JSON)
data = JSON.parse(data);
else{
gantt._temp = eval("(" + data + ")");
data = gantt._temp || {};
gantt._temp = null;
}
}
if (data.dhx_security)
dhtmlx.security_key = data.dhx_security;
return data;
},
_copyLink:function(obj){
var copy = {};
for (var key in obj)
copy[key] = obj[key];
return copy;
},
_copyObject:function(obj){
var copy = {};
for (var key in obj){
if (key.charAt(0) == "$")
continue;
copy[key] = obj[key];
if(copy[key] instanceof Date){
copy[key] = gantt.templates.xml_format(copy[key]);
}
}
return copy;
},
serialize:function(){
var tasks = [];
var links = [];
gantt.eachTask(function(obj){
gantt.resetProjectDates(obj);
tasks.push(this._copyObject(obj));
}, gantt.config.root_id, this);
for (var key in gantt._lpull)
links.push(this._copyLink(gantt._lpull[key]));
return {
data : tasks,
links: links
};
}
};
/*
<data>
<task id:"some" parent_id="0" progress="0.5">
<text>My task 1</text>
<start_date>16.08.2013</start_date>
<end_date>22.08.2013</end_date>
</task>
<coll_options>
<links>
<link source='a1' target='b2' type='c3' />
</links>
</coll_options>
</data>
*/
gantt.xml = {
_xmlNodeToJSON:function(node, attrs_only){
var t = {};
for (var i = 0; i < node.attributes.length; i++)
t[node.attributes[i].name] = node.attributes[i].value;
if (!attrs_only){
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeType == 1)
t[child.tagName] = child.firstChild ? child.firstChild.nodeValue : "";
}
if (!t.text) t.text = node.firstChild ? node.firstChild.nodeValue : "";
}
return t;
},
_getCollections:function(loader){
var collection = {};
var opts = dhx4.ajax.xpath("//coll_options", loader);
for (var i = 0; i < opts.length; i++) {
var bind = opts[i].getAttribute("for");
var arr = collection[bind] = [];
var itms = dhx4.ajax.xpath(".//item", opts[i]);
for (var j = 0; j < itms.length; j++) {
var itm = itms[j];
var attrs = itm.attributes;
var obj = { key: itms[j].getAttribute("value"), label: itms[j].getAttribute("label")};
for (var k = 0; k < attrs.length; k++) {
var attr = attrs[k];
if (attr.nodeName == "value" || attr.nodeName == "label")
continue;
obj[attr.nodeName] = attr.nodeValue;
}
arr.push(obj);
}
}
return collection;
},
_getXML:function(text, loader, toptag){
toptag = toptag || "data";
if (!loader.getXMLTopNode){
loader = dhx4.ajax.parse(loader);
}
var xml = dhx4.ajax.xmltop(toptag, loader.xmlDoc);
if (xml.tagName != toptag) throw "Invalid XML data";
var skey = xml.getAttribute("dhx_security");
if (skey)
dhtmlx.security_key = skey;
return xml;
},
parse:function(text, loader){
loader = this._getXML(text, loader);
var data = { };
var evs = data.data = [];
var xml = dhx4.ajax.xpath("//task", loader);
for (var i = 0; i < xml.length; i++)
evs[i] = this._xmlNodeToJSON(xml[i]);
data.collections = this._getCollections(loader);
return data;
},
_copyLink:function(obj){
return "<item id='"+obj.id+"' source='"+obj.source+"' target='"+obj.target+"' type='"+obj.type+"' />";
},
_copyObject:function(obj){
return "<task id='"+obj.id+"' parent='"+(obj.parent||"")+"' start_date='"+obj.start_date+"' duration='"+obj.duration+"' open='"+(!!obj.open)+"' progress='"+obj.progress+"' end_date='"+obj.end_date+"'><![CDATA["+obj.text+"]]></task>";
},
serialize:function(){
var tasks = [];
var links = [];
var json = gantt.json.serialize();
for(var i= 0, len = json.data.length; i < len; i++){
tasks.push(this._copyObject(json.data[i]));
}
for(var i= 0, len = json.links.length; i < len; i++){
links.push(this._copyLink(json.links[i]));
}
return "<data>"+tasks.join("")+"<coll_options for='links'>"+links.join("")+"</coll_options></data>";
}
};
gantt.oldxml = {
parse:function(text, loader){
loader = gantt.xml._getXML(text, loader, "projects");
var data = { collections:{ links:[] } };
var evs = data.data = [];
var xml = dhx4.ajax.xpath("//task", loader);
for (var i = 0; i < xml.length; i++){
evs[i] = gantt.xml._xmlNodeToJSON(xml[i]);
var parent = xml[i].parentNode;
if (parent.tagName == "project")
evs[i].parent = "project-"+parent.getAttribute("id");
else
evs[i].parent = parent.parentNode.getAttribute("id");
}
xml = dhx4.ajax.xpath("//project", loader);
for (var i = 0; i < xml.length; i++){
var ev = gantt.xml._xmlNodeToJSON(xml[i], true);
ev.id ="project-"+ev.id;
evs.push(ev);
}
for (var i=0; i<evs.length; i++){
var ev = evs[i];
ev.start_date = ev.startdate || ev.est;
ev.end_date = ev.enddate;
ev.text = ev.name;
ev.duration = ev.duration / 8;
ev.open = 1;
if (!ev.duration && !ev.end_date) ev.duration = 1;
if (ev.predecessortasks)
data.collections.links.push({ target:ev.id, source:ev.predecessortasks, type:gantt.config.links.finish_to_start });
}
return data;
},
serialize:function(){
dhtmlx.message("Serialization to 'old XML' is not implemented");
}
};
gantt.serverList = function(name, array) {
if (array) {
this.serverList[name] = array.slice(0);
}else if(!this.serverList[name]){
this.serverList[name] = [];
}
return this.serverList[name];
};
gantt._working_time_helper = {
units : [
"year",
"month",
"week",
"day",
"hour",
"minute"
],
hours:[8, 17],
dates:{
0:false,
6:false
},
// cache previously calculated worktime
_working_units_cache: {
_cache: {},
get: function(unit, date) {
if(!unit || !date) return -1;
var cache = this._cache;
var time = date.getTime();
var result = -1; // default value (if not existed in the cache)
if(cache && cache[unit] && cache[unit][time] !== undefined) result = cache[unit][time];
return result;
},
put: function(unit, date, value) {
if(!unit || !date) return false;
var cache = this._cache;
var time = date.getTime();
value = !!value;
if(!cache) return false;
if(!cache[unit]) cache[unit] = {};
cache[unit][time] = value;
return true;
},
clear: function() {
this._cache = {};
}
},
_get_unit_order : function(unit){
for(var i= 0, len = this.units.length; i < len; i++){
if(this.units[i] == unit)
return i;
}
dhtmlx.assert(false, "Incorrect duration unit");
},
_timestamp:function(settings){
var timestamp = null;
if((settings.day || settings.day === 0)){
timestamp = settings.day;
}else if(settings.date){
timestamp = gantt.date.date_part(new Date(settings.date)).valueOf();
}
return timestamp;
},
set_time:function(settings){
var hours = settings.hours !== undefined ? settings.hours : true;
var timestamp = this._timestamp(settings);
if(timestamp !== null){
this.dates[timestamp] = hours;
}else{
this.hours = hours;
}
this._working_units_cache.clear();
},
unset_time:function(settings){
if(!settings){
this.hours = [];
}else{
var timestamp = this._timestamp(settings);
if(timestamp !== null){
delete this.dates[timestamp];
}
}
// Clear work units cache
this._working_units_cache.clear();
},
is_working_unit : function(date, unit, order){
if(!gantt.config.work_time) return true;
//Check if this item has in the cache
var is_work_unit = this._working_units_cache.get(unit, date);
if(is_work_unit == -1) {
// calculate if not cached
is_work_unit = this._check_is_working_unit(date, unit, order);
this._working_units_cache.put(unit, date, is_work_unit);
}
return is_work_unit;
},
_check_is_working_unit: function(date, unit, order) {
if(order === undefined){
order = this._get_unit_order(unit);
}
if(order === undefined){
return false;
}
if(order){
//check if bigger time unit is a work time (hour < day < month...)
//i.e. don't check particular hour if the whole day is marked as not working
if(!this.is_working_unit(date, this.units[order-1], order-1))
return false;
}
if(!this["is_work_" + unit])
return true;
return this["is_work_" + unit](date);
},
//checkings for particular time units
//methods for month-year-week can be defined, otherwise always return 'true'
is_work_day:function(date){
var val = this.get_working_hours(date);
if(val instanceof Array){
return val.length > 0;
}
return false;
},
is_work_hour:function(date){
var hours = this.get_working_hours(date);
var hour = date.getHours();
for(var i=0; i < hours.length; i += 2){
if(hours[i+1] === undefined){
return hours[i] == hour;
}else{
if(hour >= hours[i] && hour < hours[i+1])
return true;
}
}
return false;
},
get_working_hours:function(date){
var t = this._timestamp({date:date});
var hours = true;
if(this.dates[t] !== undefined){
hours = this.dates[t];//custom day
}else if(this.dates[date.getDay()] !== undefined){
hours = this.dates[date.getDay()];//week day
}
if(hours === true){
return this.hours;
}else if(hours){
return hours;
}
return [];
},
get_work_units_between:function(from, to, unit, step){
if(!unit){
return false;
}
var start = new Date(from),
end = new Date(to),
step = step || 1;
var units = 0;
while(start.valueOf() < end.valueOf()){
if(this.is_working_unit(start, unit))
units++;
start = gantt.date.add(start, step, unit);
}
return units;
},
is_work_units_between:function(from, to, unit, step){
if(!unit){
return false;
}
var start = new Date(from),
end = new Date(to),
step = step || 1;
while(start.valueOf() < end.valueOf()){
if(this.is_working_unit(start, unit))
return true;
start = gantt.date.add(start, step, unit);
}
return false;
},
add_worktime : function(from, duration, unit, step){
if(!unit)
return false;
var start = new Date(from),
added = 0,
step = step || 1,
duration = duration*1;
if(!gantt.config.work_time){
return gantt.date.add(start, step*duration, unit);
}else{
while(added < duration){
var next = gantt.date.add(start, step, unit);
if(this.is_working_unit(step > 0 ? start : next, unit))
added++;
start = next;
}
return start;
}
},
/* settings:
{
date:date,
unit:'day'/'hour'...,
dir:'future'/'past'/'any'/''
}
*/
get_closest_worktime : function(settings){
if(this.is_working_unit(settings.date, settings.unit))
return settings.date;
var unit = settings.unit;
var curr = gantt.date[unit + '_start'](settings.date);
var future_target = new Date(curr),
prev_target = new Date(curr),
tick = true,
maximum_loop = 3000,//be extra sure we won't fall into infinite loop, 3k seems big enough
count = 0,
both_directins = (settings.dir == 'any' || !settings.dir);
var inc = 1;
if(settings.dir == 'past')
inc = -1;
//will seek closest working hour in future or in past, one step in one direction per iteration
while(!this.is_working_unit(curr, unit)){
if(both_directins){
curr = tick ? future_target : prev_target;
inc = inc*(-1);
}
curr = gantt.date.add(curr, inc, unit);
curr = gantt.date[unit + '_start'](curr);
if(both_directins){
if(tick){
future_target = curr;
}else{
prev_target = curr;
}
}
tick = !tick;
count++;
if(count > maximum_loop){
dhtmlx.assert(false, "Invalid working time check");
return false;
}
}
if(curr == prev_target || settings.dir == 'past'){
curr = gantt.date.add(curr, 1, unit);
}
return curr;
}
};
gantt.getTask = function(id) {
dhtmlx.assert(id, "Invalid argument for gantt.getTask");
dhtmlx.assert(this._pull[id], "Task not found id=" + id);
return this._pull[id];
};
gantt.getTaskByTime = function(from, to){
var p = this._pull,
res = [],
pos = 0,
taken = 0;
if(!(from || to)){
for (var t in p) res.push(p[t]);
}else{
from = +from || -Infinity;
to = +to || Infinity;
for (var t in p){
var task = p[t];
if (+task.start_date < to && +task.end_date > from)
res.push(task);
}
}
return res;
};
gantt.isTaskExists = function(id) {
return dhtmlx.defined(this._pull[id]);
};
gantt.isTaskVisible = function(id){
if(!this._pull[id])
return false;
if(!(+this._pull[id].start_date < +this._max_date && +this._pull[id].end_date > +this._min_date))
return false;
for(var i= 0, count = this._order.length; i < count; i++)
if(this._order[i] == id) return true;
return false;
};
gantt.updateTask = function(id, item) {
if (!dhtmlx.defined(item)) item = this.getTask(id);
if (this.callEvent("onBeforeTaskUpdate", [id, item])===false) return false;
this._pull[item.id] = item;
if(!this._is_parent_sync(item)){
this._resync_parent(item);
}
this._update_parents(item.id);
this.refreshTask(item.id);
this.callEvent("onAfterTaskUpdate", [id, item]);
this._sync_order();
this._adjust_scales();
};
gantt._add_branch = function(task, silent){
var pid = this.getParent(task);
if (!this.hasChild(pid))
this._branches[pid] = [];
var branch = this.getChildren(pid);
var added_already = false;
for(var i = 0, length = branch.length; i < length; i++){
if(branch[i] == task.id){
added_already = true;
break;
}
}
if(!added_already)
branch.push(task.id);
this._sync_parent(task);
this._sync_order(silent);
};
gantt._move_branch = function(task, old_parent, new_parent){
this.setParent(task, new_parent);
this._sync_parent(task);
this._replace_branch_child(old_parent, task.id);
if(this.isTaskExists(new_parent) || new_parent == this.config.root_id){
this._add_branch(task);
}else{
delete this._branches[task.id];
}
task.$level = this.calculateTaskLevel(task);
this._sync_order();
};
gantt._resync_parent = function(task){
this._move_branch(task, task.$rendered_parent, this.getParent(task));
};
gantt._sync_parent = function(task){
task.$rendered_parent = this.getParent(task);
};
gantt._is_parent_sync = function(task){
return (task.$rendered_parent == this.getParent(task));
};
gantt._replace_branch_child = function(node, old_id, new_id){
var branch = this.getChildren(node);
if (branch){
var newbranch = [];
for (var i=0; i<branch.length; i++){
if (branch[i] != old_id)
newbranch.push(branch[i]);
else if (new_id)
newbranch.push(new_id);
}
this._branches[node] = newbranch;
}
this._sync_order();
};
gantt.addTask = function(item, parent) {
if (!dhtmlx.defined(parent)) parent = this.getParent(item) || 0;
if (!this.isTaskExists(parent)) parent = 0;
this.setParent(item, parent);
item = this._init_task(item);
if (this.callEvent("onBeforeTaskAdd", [item.id, item])===false) return false;
this._pull[item.id] = item;
this._add_branch(item);
this.refreshData();
this._adjust_scales();
this.callEvent("onAfterTaskAdd", [item.id, item]);
return item.id;
};
gantt._default_task_date = function(item, parent_id){
var parent = (parent_id && parent_id != this.config.root_id) ? this.getTask(parent_id) : false,
startDate = '';
if(parent){
startDate = parent.start_date;
}else{
var first = this._order[0];
startDate = first ? this.getTask(first).start_date : this.getState().min_date;
}
return new Date(startDate);
};
gantt._set_default_task_timing = function(task){
task.start_date = task.start_date || gantt._default_task_date(task, this.getParent(task));
task.duration = task.duration || this.config.duration_step;
task.end_date = task.end_date || this.calculateEndDate(task.start_date, task.duration);
};
gantt.createTask = function(item, parent){
item = item || {};
item.id = dhtmlx.uid();
if(!item.start_date){
item.start_date = gantt._default_task_date(item, parent);
}
if(item.text === undefined){
item.text = gantt.locale.labels.new_task;
}
if(item.duration === undefined){
item.duration = 1;
}
if(parent){
this.setParent(item, parent);
parent = this.getTask(parent);
parent.$open = true;
}
if(!this.callEvent("onTaskCreated", [item])){
return null;
}
if (this.config.details_on_create){
item.$new = true;
this._pull[item.id] = this._init_task(item);
this._add_branch(item);
item.$level = this.calculateTaskLevel(item);
this.selectTask(item.id);
this.refreshData();
this.showLightbox(item.id);
}else{
if (this.addTask(item)){
this.showTask(item.id);
this.selectTask(item.id);
}
}
return item.id;
};
gantt.deleteTask = function(id) {
return this._deleteTask(id);
};
//TODO: do something with overcomplicated dataprocessor logic
gantt._getChildLinks = function(id){
var item = this.getTask(id);
if(!item){
return [];
}
var links = item.$source.concat(item.$target);
var branches = this.getChildren(item.id);
for (var i = 0; i < branches.length; i++) {
links = links.concat(this._getChildLinks(branches[i]));
}
var res = {};
for(var i=0; i < links.length; i++){
res[links[i]] = true;
}
links = [];
for(var i in res){
links.push(i);
}
return links;
};
gantt._getTaskTree = function(id){
var item = this.getTask(id);
if(!item){
return [];
}
var items = [];
var branches = this.getChildren(item.id);
for (var i = 0; i < branches.length; i++) {
items.push(branches[i]);
items = items.concat(this._getTaskTree(branches[i]));
}
return items;
};
gantt._deleteRelatedLinks = function(links, silent){
var use_dp = (this._dp && !silent);
var prev_mode = '';
var send_changes = use_dp ? this._dp.updateMode != 'off' : false;
if (use_dp){
prev_mode = this._dp.updateMode;
this._dp.setUpdateMode("off");
}
for(var i =0; i < links.length; i++){
if (use_dp) {
this._dp.setGanttMode("links");
this._dp.setUpdated(links[i],true,"deleted");
}
this._deleteLink(links[i], true);
}
if(use_dp){
this._dp.setUpdateMode(prev_mode);
if(send_changes)
this._dp.sendAllData();
}
};
gantt._deleteRelatedTasks = function(id, silent){
var use_dp = (this._dp && !silent);
var prev_mode = '';
if (use_dp) {
prev_mode = this._dp.updateMode;
this._dp.setGanttMode("tasks");
this._dp.setUpdateMode("off");
}
var tree = this._getTaskTree(id);
for (var i = 0; i < tree.length; i++) {
// add deleted subrow into dataprocessor update list manually
// because silent mode is on
var t_id = tree[i];
this._unset_task(t_id);
if(use_dp){
this._dp.setUpdated(t_id,true,"deleted");
}
}
if(use_dp){
this._dp.setUpdateMode(prev_mode);
}
};
gantt._unset_task = function(id){
var item = this.getTask(id);
this._update_flags(id, null);
delete this._pull[id];
this._move_branch(item, this.getParent(item), null);
};
gantt._deleteTask = function(id, silent) {
var item = this.getTask(id);
if (!silent && this.callEvent("onBeforeTaskDelete", [id, item])===false) return false;
var links = gantt._getChildLinks(id);
this._deleteRelatedTasks(id, silent);
this._deleteRelatedLinks(links, silent);
this._unset_task(id);
if (!silent) {
this.callEvent("onAfterTaskDelete", [id, item]);
this.refreshData();
}
return true;
};
gantt.clearAll = function() {
this._clear_data();
this.callEvent("onClear", []);
this.refreshData();
};
gantt._clear_data = function(){
this._pull = {};
this._branches = {};
this._order = [];
this._order_full = [];
this._lpull = {};
this._update_flags();
this.userdata = {};
};
gantt._update_flags = function(oldid, newid){
// TODO: need a proper way to update all possible flags
if(oldid === undefined){
this._lightbox_id = this._selected_task = null;
if (this._tasks_dnd.drag){
this._tasks_dnd.drag.id = null;
}
}else{
if (this._lightbox_id == oldid)
this._lightbox_id = newid;
if (this._selected_task == oldid){
this._selected_task = newid;
}
if (this._tasks_dnd.drag && this._tasks_dnd.drag.id == oldid){
this._tasks_dnd.drag.id = newid;
}
}
};
gantt.changeTaskId = function(oldid, newid) {
var item = this._pull[newid] = this._pull[oldid];
this._pull[newid].id = newid;
delete this._pull[oldid];
for (var id in this._pull) {
var task = this._pull[id];
if(this.getParent(task) == oldid)
this.setParent(task, newid);
}
this._update_flags(oldid, newid);
this._replace_branch_child(this.getParent(item), oldid, newid);
this.callEvent("onTaskIdChange", [oldid, newid]);
};
gantt._get_duration_unit = function(){
return (gantt._get_line(this.config.duration_unit)*1000) || this.config.duration_unit;
};
gantt._get_safe_type = function(type){
return "task";
};
gantt._get_type_name = function(type_value){
for(var i in this.config.types){
if(this.config.types[i] == type_value){
return i;
}
}
return "task";
};
gantt.getWorkHours = function(date){
return this._working_time_helper.get_working_hours(date);
};
gantt.setWorkTime = function(config){
this._working_time_helper.set_time(config);
};
gantt.isWorkTime = function(date, unit){
var helper = this._working_time_helper;
return helper.is_working_unit(date, unit || this.config.duration_unit);
};
gantt.correctTaskWorkTime = function(task){
if(gantt.config.work_time && gantt.config.correct_work_time){
if(!gantt.isWorkTime(task.start_date)){
task.start_date = gantt.getClosestWorkTime({date:task.start_date, dir:'future'});
task.end_date = gantt.calculateEndDate(task.start_date, task.duration);
}else if(!gantt.isWorkTime(new Date(+task.end_date - 1))){
task.end_date = gantt.calculateEndDate(task.start_date, task.duration);
}
}
};
gantt.getClosestWorkTime = function(config){
var helper = this._working_time_helper;
if(config instanceof Date){
config = {
date:config
};
}
config.dir = config.dir || 'any';
config.unit = config.unit || this.config.duration_unit;
return helper.get_closest_worktime(config);
};
gantt.calculateDuration = function(start_date, end_date){
var helper = this._working_time_helper;
return helper.get_work_units_between(start_date, end_date, this.config.duration_unit, this.config.duration_step);
};
gantt._hasDuration = function(start_date, end_date){
var helper = this._working_time_helper;
return helper.is_work_units_between(start_date, end_date, this.config.duration_unit, this.config.duration_step);
};
gantt.calculateEndDate = function(start, duration, unit){
var helper = this._working_time_helper;
var mult = duration >= 0 ? 1 : -1;
return helper.add_worktime(start, Math.abs(duration), unit || this.config.duration_unit, mult*this.config.duration_step);
};
gantt._init_task = function(task){
if (!dhtmlx.defined(task.id))
task.id = dhtmlx.uid();
if(task.start_date)
task.start_date = gantt.date.parseDate(task.start_date, "xml_date");
if(task.end_date)
task.end_date = gantt.date.parseDate(task.end_date, "xml_date");
if(task.start_date){
if(!task.end_date && task.duration){
task.end_date = this.calculateEndDate(task.start_date, task.duration);
}
}
gantt._init_task_timing(task);
if(task.start_date && task.end_date)
gantt.correctTaskWorkTime(task);
task.$source = [];
task.$target = [];
if(task.parent === undefined){
this.setParent(task, this.config.root_id);
}
task.$open = dhtmlx.defined(task.open) ? task.open : this.config.open_tree_initially;
task.$level = this.calculateTaskLevel(task);
return task;
};
gantt._init_task_timing = function(task){
var task_type = this._get_safe_type(task.type);
if(task.$rendered_type === undefined){
task.$rendered_type = task_type;
}else if(task.$rendered_type != task_type){
delete task.$no_end;
delete task.$no_start;
task.$rendered_type = task_type;
}
if((task.$no_end === undefined || task.$no_start === undefined) && task_type != this.config.types.milestone){
if(task_type == this.config.types.project){
//project duration is always defined by children duration
task.$no_end = task.$no_start = true;
this._set_default_task_timing(task);
}else{
//tasks can have fixed duration, children duration(as projects), or one date fixed, and other defined by nested items
task.$no_end = !(task.end_date || task.duration);
task.$no_start = !task.start_date;
}
}
if(task_type == this.config.types.milestone){
task.end_date = task.start_date;
}
if (task.start_date && task.end_date){
task.duration = this.calculateDuration(task.start_date, task.end_date);
}
task.duration = task.duration || 0;
};
gantt._is_flex_task = function(task){
return !!(task.$no_end || task.$no_start);
};
// downward calculation of project duration
gantt.resetProjectDates = function(task){
if(task.$no_end || task.$no_start){
var dates = this.getSubtaskDates(task.id);
this._assign_project_dates(task, dates.start_date, dates.end_date);
}
};
gantt.getSubtaskDates = function(task_id){
var min = null,
max = null,
root = task_id !== undefined ? task_id : gantt.config.root_id;
this.eachTask(function(child){
if(this._get_safe_type(child.type) == gantt.config.types.project)
return;
if((child.start_date && !child.$no_start) && (!min || min > child.start_date.valueOf()))
min = child.start_date.valueOf();
if((child.end_date && !child.$no_end) && (!max || max < child.end_date.valueOf()))
max = child.end_date.valueOf();
}, root);
return {
start_date: min ? new Date(min) : null,
end_date: max ? new Date(max): null
};
};
gantt._assign_project_dates = function(task, from, to){
if(task.$no_start){
if(from && from != Infinity){
task.start_date = new Date(from);
}else{
task.start_date = this._default_task_date(task, this.getParent(task));
}
}
if(task.$no_end){
if(to && to != -Infinity){
task.end_date = new Date(to);
}else{
task.end_date = this.calculateEndDate(task.start_date, this.config.duration_step);
}
}
if(task.$no_start || task.$no_end){
this._init_task_timing(task);
}
};
// upward calculation of project duration
gantt._update_parents = function(taskId, silent){
if(!taskId) return;
var task = this.getTask(taskId);
var pid = this.getParent(task);
while(!(task.$no_end || task.$no_start) && pid && this.isTaskExists(pid)){
task = this.getTask(pid);
pid = this.getParent(task);
}
if(task.$no_start || task.$no_end){
gantt.resetProjectDates(task);
if(!silent)
this.refreshTask(task.id, true);
}
if(pid && this.isTaskExists(pid)){
this._update_parents(pid, silent);
}
};
gantt.isChildOf = function(child_id, parent_id){
if(!this.isTaskExists(child_id))
return false;
if(parent_id === this.config.root_id)
return this.isTaskExists(child_id);
var task = this.getTask(child_id);
var pid = this.getParent(child_id);
while(task && this.isTaskExists(pid)){
task = this.getTask(pid);
if(task && task.id == parent_id)
return true;
pid = this.getParent(task);
}
return false;
};
gantt.roundDate = function(config){
if(config instanceof Date){
config = {
date: config,
unit: gantt._tasks.unit,
step: gantt._tasks.step
};
}
var date = config.date,
steps = config.step,
unit = config.unit;
var upper = gantt.date[unit + "_start"](new Date(this._min_date));
while(+upper < +date){
upper = gantt.date[unit + "_start"](gantt.date.add(upper, steps, unit));
}
var lower = gantt.date.add(upper, -1*steps, unit);
if(config.dir && config.dir == 'future')
return upper;
if(config.dir && config.dir == 'past')
return lower;
if(Math.abs(date - lower) < Math.abs(upper - date)){
return lower;
}else{
return upper;
}
};
gantt.attachEvent("onBeforeTaskUpdate", function(id, task){
gantt._init_task_timing(task);
return true;
});
gantt.attachEvent("onBeforeTaskAdd", function(id, task){
gantt._init_task_timing(task);
return true;
});
gantt.calculateTaskLevel = function (item) {
var level = 0;
while (this.getParent(item)) {
if (!this.isTaskExists(this.getParent(item))) break;
item = this.getTask(this.getParent(item));
level++;
}
return level;
};
gantt.sort = function(field, desc, parent, silent) {
var render = !silent;//4th argument to cancel redraw after sorting
if (!this.isTaskExists(parent)) {
parent = this.config.root_id;
}
if (!field) field = "order";
var criteria = (typeof(field) == "string") ? (function(a, b) {
if(a[field] == b[field]){
return 0;
}
var result = a[field] > b[field];
if (desc) result = !result;
return result ? 1 : -1;
}) : field;
var els = this.getChildren(parent);
if (els){
var temp = [];
for (var i = els.length - 1; i >= 0; i--)
temp[i] = this._pull[els[i]];
temp.sort(criteria);
for (var i = 0; i < temp.length; i++) {
els[i] = temp[i].id;
this.sort(field, desc, els[i], true);
}
}
if (render) {
this.render();
}
};
gantt.getNext = function(id) {
for (var i = 0; i < this._order.length-1; i++) {
if (this._order[i] == id)
return this._order[i+1];
}
return null;
};
gantt.getPrev = function(id) {
for (var i = 1; i < this._order.length; i++) {
if (this._order[i] == id)
return this._order[i-1];
}
return null;
};
gantt._get_parent_id = function(task){
var parent = this.config.root_id;
if(task){
parent = task.parent;
}
return parent;
};
gantt.getParent = function(id){
var task = null;
if(id.id){
task = id;
}else if(this.isTaskExists(id)){
task = gantt.getTask(id);
}
return this._get_parent_id(task);
};
gantt.setParent = function(task, new_pid){
task.parent = new_pid;
};
gantt.getSiblings = function(id){
if(!this.isTaskExists(id)){
return [];
}
var parent = this.getParent(id);
return this.getChildren(parent);
};
gantt.getNextSibling = function(id){
var siblings = this.getSiblings(id);
for(var i= 0, len = siblings.length; i < len; i++){
if(siblings[i] == id)
return siblings[i+1] || null;
}
return null;
};
gantt.getPrevSibling = function(id){
var siblings = this.getSiblings(id);
for(var i= 0, len = siblings.length; i < len; i++){
if(siblings[i] == id)
return siblings[i-1] || null;
}
return null;
};
gantt._dp_init = function(dp) {
dp.setTransactionMode("POST", true);
dp.serverProcessor += (dp.serverProcessor.indexOf("?") != -1 ? "&" : "?") + "editing=true";
dp._serverProcessor = dp.serverProcessor;
dp.styles = {
updated:"gantt_updated",
inserted:"gantt_inserted",
deleted:"gantt_deleted",
invalid:"gantt_invalid",
error:"gantt_error",
clear:""
};
dp._methods=["_row_style","setCellTextStyle","_change_id","_delete_task"];
dp.setGanttMode = function(mode){
var modes = dp.modes || {};
if(dp._ganttMode){
modes[dp._ganttMode] = {
_in_progress : dp._in_progress,
_invalid : dp._invalid,
updatedRows : dp.updatedRows
};
}
var newState = modes[mode];
if(!newState){
newState = modes[mode] = {
_in_progress : {},
_invalid : {},
updatedRows : []
};
}
dp._in_progress = newState._in_progress;
dp._invalid = newState._invalid;
dp.updatedRows = newState.updatedRows;
dp.modes = modes;
dp._ganttMode = mode;
};
this._sendTaskOrder = function(id, item){
if(item.$drop_target){
dp.setGanttMode("tasks");
this.getTask(id).target = item.$drop_target;
dp.setUpdated(id, true,"order");
delete this.getTask(id).$drop_target;
}
};
this.attachEvent("onAfterTaskAdd", function(id, item) {
dp.setGanttMode("tasks");
dp.setUpdated(id,true,"inserted");
});
this.attachEvent("onAfterTaskUpdate", function(id, item) {
dp.setGanttMode("tasks");
dp.setUpdated(id,true);
gantt._sendTaskOrder(id, item);
});
this.attachEvent("onAfterTaskDelete", function(id, item) {
dp.setGanttMode("tasks");
dp.setUpdated(id,true,"deleted");
if(dp.updateMode != 'off' && !dp._tSend){
dp.sendAllData();
}
});
this.attachEvent("onAfterLinkUpdate", function(id, item) {
dp.setGanttMode("links");
dp.setUpdated(id, true);
});
this.attachEvent("onAfterLinkAdd", function(id, item) {
dp.setGanttMode("links");
dp.setUpdated(id, true,"inserted");
});
this.attachEvent("onAfterLinkDelete", function(id, item) {
dp.setGanttMode("links");
dp.setUpdated(id, true,"deleted");
});
this.attachEvent("onRowDragEnd", function(id, target) {
gantt._sendTaskOrder(id, gantt.getTask(id));
});
dp.attachEvent("onBeforeDataSending", function() {
if(this._tMode == "REST"){
var urlPart = this._ganttMode.substr(0, this._ganttMode.length - 1);// links, tasks -> /link/id, /task/id
this.serverProcessor = this._serverProcessor + (this._serverProcessor.slice(-1) == "/" ? "" : "/") + urlPart;
}else{
this.serverProcessor = this._serverProcessor + window.dhtmlx.url(this._serverProcessor) + "gantt_mode=" + this._ganttMode;
}
return true;
});
var afterUpdate = dp.afterUpdate;
dp.afterUpdate = function(){
var xml;
if(arguments.length == 3){
xml = arguments[1];
}else{
// old dataprocessor
xml = arguments[4];
}
var mode = dp._ganttMode;
var reqUrl = xml.filePath || (xml.xmlDoc ? xml.xmlDoc.responseURL : "");
if(this._tMode != "REST"){
if (reqUrl.indexOf("gantt_mode=links") != -1) {
mode = "links";
}else{
mode = "tasks";
}
}else{
if(reqUrl.indexOf("/link") > reqUrl.indexOf("/task")){
mode = "links";
}else{
mode = "tasks";
}
}
dp.setGanttMode(mode);
var res = afterUpdate.apply(dp, arguments);
dp.setGanttMode(mode);
return res;
};
dp._getRowData=dhtmlx.bind(function(id, pref) {
var task;
if (dp._ganttMode == "tasks")
task = this.isTaskExists(id) ? this.getTask(id) : { id: id };
else
task = this.isLinkExists(id) ? this.getLink(id) : { id: id };
task = dhtmlx.copy(task);
var data = {};
for (var key in task) {
if (key.substr(0, 1) == "$") continue;
var value = task[key];
if (value instanceof Date)
data[key] = this.templates.xml_format(value);
else if(value === null)
data[key] = "";
else
data[key] = value;
}
if(task.$no_start){
task.start_date = "";
task.duration = "";
}
if(task.$no_end){
task.end_date = "";
task.duration = "";
}
data[dp.action_param] = this.getUserData(id, dp.action_param);
return data;
}, this);
this._change_id = dhtmlx.bind(function(oldid, newid) {
if (dp._ganttMode != "tasks")
this.changeLinkId(oldid, newid);
else
this.changeTaskId(oldid, newid);
}, this);
this._row_style = function(row_id, classname){
if (dp._ganttMode != "tasks") return;
var el = gantt.getTaskRowNode(row_id);
if (!el) return;
if (!classname) {
var regexp = / (gantt_updated|gantt_inserted|gantt_deleted|gantt_invalid|gantt_error)/g;
el.className = el.className.replace(regexp, "");
} else
el.className += " " + classname;
};
// fake method for dataprocessor
this._delete_task = function(row_id, node){};
this._dp = dp;
};
gantt.getUserData = function(id, name) {
if (!this.userdata) this.userdata = {};
if (this.userdata[id] && this.userdata[id][name]) return this.userdata[id][name];
return "";
};
gantt.setUserData = function(id, name, value) {
if (!this.userdata) this.userdata = {};
if (!this.userdata[id]) this.userdata[id] = {};
this.userdata[id][name] = value;
};
gantt._init_link = function(link) {
if (!dhtmlx.defined(link.id))
link.id = dhtmlx.uid();
return link;
};
gantt._sync_links = function() {
for (var id in this._pull) {
this._pull[id].$source = [];
this._pull[id].$target = [];
}
for (var id in this._lpull) {
var link = this._lpull[id];
if(this._pull[link.source])
this._pull[link.source].$source.push(id);
if(this._pull[link.target])
this._pull[link.target].$target.push(id);
}
};
gantt.getLink = function(id) {
dhtmlx.assert(this._lpull[id], "Link doesn't exist");
return this._lpull[id];
};
gantt.getLinks = function(){
var links = [];
for (var key in gantt._lpull)
links.push(gantt._lpull[key]);
return links;
};
gantt.isLinkExists = function(id) {
return dhtmlx.defined(this._lpull[id]);
};
gantt.addLink = function(link) {
link = this._init_link(link);
if (this.callEvent("onBeforeLinkAdd", [link.id, link])===false) return false;
this._lpull[link.id] = link;
this._sync_links();
this._render_link(link.id);
this.callEvent("onAfterLinkAdd", [link.id, link]);
return link.id;
};
gantt.updateLink = function(id, data) {
if (!dhtmlx.defined(data))
data = this.getLink(id);
if (this.callEvent("onBeforeLinkUpdate", [id, data]) === false) return false;
this._lpull[id] = data;
this._sync_links();
this._render_link(id);
this.callEvent("onAfterLinkUpdate", [id, data]);
return true;
};
gantt.deleteLink = function(id) {
return this._deleteLink(id);
};
gantt._deleteLink = function(id, silent) {
var link = this.getLink(id);
if (!silent && this.callEvent("onBeforeLinkDelete", [id, link])===false) return false;
delete this._lpull[id];
this._sync_links();
this.refreshLink(id);
if (!silent) this.callEvent("onAfterLinkDelete", [id, link]);
return true;
};
gantt.changeLinkId = function(oldid, newid) {
this._lpull[newid] = this._lpull[oldid];
this._lpull[newid].id = newid;
delete this._lpull[oldid];
this._sync_links();
this.callEvent("onLinkIdChange", [oldid, newid]);
};
gantt.getChildren = function(id) {
return dhtmlx.defined(this._branches[id]) ? this._branches[id] : [];
};
gantt.hasChild = function(id) {
return (dhtmlx.defined(this._branches[id]) && this._branches[id].length);
};
gantt.refreshData = function(){
this._render_data();
};
gantt._configure = function(col, data, force){
for (var key in data)
if (typeof col[key] == "undefined" || force)
col[key] = data[key];
};
gantt._init_skin = function(){
gantt._get_skin(false);
gantt._init_skin = function(){};
};
gantt._get_skin = function(force){
if (!gantt.skin || force){
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var res = links[i].href.match("dhtmlxgantt_([a-z]+).css");
if (res){
gantt.skin = res[1];
break;
}
}
}
if (!gantt.skin) gantt.skin = "terrace";
var skinset = gantt.skins[gantt.skin];
//apply skin related settings
this._configure(gantt.config, skinset.config, force);
var config = gantt.getGridColumns();
if (config[1] && typeof config[1].width == "undefined")
config[1].width = skinset._second_column_width;
if (config[2] && typeof config[2].width == "undefined")
config[2].width = skinset._third_column_width;
if (skinset._lightbox_template)
gantt._lightbox_template = skinset._lightbox_template;
gantt.resetLightbox();
};
gantt.resetSkin = function(){
this.skin = "";
this._get_skin(true);
};
gantt.skins = {};
gantt._lightbox_methods = {};
gantt._lightbox_template="<div class='gantt_cal_ltitle'><span class='gantt_mark'>&nbsp;</span><span class='gantt_time'></span><span class='gantt_title'></span></div><div class='gantt_cal_larea'></div>";
gantt.showLightbox=function(id){
if (!id || gantt._is_readonly(this.getTask(id))) return;
if (!this.callEvent("onBeforeLightbox",[id])) return;
var task = this.getTask(id);
var box = this.getLightbox(this._get_safe_type(task.type));
this._center_lightbox(box);
this.showCover();
this._fill_lightbox(id,box);
this.callEvent("onLightbox",[id]);
};
gantt._get_timepicker_step = function(){
if(this.config.round_dnd_dates){
var scale = gantt._tasks,
step = (this._get_line(scale.unit) * scale.step)/60;//timepicker step is measured in minutes
if(step >= 60*24 || !this._is_chart_visible()){
step = this.config.time_step;
}
return step;
}
return this.config.time_step;
};
gantt.getLabel = function(property, key) {
var sections = this._get_typed_lightbox_config();
for (var i=0; i<sections.length; i++) {
if(sections[i].map_to == property) {
var options = sections[i].options;
for (var j=0; j<options.length; j++) {
if(options[j].key == key) {
return options[j].label;
}
}
}
}
return "";
};
gantt.updateCollection = function(list_name, collection) {
collection = collection.slice(0);
var list = gantt.serverList(list_name);
if (!list) return false;
list.splice(0, list.length);
list.push.apply(list, collection || []);
gantt.resetLightbox();
};
gantt.getLightboxType = function(){
return this._get_safe_type(this._lightbox_type);
};
gantt.getLightbox = function(type){
if(type === undefined)
type = this.getLightboxType();
if (!this._lightbox || this.getLightboxType() != this._get_safe_type(type)){
this._lightbox_type = this._get_safe_type(type);
var d=document.createElement("DIV");
d.className="gantt_cal_light";
var full_width = this._is_lightbox_timepicker();
if (gantt.config.wide_form || full_width)
d.className+=" gantt_cal_light_wide";
if (full_width) {
gantt.config.wide_form = true;
d.className+=" gantt_cal_light_full";
}
d.style.visibility="hidden";
var html = this._lightbox_template;
var buttons = this.config.buttons_left;
for (var i = 0; i < buttons.length; i++){
// needed to migrate from 'dhx_something' to 'gantt_something' naming in a lightbox
var button = this.config._migrate_buttons[buttons[i]] ? this.config._migrate_buttons[buttons[i]] : buttons[i];
html+="<div class='gantt_btn_set gantt_left_btn_set "+button+"_set'><div dhx_button='1' class='"+button+"'></div><div>"+this.locale.labels[button]+"</div></div>";
}
buttons = this.config.buttons_right;
for (var i = 0; i < buttons.length; i++){
var button = this.config._migrate_buttons[buttons[i]] ? this.config._migrate_buttons[buttons[i]] : buttons[i];
html+="<div class='gantt_btn_set gantt_right_btn_set "+button+"_set' style='float:right;'><div dhx_button='1' class='"+button+"'></div><div>"+this.locale.labels[button]+"</div></div>";
}
html+="</div>";
d.innerHTML=html;
if (gantt.config.drag_lightbox){
d.firstChild.onmousedown = gantt._ready_to_dnd;
d.firstChild.onselectstart = function(){ return false; };
d.firstChild.style.cursor = "pointer";
gantt._init_dnd_events();
}
document.body.insertBefore(d,document.body.firstChild);
this._lightbox=d;
var sns = this._get_typed_lightbox_config(type);
html = this._render_sections(sns);
var ds=d.getElementsByTagName("div");
for (var i=0; i<ds.length; i++) {
var t_ds = ds[i];
if (t_ds.className == "gantt_cal_larea") {
t_ds.innerHTML = html;
break;
}
}
//sizes
this.resizeLightbox();
this._init_lightbox_events(this);
d.style.display="none";
d.style.visibility="visible";
}
return this._lightbox;
};
gantt._render_sections = function(sns) {
var html="";
for (var i=0; i < sns.length; i++) {
var block=this.form_blocks[sns[i].type];
if (!block) continue; //ignore incorrect blocks
sns[i].id="area_"+dhtmlx.uid();
var display = sns[i].hidden ? " style='display:none'" : "";
var button = "";
if (sns[i].button){
button = "<div class='gantt_custom_button' index='"+i+"'><div class='gantt_custom_button_"+sns[i].button+"'></div><div>"+this.locale.labels["button_"+sns[i].button]+"</div></div>";
}
if (this.config.wide_form){
html+="<div class='gantt_wrap_section' " + display+">";
}
html+="<div id='"+sns[i].id+"' class='gantt_cal_lsection'>"+button+this.locale.labels["section_"+sns[i].name]+"</div>"+block.render.call(this,sns[i]);
html+="</div>";
}
return html;
};
gantt.resizeLightbox=function(){
var d = this._lightbox;
if (!d) return;
var con = d.childNodes[1];
con.style.height="0px";
con.style.height=con.scrollHeight+"px";
d.style.height=con.scrollHeight+this.config.lightbox_additional_height+"px";
con.style.height=con.scrollHeight+"px"; //it is incredible , how ugly IE can be
};
gantt._center_lightbox = function(box) {
if (box){
box.style.display="block";
var scroll_top = window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop;
var scroll_left = window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft;
var view_height = window.innerHeight||document.documentElement.clientHeight;
if(scroll_top) // if vertical scroll on window
box.style.top=Math.round(scroll_top+Math.max((view_height-box.offsetHeight)/2, 0))+"px";
else // vertical scroll on body
box.style.top=Math.round(Math.max(((view_height-box.offsetHeight)/2), 0) + 9)+"px"; // +9 for compatibility with auto tests
// not quite accurate but used for compatibility reasons
if(document.documentElement.scrollWidth > document.body.offsetWidth) // if horizontal scroll on the window
box.style.left=Math.round(scroll_left+(document.body.offsetWidth-box.offsetWidth)/2)+"px";
else // horizontal scroll on the body
box.style.left=Math.round((document.body.offsetWidth-box.offsetWidth)/2)+"px";
}
};
gantt.showCover = function(){
if(this._cover) return;
this._cover=document.createElement("DIV");
this._cover.className="gantt_cal_cover";
var _document_height = ((document.height !== undefined) ? document.height : document.body.offsetHeight);
var _scroll_height = ((document.documentElement) ? document.documentElement.scrollHeight : 0);
this._cover.style.height = Math.max(_document_height, _scroll_height) + 'px';
document.body.appendChild(this._cover);
};
gantt._init_lightbox_events = function(){
gantt.lightbox_events = {};
gantt.lightbox_events["gantt_save_btn"] = function(e) {
gantt._save_lightbox();
};
gantt.lightbox_events["gantt_delete_btn"] = function(e) {
if(!gantt.callEvent("onLightboxDelete", [gantt._lightbox_id]))
return;
if(gantt.isTaskExists(gantt._lightbox_id)){
gantt.$click.buttons["delete"](gantt._lightbox_id);
}else{
gantt.hideLightbox();
}
};
gantt.lightbox_events["gantt_cancel_btn"] = function(e) {
gantt._cancel_lightbox();
};
gantt.lightbox_events["default"] = function(e, src) {
if (src.getAttribute("dhx_button")) {
gantt.callEvent("onLightboxButton", [src.className, src, e]);
} else {
var index, block, sec;
if (src.className.indexOf("gantt_custom_button") != -1) {
if (src.className.indexOf("gantt_custom_button_") != -1) {
index = src.parentNode.getAttribute("index");
sec = src.parentNode.parentNode;
} else {
index = src.getAttribute("index");
sec = src.parentNode;
src = src.firstChild;
}
}
var sections = gantt._get_typed_lightbox_config();
if (index) {
block = gantt.form_blocks[sections[index].type];
block.button_click(index, src, sec, sec.nextSibling);
}
}
};
dhtmlxEvent(gantt.getLightbox(), "click", function(e) {
e = e || window.event;
var src = e.target ? e.target : e.srcElement;
if (!src.className)
src = src.previousSibling;
if (src && src.className && src.className.indexOf("gantt_btn_set") === 0)
src = src.firstChild;
if (src && src.className) {
var func = dhtmlx.defined(gantt.lightbox_events[src.className]) ? gantt.lightbox_events[src.className] : gantt.lightbox_events["default"];
return func(e, src);
}
return false;
});
gantt.getLightbox().onkeydown=function(e){
switch((e||event).keyCode){
case gantt.keys.edit_save:
if ((e||event).shiftKey) return;
gantt._save_lightbox();
break;
case gantt.keys.edit_cancel:
gantt._cancel_lightbox();
break;
default:
break;
}
};
};
gantt._cancel_lightbox=function(){
var task = this.getLightboxValues();
this.callEvent("onLightboxCancel",[this._lightbox_id, task.$new]);
if(gantt.isTaskExists(task.id) && task.$new){
this._deleteTask(task.id, true);
}
this.refreshData();
this.hideLightbox();
};
gantt._save_lightbox=function(){
var task = this.getLightboxValues();
if(!this.callEvent("onLightboxSave", [this._lightbox_id, task, !!task.$new]))
return;
if (task.$new){
delete task.$new;
this.addTask(task);
}else if(this.isTaskExists(task.id)){
dhtmlx.mixin(this.getTask(task.id), task, true);
this.updateTask(task.id);
}
this.refreshData();
// TODO: do we need any blockable events here to prevent closing lightbox?
this.hideLightbox();
};
gantt._resolve_default_mapping = function(section) {
var mapping = section.map_to;
var time_controls = {"time":true, "duration":true};
if(time_controls[section.type]){
if(section.map_to == 'auto'){
mapping = {start_date: "start_date", end_date: "end_date", duration: "duration"};
}else if(typeof(section.map_to) === "string"){
mapping = {start_date: section.map_to};
}
}
return mapping;
};
gantt.getLightboxValues=function(){
var task = {};
if(gantt.isTaskExists(this._lightbox_id)) {
task = dhtmlx.mixin({}, this.getTask(this._lightbox_id));
}
var sns = this._get_typed_lightbox_config();
for (var i=0; i < sns.length; i++) {
var node = document.getElementById(sns[i].id);
node=(node?node.nextSibling:node);
var block=this.form_blocks[sns[i].type];
if(!block) continue;
var res=block.get_value.call(this,node,task, sns[i]);
var map_to = gantt._resolve_default_mapping(sns[i]);
if (typeof map_to == "string" && map_to != "auto") {
task[map_to] = res;
} else if(typeof map_to == "object") {
for(var property in map_to) {
if(map_to[property])
task[map_to[property]] = res[property];
}
}
}
return task;
};
gantt.hideLightbox=function(){
var box = this.getLightbox();
if (box) box.style.display="none";
this._lightbox_id=null;
this.hideCover();
this.callEvent("onAfterLightbox",[]);
};
gantt.hideCover=function(){
if (this._cover)
this._cover.parentNode.removeChild(this._cover);
this._cover=null;
};
gantt.resetLightbox = function(){
if (gantt._lightbox && !gantt._custom_lightbox)
gantt._lightbox.parentNode.removeChild(gantt._lightbox);
gantt._lightbox = null;
};
gantt._set_lightbox_values = function(data, box){
var task = data;
var s = box.getElementsByTagName("span");
if (gantt.templates.lightbox_header) {
s[1].innerHTML = "";
s[2].innerHTML = gantt.templates.lightbox_header(task.start_date, task.end_date, task);
} else {
s[1].innerHTML = this.templates.task_time(task.start_date, task.end_date, task);
s[2].innerHTML = (this.templates.task_text(task.start_date, task.end_date, task) || "").substr(0, 70); //IE6 fix
}
var sns = this._get_typed_lightbox_config(this.getLightboxType());
for (var i = 0; i < sns.length; i++) {
var section = sns[i];
if(!this.form_blocks[section.type]){
continue;//skip incorrect sections, same check is done during rendering
}
var node = document.getElementById(section.id).nextSibling;
var block = this.form_blocks[section.type];
var map_to = gantt._resolve_default_mapping(sns[i]);
var value = dhtmlx.defined(task[map_to]) ? task[map_to] : section.default_value;
block.set_value.call(gantt, node, value, task, section);
if (section.focus)
block.focus.call(gantt, node);
}
if(data.id)
gantt._lightbox_id = data.id;
};
gantt._fill_lightbox = function(id, box) {
var task = this.getTask(id);
this._set_lightbox_values(task, box);
};
gantt.getLightboxSection = function(name){
var config = this._get_typed_lightbox_config();
var i =0;
for (i; i < config.length; i++)
if (config[i].name == name)
break;
var section = config[i];
if(!section)
return null;
if (!this._lightbox)
this.getLightbox();
var header = document.getElementById(section.id);
var node = header.nextSibling;
var result = {
section: section,
header: header,
node: node,
getValue:function(ev){
return gantt.form_blocks[section.type].get_value.call(gantt, node, (ev||{}), section);
},
setValue:function(value, ev){
return gantt.form_blocks[section.type].set_value.call(gantt, node, value, (ev||{}), section);
}
};
var handler = this._lightbox_methods["get_"+section.type+"_control"];
return handler?handler(result):result;
};
gantt._lightbox_methods.get_template_control = function(result) {
result.control = result.node;
return result;
};
gantt._lightbox_methods.get_select_control = function(result) {
result.control = result.node.getElementsByTagName('select')[0];
return result;
};
gantt._lightbox_methods.get_textarea_control = function(result) {
result.control = result.node.getElementsByTagName('textarea')[0];
return result;
};
gantt._lightbox_methods.get_time_control = function(result) {
result.control = result.node.getElementsByTagName('select'); // array
return result;
};
gantt._init_dnd_events = function(){
dhtmlxEvent(document.body, "mousemove", gantt._move_while_dnd);
dhtmlxEvent(document.body, "mouseup", gantt._finish_dnd);
gantt._init_dnd_events = function(){};
};
gantt._move_while_dnd = function(e){
if (gantt._dnd_start_lb){
if (!document.gantt_unselectable){
document.body.className += " gantt_unselectable";
document.gantt_unselectable = true;
}
var lb = gantt.getLightbox();
var now = (e&&e.target)?[e.pageX, e.pageY]:[event.clientX, event.clientY];
lb.style.top = gantt._lb_start[1]+now[1]-gantt._dnd_start_lb[1]+"px";
lb.style.left = gantt._lb_start[0]+now[0]-gantt._dnd_start_lb[0]+"px";
}
};
gantt._ready_to_dnd = function(e){
var lb = gantt.getLightbox();
gantt._lb_start = [parseInt(lb.style.left,10), parseInt(lb.style.top,10)];
gantt._dnd_start_lb = (e&&e.target)?[e.pageX, e.pageY]:[event.clientX, event.clientY];
};
gantt._finish_dnd = function(){
if (gantt._lb_start){
gantt._lb_start = gantt._dnd_start_lb = false;
document.body.className = document.body.className.replace(" gantt_unselectable","");
document.gantt_unselectable = false;
}
};
gantt._focus = function(node, select){
if (node && node.focus){
if (gantt.config.touch){
//do not focus editor, to prevent auto-zoom
} else {
try {
if (select && node.select) node.select();
node.focus();
}catch(e){ }
}
}
};
gantt.form_blocks={
getTimePicker: function(sns, hidden) {
var time_format = sns.time_format;
if (!time_format) {
// default order
var time_format = ["%d", "%m", "%Y"];
if(gantt._get_line(gantt._tasks.unit) < gantt._get_line("day")){
time_format.push("%H:%i");
}
}
// map: default order => real one
sns._time_format_order = { size:0 };
var cfg = this.config;
var dt = this.date.date_part(new Date(gantt._min_date.valueOf()));
var last = 24*60, first = 0;
if(gantt.config.limit_time_select){
last = 60*cfg.last_hour+1;
first = 60*cfg.first_hour;
dt.setHours(cfg.first_hour);
}
var html = "";
for (var p = 0; p < time_format.length; p++) {
var time_option = time_format[p];
// adding spaces between selects
if (p > 0) {
html += " ";
}
var options = '';
switch (time_option) {
case "%Y":
sns._time_format_order[2] = p;
sns._time_format_order.size++;
//year
var range, offset, start_year, end_year;
if(sns.year_range){
if(!isNaN(sns.year_range)){
range = sns.year_range;
}else if(sns.year_range.push){
// if
start_year = sns.year_range[0];
end_year = sns.year_range[1];
}
}
range = range || 10;
offset = offset || Math.floor(range/2);
start_year = start_year || dt.getFullYear() - offset;
end_year = end_year || start_year + range;
for (var i=start_year; i < end_year; i++)
options+="<option value='"+(i)+"'>"+(i)+"</option>";
break;
case "%m":
sns._time_format_order[1] = p;
sns._time_format_order.size++;
//month
for (var i=0; i < 12; i++)
options+="<option value='"+i+"'>"+this.locale.date.month_full[i]+"</option>";
break;
case "%d":
sns._time_format_order[0] = p;
sns._time_format_order.size++;
//days
for (var i=1; i < 32; i++)
options+="<option value='"+i+"'>"+i+"</option>";
break;
case "%H:%i":
// var last = 24*60, first = 0;
sns._time_format_order[3] = p;
sns._time_format_order.size++;
//hours
var i = first;
var tdate = dt.getDate();
sns._time_values = [];
while(i<last){
var time=this.templates.time_picker(dt);
options+="<option value='"+i+"'>"+time+"</option>";
sns._time_values.push(i);
dt.setTime(dt.valueOf()+this._get_timepicker_step()*60*1000);
var diff = (dt.getDate()!=tdate)?1:0; // moved or not to the next day
i=diff*24*60+dt.getHours()*60+dt.getMinutes();
}
break;
default:
break;
}
if(options){
var readonly = sns.readonly ? "disabled='disabled'" : "";
var display = hidden ? " style='display:none'" : "";
html += "<select "+readonly+display +">"+options+"</select>";
}
}
return html;
},
_fill_lightbox_select: function (s,i,d,map,cfg) {
s[i+map[0]].value=d.getDate();
s[i+map[1]].value=d.getMonth();
s[i+map[2]].value=d.getFullYear();
if (dhtmlx.defined(map[3])) {
var v = d.getHours()*60+ d.getMinutes();
v = Math.round(v/gantt._get_timepicker_step())*gantt._get_timepicker_step();
var input = s[i+map[3]];
input.value= v;
//in case option not shown
input.setAttribute('data-value', v);
}
},
template:{
render: function(sns){
var height=(sns.height||"30")+"px";
return "<div class='gantt_cal_ltext gantt_cal_template' style='height:"+height+";'></div>";
},
set_value:function(node,value,ev,config){
node.innerHTML = value||"";
},
get_value:function(node,ev,config){
return node.innerHTML||"";
},
focus: function(node){
}
},
textarea:{
render:function(sns){
var height=(sns.height||"130")+"px";
return "<div class='gantt_cal_ltext' style='height:"+height+";'><textarea></textarea></div>";
},
set_value:function(node,value,ev){
node.firstChild.value=value||"";
},
get_value:function(node,ev){
return node.firstChild.value;
},
focus:function(node){
var a=node.firstChild; gantt._focus(a, true);
}
},
select:{
render:function(sns){
var height=(sns.height||"23")+"px";
var html="<div class='gantt_cal_ltext' style='height:"+height+";'><select style='width:100%;'>";
for (var i=0; i < sns.options.length; i++)
html+="<option value='"+sns.options[i].key+"'>"+sns.options[i].label+"</option>";
html+="</select></div>";
return html;
},
set_value:function(node,value,ev,sns){
var select = node.firstChild;
if (!select._dhx_onchange && sns.onchange) {
select.onchange = sns.onchange;
select._dhx_onchange = true;
}
if (typeof value == "undefined")
value = (select.options[0]||{}).value;
select.value=value||"";
},
get_value:function(node,ev){
return node.firstChild.value;
},
focus:function(node){
var a=node.firstChild; gantt._focus(a, true);
}
},
time:{
render:function(sns) {
var time = this.form_blocks.getTimePicker.call(this, sns);
var parts = ["<div style='height:"+(sns.height || 30)+"px;padding-top:0px;font-size:inherit;text-align:center;' class='gantt_section_time'>"];
parts.push(time);
if(sns.single_date){
time = this.form_blocks.getTimePicker.call(this, sns, true);
parts.push("<span></span>");
}else{
parts.push("<span style='font-weight:normal; font-size:10pt;'> &nbsp;&ndash;&nbsp; </span>");
}
parts.push(time);
parts.push("</div>");
return parts.join('');
},
set_value:function(node,value,ev,config){
var cfg = config;
var s=node.getElementsByTagName("select");
var map = config._time_format_order;
var map_size = config._time_format_size;
if(cfg.auto_end_date) {
var _update_lightbox_select = function() {
start_date = new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,0,0);
end_date = gantt.calculateEndDate(start_date, 1);
this.form_blocks._fill_lightbox_select(s,map.size, end_date,map,cfg);
};
for(var i=0; i<4; i++) {
s[i].onchange = _update_lightbox_select;
}
}
var mapping = gantt._resolve_default_mapping(config);
if(typeof(mapping) === "string") mapping = {start_date: mapping};
var start_date = ev[mapping.start_date] || new Date();
var end_date = ev[mapping.end_date] || gantt.calculateEndDate(start_date, 1);
this.form_blocks._fill_lightbox_select(s,0,start_date,map,cfg);
this.form_blocks._fill_lightbox_select(s,map.size,end_date,map,cfg);
},
get_value:function(node, ev, config) {
var s=node.getElementsByTagName("select");
var map = config._time_format_order;
var hours = 0, minutes = 0;
if (dhtmlx.defined(map[3])) {
var time = parseInt(s[map[3]].value, 10);
hours = Math.floor(time/60);
minutes = time%60;
}
var start_date=new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,hours,minutes);
hours = minutes = 0;
if (dhtmlx.defined(map[3])) {
var time = parseInt(s[map.size+map[3]].value, 10);
hours = Math.floor(time/60);
minutes = time%60;
}
var end_date=new Date(s[map[2]+map.size].value,s[map[1]+map.size].value,s[map[0]+map.size].value,hours,minutes);
if (end_date <= start_date)
end_date = gantt.date.add(start_date, gantt._get_timepicker_step(),"minute");
var mapped_fields = gantt._resolve_default_mapping(config);
var res = {
start_date: new Date(start_date),
end_date: new Date(end_date)
};
if(typeof mapped_fields == "string"){
return res.start_date;
}else{
return res;
}
},
focus:function(node){
gantt._focus(node.getElementsByTagName("select")[0]);
}
},
duration:{
render:function(sns) {
var time = this.form_blocks.getTimePicker.call(this, sns);
time = "<div class='gantt_time_selects'>"+time+"</div>";
var label = this.locale.labels[this.config.duration_unit + "s"];
var singleDate = sns.single_date ? ' style="display:none"' : "";
var readonly = sns.readonly ? " disabled='disabled'" : "";
var duration = "<div class='gantt_duration' "+singleDate+">" +
"<input type='button' class='gantt_duration_dec' value='-'"+readonly+">" +
"<input type='text' value='5' class='gantt_duration_value'"+readonly+">" +
"<input type='button' class='gantt_duration_inc' value='+'"+readonly+"> " + label + " <span></span>" +
"</div>";
var html = "<div style='height:"+(sns.height || 30)+"px;padding-top:0px;font-size:inherit;' class='gantt_section_time'>"+time+" "+duration+"</div>";
return html;
},
set_value:function(node,value,ev,config){
var cfg = config;
var s=node.getElementsByTagName("select");
var inps = node.getElementsByTagName("input");
var duration = inps[1];
var btns=[inps[0],inps[2]];
var endspan = node.getElementsByTagName("span")[0];
var map = config._time_format_order;
function _calc_date() {
var start_date = gantt.form_blocks.duration._get_start_date.call(gantt, node ,config);
var duration = gantt.form_blocks.duration._get_duration.call(gantt, node ,config);
var end_date = gantt.calculateEndDate(start_date, duration);
endspan.innerHTML = gantt.templates.task_date(end_date);
}
function _change_duration(step) {
var value = duration.value;
value = parseInt(value, 10);
if (window.isNaN(value))
value = 0;
value+=step;
if (value < 1) value = 1;
duration.value = value;
_calc_date();
}
btns[0].onclick = dhtmlx.bind(function() { _change_duration(-1*this.config.duration_step); }, this);
btns[1].onclick = dhtmlx.bind(function() { _change_duration(1*this.config.duration_step); }, this);
s[0].onchange = _calc_date;
s[1].onchange = _calc_date;
s[2].onchange = _calc_date;
if (s[3]) s[3].onchange = _calc_date;
duration.onkeydown = dhtmlx.bind(function(e) {
e = e || window.event;
// up
var code = (e.charCode || e.keyCode || e.which);
if (code == 40) {
_change_duration(-1*this.config.duration_step);
return false;
}
// down
if (code == 38) {
_change_duration(1*this.config.duration_step);
return false;
}
window.setTimeout(function(e) {
_calc_date();
}, 1);
}, this);
duration.onchange = dhtmlx.bind(function(e) { _calc_date(); }, this);
var mapping = gantt._resolve_default_mapping(config);
if(typeof(mapping) === "string") mapping = {start_date: mapping};
var start_date = ev[mapping.start_date] || new Date();
var end_date = ev[mapping.end_date] || gantt.calculateEndDate(start_date, 1);
var duration_val = Math.round(ev[mapping.duration]) || gantt.calculateDuration(start_date, end_date);
gantt.form_blocks._fill_lightbox_select(s, 0, start_date, map, cfg);
duration.value = duration_val;
_calc_date();
},
_get_start_date: function(node, config) {
var s=node.getElementsByTagName("select");
var map = config._time_format_order;
var hours = 0;
var minutes = 0;
if (dhtmlx.defined(map[3])) {
var input = s[map[3]];
var time = parseInt(input.value, 10);
if(isNaN(time) && input.hasAttribute("data-value")){
time = parseInt(input.getAttribute("data-value"), 10);
}
hours = Math.floor(time/60);
minutes = time%60;
}
return new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,hours,minutes);
},
_get_duration: function(node, config) {
var duration = node.getElementsByTagName("input")[1];
duration = parseInt(duration.value, 10);
if (!duration || window.isNaN(duration)) duration = 1;
if (duration < 0) duration *= -1;
return duration;
},
get_value:function(node, ev, config) {
var start_date = gantt.form_blocks.duration._get_start_date(node, config);
var duration = gantt.form_blocks.duration._get_duration(node, config);
var end_date = gantt.calculateEndDate(start_date, duration);
var mapped_fields = gantt._resolve_default_mapping(config);
var res = {
start_date: new Date(start_date),
end_date: new Date(end_date),
duration: duration
};
if(typeof mapped_fields == "string"){
return res.start_date;
}else{
return res;
}
},
focus:function(node){
gantt._focus(node.getElementsByTagName("select")[0]);
}
},
parent: {
_filter : function(options, config, item_id){
var filter = config.filter || function(){ return true;};
options = options.slice(0);
for(var i=0; i < options.length; i++){
var task = options[i];
if(task.id == item_id || gantt.isChildOf(task.id, item_id) || filter(task.id, task) === false){
options.splice(i, 1);
i--;
}
}
return options;
},
_display : function(config, item_id){
var tasks = [],
options = [];
if(item_id){
tasks = gantt.getTaskByTime();
if(config.allow_root){
tasks.unshift({id:gantt.config.root_id, text:config.root_label || ""});
}
tasks = this._filter(tasks, config, item_id);
if(config.sort){
tasks.sort(config.sort);
}
}
var text = config.template || gantt.templates.task_text;
for(var i = 0; i < tasks.length; i++){
var label = text.apply(gantt, [tasks[i].start_date, tasks[i].end_date, tasks[i]]);
if(label === undefined){
label = "";
}
options.push({
key: tasks[i].id,
label: label
});
}
config.options = options;
config.map_to = config.map_to || "parent";
return gantt.form_blocks.select.render.apply(this, arguments);
},
render : function(sns){
return gantt.form_blocks.parent._display(sns, false);
},
set_value:function(node,value,ev,config){
var tmpDom = document.createElement("div");
tmpDom.innerHTML = gantt.form_blocks.parent._display(config, ev.id);
var newOptions = tmpDom.removeChild(tmpDom.firstChild);
node.onselect = null;
node.parentNode.replaceChild(newOptions, node);
return gantt.form_blocks.select.set_value.apply(gantt, [newOptions,value,ev,config]);
},
get_value:function(){
return gantt.form_blocks.select.get_value.apply(gantt, arguments);
},
focus:function(){
return gantt.form_blocks.select.focus.apply(gantt, arguments);
}
}
};
gantt._is_lightbox_timepicker = function() {
var s = this._get_typed_lightbox_config();
for (var i = 0; i < s.length; i++)
if (s[i].name == "time" && s[i].type == "time")
return true;
return false;
};
gantt._dhtmlx_confirm = function(message, title, callback, ok) {
if (!message)
return callback();
var opts = { text: message };
if (title)
opts.title = title;
if(ok){
opts.ok = ok;
}
if (callback) {
opts.callback = function(result) {
if (result)
callback();
};
}
dhtmlx.confirm(opts);
};
gantt._get_typed_lightbox_config = function(type){
if(type === undefined){
type = this.getLightboxType();
}
var field = this._get_type_name(type);
if(gantt.config.lightbox[field+"_sections"]){
return gantt.config.lightbox[field+"_sections"];
}else{
return gantt.config.lightbox.sections;
}
};
gantt._silent_redraw_lightbox = function(type){
var oldType = this.getLightboxType();
if(this.getState().lightbox){
var taskId = this.getState().lightbox;
var formData = this.getLightboxValues(),
task = dhtmlx.copy(this.getTask(taskId));
this.resetLightbox();
var updTask = dhtmlx.mixin(task, formData, true);
var box = this.getLightbox(type ? type : undefined);
this._center_lightbox(this.getLightbox());
this._set_lightbox_values(updTask, box);
}else{
this.resetLightbox();
this.getLightbox(type ? type : undefined);
}
this.callEvent("onLightboxChange", [oldType, this.getLightboxType()]);
};
gantt._extend_to_optional = function(lightbox_block){
var duration = lightbox_block;
var optional_time = {
render : duration.render,
focus : duration.focus,
set_value: function (node, value, task, section){
var mapping = gantt._resolve_default_mapping(section);
if(!task[mapping.start_date]){
optional_time.disable(node, section);
var val = {};
for(var i in mapping){
//take default values from the time control from task start/end dates
val[mapping[i]] = task[i];
}
return duration.set_value.call(gantt, node, value, val, section);//set default value
}else{
optional_time.enable(node, section);
return duration.set_value.call(gantt, node, value, task, section);
}
},
get_value: function (node, task, section){
if(section.disabled){
return {start_date: null};
}else{
return duration.get_value.call(gantt, node, task, section);
}
},
update_block : function(node, section){
gantt.callEvent("onSectionToggle", [gantt._lightbox_id, section]);
node.style.display = section.disabled ? "none" : "block";
if(section.button){
var button = node.previousSibling.firstChild.firstChild,
labels = gantt.locale.labels;
var button_text = section.disabled ? labels[section.name + "_enable_button"] : labels[section.name + "_disable_button"];
button.nextSibling.innerHTML = button_text;
}
gantt.resizeLightbox();
},
disable: function(node, section){
section.disabled = true;
optional_time.update_block(node, section);
},
enable:function(node, section){
section.disabled = false;
optional_time.update_block(node, section);
},
button_click: function(index, el, section, container){
if(gantt.callEvent("onSectionButton", [gantt._lightbox_id, section]) === false){
return;
}
var config = gantt._get_typed_lightbox_config()[index];
if(config.disabled){
optional_time.enable(container, config);
}else{
optional_time.disable(container, config);
}
}
};
return optional_time;
};
gantt.form_blocks.duration_optional = gantt._extend_to_optional(gantt.form_blocks.duration);
gantt.form_blocks.time_optional = gantt._extend_to_optional(gantt.form_blocks.time);
/**
* @desc: constructor, data processor object
* @param: serverProcessorURL - url used for update
* @type: public
*/
function dataProcessor(serverProcessorURL){
this.serverProcessor = serverProcessorURL;
this.action_param="!nativeeditor_status";
this.object = null;
this.updatedRows = []; //ids of updated rows
this.autoUpdate = true;
this.updateMode = "cell";
this._tMode="GET";
this._headers = null;
this._payload = null;
this.post_delim = "_";
this._waitMode=0;
this._in_progress={};//?
this._invalid={};
this.mandatoryFields=[];
this.messages=[];
this.styles={
updated:"font-weight:bold;",
inserted:"font-weight:bold;",
deleted:"text-decoration : line-through;",
invalid:"background-color:FFE0E0;",
invalid_cell:"border-bottom:2px solid red;",
error:"color:red;",
clear:"font-weight:normal;text-decoration:none;"
};
this.enableUTFencoding(true);
dhx4._eventable(this);
return this;
}
dataProcessor.prototype={
setTransactionMode:function(mode,total){
if (typeof mode == "object"){
this._tMode = mode.mode || this._tMode;
this._headers = this._headers || mode.headers;
this._payload = this._payload || mode.payload;
} else {
this._tMode=mode;
this._tSend=total;
}
if (this._tMode == "REST"){
this._tSend = false;
this._endnm = true;
}
},
escape:function(data){
if (this._utf)
return encodeURIComponent(data);
else
return escape(data);
},
/**
* @desc: allows to set escaping mode
* @param: true - utf based escaping, simple - use current page encoding
* @type: public
*/
enableUTFencoding:function(mode){
this._utf=dhx4.s2b(mode);
},
/**
* @desc: allows to define, which column may trigger update
* @param: val - array or list of true/false values
* @type: public
*/
setDataColumns:function(val){
this._columns=(typeof val == "string")?val.split(","):val;
},
/**
* @desc: get state of updating
* @returns: true - all in sync with server, false - some items not updated yet.
* @type: public
*/
getSyncState:function(){
return !this.updatedRows.length;
},
/**
* @desc: enable/disable named field for data syncing, will use column ids for grid
* @param: mode - true/false
* @type: public
*/
enableDataNames:function(mode){
this._endnm=dhx4.s2b(mode);
},
/**
* @desc: enable/disable mode , when only changed fields and row id send to the server side, instead of all fields in default mode
* @param: mode - true/false
* @type: public
*/
enablePartialDataSend:function(mode){
this._changed=dhx4.s2b(mode);
},
/**
* @desc: set if rows should be send to server automaticaly
* @param: mode - "row" - based on row selection changed, "cell" - based on cell editing finished, "off" - manual data sending
* @type: public
*/
setUpdateMode:function(mode,dnd){
this.autoUpdate = (mode=="cell");
this.updateMode = mode;
this.dnd=dnd;
},
ignore:function(code,master){
this._silent_mode=true;
code.call(master||window);
this._silent_mode=false;
},
/**
* @desc: mark row as updated/normal. check mandatory fields,initiate autoupdate (if turned on)
* @param: rowId - id of row to set update-status for
* @param: state - true for "updated", false for "not updated"
* @param: mode - update mode name
* @type: public
*/
setUpdated:function(rowId,state,mode){
if (this._silent_mode) return;
var ind=this.findRow(rowId);
mode=mode||"updated";
var existing = this.obj.getUserData(rowId,this.action_param);
if (existing && mode == "updated") mode=existing;
if (state){
this.set_invalid(rowId,false); //clear previous error flag
this.updatedRows[ind]=rowId;
this.obj.setUserData(rowId,this.action_param,mode);
if (this._in_progress[rowId])
this._in_progress[rowId]="wait";
} else{
if (!this.is_invalid(rowId)){
this.updatedRows.splice(ind,1);
this.obj.setUserData(rowId,this.action_param,"");
}
}
//clear changed flag
if (!state)
this._clearUpdateFlag(rowId);
this.markRow(rowId,state,mode);
if (state && this.autoUpdate) this.sendData(rowId);
},
_clearUpdateFlag:function(id){},
markRow:function(id,state,mode){
var str="";
var invalid=this.is_invalid(id);
if (invalid){
str=this.styles[invalid];
state=true;
}
if (this.callEvent("onRowMark",[id,state,mode,invalid])){
//default logic
str=this.styles[state?mode:"clear"]+str;
this.obj[this._methods[0]](id,str);
if (invalid && invalid.details){
str+=this.styles[invalid+"_cell"];
for (var i=0; i < invalid.details.length; i++)
if (invalid.details[i])
this.obj[this._methods[1]](id,i,str);
}
}
},
getState:function(id){
return this.obj.getUserData(id,this.action_param);
},
is_invalid:function(id){
return this._invalid[id];
},
set_invalid:function(id,mode,details){
if (details) mode={value:mode, details:details, toString:function(){ return this.value.toString(); }};
this._invalid[id]=mode;
},
/**
* @desc: check mandatory fields and varify values of cells, initiate update (if specified)
* @param: rowId - id of row to set update-status for
* @type: public
*/
checkBeforeUpdate:function(rowId){
return true;
},
/**
* @desc: send row(s) values to server
* @param: rowId - id of row which data to send. If not specified, then all "updated" rows will be send
* @type: public
*/
sendData:function(rowId){
if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return;
if (this.obj.editStop) this.obj.editStop();
if(typeof rowId == "undefined" || this._tSend) return this.sendAllData();
if (this._in_progress[rowId]) return false;
this.messages=[];
if (!this.checkBeforeUpdate(rowId) && this.callEvent("onValidationError",[rowId,this.messages])) return false;
this._beforeSendData(this._getRowData(rowId),rowId);
},
_beforeSendData:function(data,rowId){
if (!this.callEvent("onBeforeUpdate",[rowId,this.getState(rowId),data])) return false;
this._sendData(data,rowId);
},
serialize:function(data, id){
if (typeof data == "string")
return data;
if (typeof id != "undefined")
return this.serialize_one(data,"");
else{
var stack = [];
var keys = [];
for (var key in data)
if (data.hasOwnProperty(key)){
stack.push(this.serialize_one(data[key],key+this.post_delim));
keys.push(key);
}
stack.push("ids="+this.escape(keys.join(",")));
if (dhtmlx.security_key)
stack.push("dhx_security="+dhtmlx.security_key);
return stack.join("&");
}
},
serialize_one:function(data, pref){
if (typeof data == "string")
return data;
var stack = [];
for (var key in data)
if (data.hasOwnProperty(key)){
if ((key == "id" || key == this.action_param) && this._tMode == "REST") continue;
stack.push(this.escape((pref||"")+key)+"="+this.escape(data[key]));
}
return stack.join("&");
},
_sendData:function(a1,rowId){
if (!a1) return; //nothing to send
if (!this.callEvent("onBeforeDataSending",rowId?[rowId,this.getState(rowId),a1]:[null, null, a1])) return false;
if (rowId)
this._in_progress[rowId]=(new Date()).valueOf();
var that = this;
var back = function(xml){
var ids = [];
if (rowId)
ids.push(rowId);
else if (a1)
for (var key in a1)
ids.push(key);
return that.afterUpdate(that,xml,ids);
};
var a3 = this.serverProcessor+(this._user?(dhtmlx.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):"");
if (this._tMode=="GET")
dhx4.ajax.get(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId), back);
else if (this._tMode == "POST")
dhx4.ajax.post(a3,this.serialize(a1,rowId), back);
else if (this._tMode == "REST"){
var state = this.getState(rowId);
var url = a3.replace(/(\&|\?)editing\=true/,"");
var data = "";
var method = "post";
if (state == "inserted"){
data = this.serialize(a1, rowId);
} else if (state == "deleted"){
method = "DELETE";
url = url + (url.slice(-1) == "/" ? "" : "/") + rowId;
} else {
method = "PUT";
data = this.serialize(a1, rowId);
url = url + (url.slice(-1) == "/" ? "" : "/") + rowId;
}
if (this._payload)
for (var key in this._payload)
url = url + dhtmlx.url(url) + this.escape(key) + "=" + this.escape(this._payload[key]);
dhx4.ajax.query({
url:url,
method:method,
headers:this._headers,
data:data,
callback:back
});
}
this._waitMode++;
},
sendAllData:function(){
if (!this.updatedRows.length) return;
this.messages=[]; var valid=true;
for (var i=0; i<this.updatedRows.length; i++)
valid&=this.checkBeforeUpdate(this.updatedRows[i]);
if (!valid && !this.callEvent("onValidationError",["",this.messages])) return false;
if (this._tSend)
this._sendData(this._getAllData());
else
for (var i=0; i<this.updatedRows.length; i++)
if (!this._in_progress[this.updatedRows[i]]){
if (this.is_invalid(this.updatedRows[i])) continue;
this._beforeSendData(this._getRowData(this.updatedRows[i]),this.updatedRows[i]);
if (this._waitMode && (this.obj.mytype=="tree" || this.obj._h2)) return; //block send all for tree
}
},
_getAllData:function(rowId){
var out={};
var has_one = false;
for(var i=0;i<this.updatedRows.length;i++){
var id=this.updatedRows[i];
if (this._in_progress[id] || this.is_invalid(id)) continue;
if (!this.callEvent("onBeforeUpdate",[id,this.getState(id), this._getRowData(id)])) continue;
out[id]=this._getRowData(id,id+this.post_delim);
has_one = true;
this._in_progress[id]=(new Date()).valueOf();
}
return has_one?out:null;
},
/**
* @desc: specify column which value should be varified before sending to server
* @param: ind - column index (0 based)
* @param: verifFunction - function (object) which should verify cell value (if not specified, then value will be compared to empty string). Two arguments will be passed into it: value and column name
* @type: public
*/
setVerificator:function(ind,verifFunction){
this.mandatoryFields[ind] = verifFunction||(function(value){return (value!=="");});
},
/**
* @desc: remove column from list of those which should be verified
* @param: ind - column Index (0 based)
* @type: public
*/
clearVerificator:function(ind){
this.mandatoryFields[ind] = false;
},
findRow:function(pattern){
var i=0;
for(i=0;i<this.updatedRows.length;i++)
if(pattern==this.updatedRows[i]) break;
return i;
},
/**
* @desc: define custom actions
* @param: name - name of action, same as value of action attribute
* @param: handler - custom function, which receives a XMl response content for action
* @type: private
*/
defineAction:function(name,handler){
if (!this._uActions) this._uActions=[];
this._uActions[name]=handler;
},
/**
* @desc: used in combination with setOnBeforeUpdateHandler to create custom client-server transport system
* @param: sid - id of item before update
* @param: tid - id of item after up0ate
* @param: action - action name
* @type: public
* @topic: 0
*/
afterUpdateCallback:function(sid, tid, action, btag) {
var marker = sid;
var correct=(action!="error" && action!="invalid");
if (!correct) this.set_invalid(sid,action);
if ((this._uActions)&&(this._uActions[action])&&(!this._uActions[action](btag)))
return (delete this._in_progress[marker]);
if (this._in_progress[marker]!="wait")
this.setUpdated(sid, false);
var soid = sid;
switch (action) {
case "inserted":
case "insert":
if (tid != sid) {
this.obj[this._methods[2]](sid, tid);
sid = tid;
}
break;
case "delete":
case "deleted":
this.obj.setUserData(sid, this.action_param, "true_deleted");
this.obj[this._methods[3]](sid);
delete this._in_progress[marker];
return this.callEvent("onAfterUpdate", [sid, action, tid, btag]);
break;
}
if (this._in_progress[marker]!="wait"){
if (correct) this.obj.setUserData(sid, this.action_param,'');
delete this._in_progress[marker];
} else {
delete this._in_progress[marker];
this.setUpdated(tid,true,this.obj.getUserData(sid,this.action_param));
}
this.callEvent("onAfterUpdate", [soid, action, tid, btag]);
},
/**
* @desc: response from server
* @param: xml - XMLLoader object with response XML
* @type: private
*/
afterUpdate:function(that,xml,id){
//try to use json first
if (window.JSON){
try{
var tag = JSON.parse(xml.xmlDoc.responseText);
var action = tag.action || this.getState(id) || "updated";
var sid = tag.sid || id[0];
var tid = tag.tid || id[0];
that.afterUpdateCallback(sid, tid, action, tag);
that.finalizeUpdate();
return;
} catch(e){
}
}
//xml response
var top = dhx4.ajax.xmltop("data", xml.xmlDoc); //fix incorrect content type in IE
if (!top) return this.cleanUpdate(id);
var atag=dhx4.ajax.xpath("//data/action", top);
if (!atag.length) return this.cleanUpdate(id);
for (var i=0; i<atag.length; i++){
var btag=atag[i];
var action = btag.getAttribute("type");
var sid = btag.getAttribute("sid");
var tid = btag.getAttribute("tid");
that.afterUpdateCallback(sid,tid,action,btag);
}
that.finalizeUpdate();
},
cleanUpdate:function(id){
if (id)
for (var i = 0; i < id.length; i++)
delete this._in_progress[id[i]];
},
finalizeUpdate:function(){
if (this._waitMode) this._waitMode--;
if ((this.obj.mytype=="tree" || this.obj._h2) && this.updatedRows.length)
this.sendData();
this.callEvent("onAfterUpdateFinish",[]);
if (!this.updatedRows.length)
this.callEvent("onFullSync",[]);
},
/**
* @desc: initializes data-processor
* @param: anObj - dhtmlxGrid object to attach this data-processor to
* @type: public
*/
init:function(anObj){
this.obj = anObj;
if (this.obj._dp_init)
this.obj._dp_init(this);
},
setOnAfterUpdate:function(ev){
this.attachEvent("onAfterUpdate",ev);
},
enableDebug:function(mode){
},
setOnBeforeUpdateHandler:function(func){
this.attachEvent("onBeforeDataSending",func);
},
/* starts autoupdate mode
@param interval
time interval for sending update requests
*/
setAutoUpdate: function(interval, user) {
interval = interval || 2000;
this._user = user || (new Date()).valueOf();
this._need_update = false;
this._loader = null;
this._update_busy = false;
this.attachEvent("onAfterUpdate",function(sid,action,tid,xml_node){
this.afterAutoUpdate(sid, action, tid, xml_node);
});
this.attachEvent("onFullSync",function(){
this.fullSync();
});
var self = this;
window.setInterval(function(){
self.loadUpdate();
}, interval);
},
/* process updating request answer
if status == collision version is depricated
set flag for autoupdating immidiatly
*/
afterAutoUpdate: function(sid, action, tid, xml_node) {
if (action == 'collision') {
this._need_update = true;
return false;
} else {
return true;
}
},
/* callback function for onFillSync event
call update function if it's need
*/
fullSync: function() {
if (this._need_update == true) {
this._need_update = false;
this.loadUpdate();
}
return true;
},
/* sends query to the server and call callback function
*/
getUpdates: function(url,callback){
if (this._update_busy)
return false;
else
this._update_busy = true;
this._loader = this._loader || new dtmlXMLLoaderObject(true);
this._loader.async=true;
this._loader.waitCall=callback;
this._loader.loadXML(url);
},
/* returns xml node value
@param node
xml node
*/
_v: function(node) {
if (node.firstChild) return node.firstChild.nodeValue;
return "";
},
/* returns values array of xml nodes array
@param arr
array of xml nodes
*/
_a: function(arr) {
var res = [];
for (var i=0; i < arr.length; i++) {
res[i]=this._v(arr[i]);
};
return res;
},
/* loads updates and processes them
*/
loadUpdate: function(){
var self = this;
var version = this.obj.getUserData(0,"version");
var url = this.serverProcessor+dhtmlx.url(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+version].join("&");
url = url.replace("editing=true&","");
this.getUpdates(url, function(){
var vers = self._loader.doXPath("//userdata");
self.obj.setUserData(0,"version",self._v(vers[0]));
var upds = self._loader.doXPath("//update");
if (upds.length){
self._silent_mode = true;
for (var i=0; i<upds.length; i++) {
var status = upds[i].getAttribute('status');
var id = upds[i].getAttribute('id');
var parent = upds[i].getAttribute('parent');
switch (status) {
case 'inserted':
self.callEvent("insertCallback",[upds[i], id, parent]);
break;
case 'updated':
self.callEvent("updateCallback",[upds[i], id, parent]);
break;
case 'deleted':
self.callEvent("deleteCallback",[upds[i], id, parent]);
break;
}
}
self._silent_mode = false;
}
self._update_busy = false;
self = null;
});
}
};
// --#include core/data_task_types.js
/*
asserts will be removed in final code, so you can place them anythere
without caring about performance impacts
*/
dhtmlx.assert = function(check, message){
//jshint -W087
if (!check){
dhtmlx.message({ type:"error", text:message, expire:-1 });
debugger;
}
};
//initial initialization
gantt.init = function(node, from, to){
this.callEvent("onBeforeGanttReady", []);
if(from && to){
this.config.start_date = this._min_date = new Date(from);
this.config.end_date = this._max_date = new Date(to);
}
this._init_skin();
if (!this.config.scroll_size)
this.config.scroll_size = this._detectScrollSize();
dhtmlxEvent(window, "resize", this._on_resize);
//can be called only once
this.init = function(node){
if (this.$container && this.$container.parentNode){
this.$container.parentNode.removeChild(this.$container);
this.$container = null;
}
this._reinit(node);
};
this._reinit(node);
};
gantt._reinit = function(node){
this._init_html_area(node);
this._set_sizes();
this._clear_renderers();
this.resetLightbox();
this._update_flags();
this._init_touch_events();
this._init_templates();
this._init_grid();
this._init_tasks();
this._set_scroll_events();
dhtmlxEvent(this.$container, "click", this._on_click);
dhtmlxEvent(this.$container, "dblclick", this._on_dblclick);
dhtmlxEvent(this.$container, "mousemove", this._on_mousemove);
dhtmlxEvent(this.$container, "contextmenu", this._on_contextmenu);
this.callEvent("onGanttReady", []);
this.render();
};
//renders initial html markup
gantt._init_html_area = function(node){
if (typeof node == "string")
this._obj = document.getElementById(node);
else
this._obj = node;
dhtmlx.assert(this._obj, "Invalid html container: "+node);
var html = "<div class='gantt_container'><div class='gantt_grid'></div><div class='gantt_task'></div>";
html += "<div class='gantt_ver_scroll'><div></div></div><div class='gantt_hor_scroll'><div></div></div></div>";
this._obj.innerHTML = html;
//store links for further reference
this.$container = this._obj.firstChild;
var childs = this.$container.childNodes;
this.$grid = childs[0];
this.$task = childs[1];
this.$scroll_ver = childs[2];
this.$scroll_hor = childs[3];
this.$grid.innerHTML = "<div class='gantt_grid_scale'></div><div class='gantt_grid_data'></div>";
this.$grid_scale = this.$grid.childNodes[0];
this.$grid_data = this.$grid.childNodes[1];
this.$task.innerHTML = "<div class='gantt_task_scale'></div><div class='gantt_data_area'><div class='gantt_task_bg'></div><div class='gantt_links_area'></div><div class='gantt_bars_area'></div></div>";
this.$task_scale = this.$task.childNodes[0];
this.$task_data = this.$task.childNodes[1];
this.$task_bg = this.$task_data.childNodes[0];
this.$task_links = this.$task_data.childNodes[1];
this.$task_bars = this.$task_data.childNodes[2];
};
gantt.$click={
buttons:{
"edit":function(id){
gantt.showLightbox(id);
},
"delete":function(id){
var question = gantt.locale.labels.confirm_deleting;
var title = gantt.locale.labels.confirm_deleting_title;
gantt._dhtmlx_confirm(question, title, function(){
var task = gantt.getTask(id);
if(task.$new){
gantt._deleteTask(id, true);
gantt.refreshData();
}else{
gantt.deleteTask(id);
}
gantt.hideLightbox();
});
}
}
};
gantt._calculate_content_height = function(){
var scale_height = this.config.scale_height,
rows_height = this._order.length*this.config.row_height,
hor_scroll_height = this._scroll_hor ? this.config.scroll_size + 1 : 0;
if(!(this._is_grid_visible() || this._is_chart_visible())){
return 0;
}else{
return scale_height + rows_height + 2 + hor_scroll_height;
}
};
gantt._calculate_content_width = function(){
var grid_width = this._get_grid_width(),
chart_width = this._tasks ? this._tasks.full_width : 0,
ver_scroll_width = this._scroll_ver ? this.config.scroll_size + 1 : 0;
if(!this._is_chart_visible()){
chart_width = 0;
}
if(!this._is_grid_visible()){
grid_width = 0;
}
return grid_width + chart_width + 1;
};
gantt._get_resize_options = function(){
var res = {x:false, y:false};
if(this.config.autosize == "xy"){
res.x = res.y = true;
}else if(this.config.autosize == "y" || this.config.autosize === true){
res.y = true;
}else if(this.config.autosize == "x"){
res.x = true;
}
return res;
};
gantt._clean_el_size = function(value){
return ((value || "").toString().replace("px", "") * 1 || 0);
};
gantt._get_box_styles = function(){
var computed = null;
if(window.getComputedStyle){
computed = window.getComputedStyle(this._obj, null);
}else{
//IE with elem.currentStyle does not calculate sizes from %, so will use the default approach
computed = {
"width":this._obj.clientWidth,
"height":this._obj.clientHeight
};
}
var properties = [
"width",
"height",
"paddingTop",
"paddingBottom",
"paddingLeft",
"paddingRight",
"borderLeftWidth",
"borderRightWidth",
"borderTopWidth",
"borderBottomWidth"
];
var styles = {
boxSizing:(computed.boxSizing == "border-box")
};
if(computed.MozBoxSizing){
styles.boxSizing = (computed.MozBoxSizing == "border-box");
}
for(var i =0; i < properties.length; i++){
styles[properties[i]] = computed[properties[i]] ? this._clean_el_size(computed[properties[i]]) : 0;
}
var box = {
horPaddings : (styles.paddingLeft + styles.paddingRight + styles.borderLeftWidth + styles.borderRightWidth),
vertPaddings : (styles.paddingTop + styles.paddingBottom + styles.borderTopWidth + styles.borderBottomWidth),
borderBox: styles.boxSizing,
innerWidth : styles.width,
innerHeight : styles.height,
outerWidth : styles.width,
outerHeight : styles.height
};
if(box.borderBox){
box.innerWidth -= box.horPaddings;
box.innerHeight -= box.vertPaddings;
}else{
box.outerWidth += box.horPaddings;
box.outerHeight += box.vertPaddings;
}
return box;
};
gantt._do_autosize = function(){
var resize = this._get_resize_options();
var boxSizes = this._get_box_styles();
if(resize.y){
var reqHeight = this._calculate_content_height();
if(boxSizes.borderBox){
reqHeight += boxSizes.vertPaddings;
}
this._obj.style.height = reqHeight + 'px';
}
if(resize.x){
var reqWidth = this._calculate_content_width();
if(boxSizes.borderBox){
reqWidth += boxSizes.horPaddings;
}
this._obj.style.width = reqWidth + 'px';
}
};
//set sizes to top level html element
gantt._set_sizes = function(){
this._do_autosize();
var boxSizes = this._get_box_styles();
this._y = boxSizes.innerHeight;
if (this._y < 20) return;
//same height
this.$grid.style.height = this.$task.style.height = Math.max(this._y - this.$scroll_hor.offsetHeight - 2, 0) +"px";
var dataHeight = Math.max((this._y - (this.config.scale_height||0) - this.$scroll_hor.offsetHeight - 2), 0);
this.$grid_data.style.height = this.$task_data.style.height = dataHeight + "px";
//share width
var gridWidth = Math.max(this._get_grid_width()-1, 0);
this.$grid.style.width = gridWidth +"px";
this.$grid.style.display = gridWidth === 0 ? 'none' : '';
boxSizes = this._get_box_styles();
this._x = boxSizes.innerWidth;
if (this._x < 20) return;
this.$grid_data.style.width = Math.max(this._get_grid_width()-1, 0) +"px";
this.$task.style.width = Math.max(this._x - this._get_grid_width() - 2, 0) +"px";
};
gantt.getScrollState = function(){
if(this.$task && this.$task_data)
return { x:this.$task.scrollLeft, y:this.$task_data.scrollTop };
else
return null;
};
gantt._save_scroll_state = function(x, y){
// according to Chrome profiler
// getting-setting scrollLeft for restoring scroll position after render takes surprisingly big amount of time
// 2x-3x times more than setting innerHTML (if using gantt.config.static_background)
// Will store scroll position in memory instead of getting actual values from DOM
var pos = {};
this._cached_scroll_pos = this._cached_scroll_pos || {};
if(x !== undefined){pos.x = x;}
if(y !== undefined){pos.y = y;}
dhtmlx.mixin(this._cached_scroll_pos, pos, true);
};
gantt._restore_scroll_state = function(){
return this._cached_scroll_pos || null;
};
gantt.scrollTo = function(left, top){
if (left*1 == left){
this.$task.scrollLeft = left;
this._save_scroll_state(left, undefined);
}
if(top*1 == top){
this.$task_data.scrollTop = top;
this.$grid_data.scrollTop = top;
this._save_scroll_state(undefined, top);
}
};
gantt.showDate = function(date){
var date_x = this.posFromDate(date);
var scroll_to = Math.max(date_x - this.config.task_scroll_offset, 0);
this.scrollTo(scroll_to);
};
gantt.showTask = function(id) {
var el = this.getTaskNode(id);
if(!el)
return;
var left = Math.max(el.offsetLeft - this.config.task_scroll_offset, 0);
var top = el.offsetTop - (this.$task_data.offsetHeight - this.config.row_height)/2;
this.scrollTo(left, top);
};
//called after window resize
gantt._on_resize = gantt.setSizes = function(){
gantt._set_sizes();
gantt._scroll_resize();
};
//renders self
gantt.render = function(){
if(!this._is_render_active())
return;
this.callEvent("onBeforeGanttRender", []);
var pos = dhtmlx.copy(this._restore_scroll_state());
var visible_date = null;
if(pos){
visible_date = gantt.dateFromPos(pos.x + this.config.task_scroll_offset);
}
this._render_grid(); //grid.js
this._render_tasks_scales(); //tasks.js
this._scroll_resize();
this._on_resize();
this._render_data();
if(this.config.preserve_scroll && pos){
var new_pos =gantt._restore_scroll_state();
var new_date = gantt.dateFromPos(new_pos.x);
if(!(+visible_date == +new_date && new_pos.y == pos.y)){
if(visible_date){
this.showDate(visible_date);
}
gantt.scrollTo(undefined, pos.y);
}
}
this.callEvent("onGanttRender", []);
};
gantt._set_scroll_events = function(){
dhtmlxEvent(this.$scroll_hor, "scroll", function() {
//in safari we can catch previous onscroll after setting new value from mouse-wheel event
//set delay to prevent value drifiting
if ((new Date()) - ( gantt._wheel_time || 0 ) < 100) return true;
if (gantt._touch_scroll_active) return;
var left = gantt.$scroll_hor.scrollLeft;
gantt.scrollTo(left);
});
dhtmlxEvent(this.$scroll_ver, "scroll", function() {
if (gantt._touch_scroll_active) return;
var top = gantt.$scroll_ver.scrollTop;
gantt.$grid_data.scrollTop = top;
gantt.scrollTo(null, top);
});
dhtmlxEvent(this.$task, "scroll", function() {
var left = gantt.$task.scrollLeft,
barLeft = gantt.$scroll_hor.scrollLeft;
if(barLeft != left)
gantt.$scroll_hor.scrollLeft = left;
});
dhtmlxEvent(this.$task_data, "scroll", function() {
var top = gantt.$task_data.scrollTop,
barTop = gantt.$scroll_ver.scrollTop;
if(barTop != top)
gantt.$scroll_ver.scrollTop = top;
});
var ff = _isFF && !window._KHTMLrv;
function onMouseWheel(e){
var res = gantt._get_resize_options();
gantt._wheel_time = new Date();
var wx = ff ? (e.deltaX*-20) : e.wheelDeltaX*2;
var wy = ff ? (e.deltaY*-40) : e.wheelDelta;
if (wx && Math.abs(wx) > Math.abs(wy)){
if(res.x) return true;//no horisontal scroll, must not block scrolling
var dir = wx/-40;
var left = gantt.$task.scrollLeft+dir*30;
gantt.scrollTo(left, null);
gantt.$scroll_hor.scrollTop = top;
} else {
if(res.y) return true;//no vertical scroll, must not block scrolling
var dir = wy/-40;
if (typeof wy == "undefined")
dir = e.detail;
var top = gantt.$scroll_ver.scrollTop+dir*30;
if(!gantt.config.prevent_default_scroll && gantt._cached_scroll_pos && gantt._cached_scroll_pos.y == top) return true;
gantt.scrollTo(null, top);
gantt.$scroll_ver.scrollTop = top;
}
if (e.preventDefault)
e.preventDefault();
e.cancelBubble=true;
return false;
}
if (ff)
dhtmlxEvent(gantt.$container, "wheel", onMouseWheel);
else
dhtmlxEvent(gantt.$container, "mousewheel", onMouseWheel);
};
gantt._scroll_resize = function() {
if (this._x < 20 || this._y < 20) return;
var grid_width = this._get_grid_width();
var task_width = Math.max(this._x - grid_width, 0);
var task_height = Math.max(this._y - this.config.scale_height, 0);
var scroll_size = this.config.scroll_size + 1;//1px for inner content
var task_data_width = Math.max(this.$task_data.offsetWidth - scroll_size, 0);
var task_data_height = this.config.row_height*this._order.length;
var resize = this._get_resize_options();
var scroll_hor = this._scroll_hor = resize.x ? false : (task_data_width > task_width);
var scroll_ver = this._scroll_ver = resize.y ? false : (task_data_height > task_height);
this.$scroll_hor.style.display = scroll_hor ? "block" : "none";
this.$scroll_hor.style.height = (scroll_hor ? scroll_size : 0) + "px";
this.$scroll_hor.style.width = Math.max((this._x - (scroll_ver ? scroll_size : 2)), 0) + "px";
this.$scroll_hor.firstChild.style.width = (task_data_width + grid_width + scroll_size + 2) + "px";
this.$scroll_ver.style.display = scroll_ver ? "block" : "none";
this.$scroll_ver.style.width = (scroll_ver ? scroll_size : 0) + "px";
this.$scroll_ver.style.height = Math.max((this._y - (scroll_hor ? scroll_size : 0) - this.config.scale_height), 0) + "px";
this.$scroll_ver.style.top = this.config.scale_height + "px";
this.$scroll_ver.firstChild.style.height = (this.config.scale_height + task_data_height) + "px";
};
gantt.locate = function(e) {
var trg = gantt._get_target_node(e);
//ignore empty cells
if ((trg.className || "").indexOf("gantt_task_cell") >= 0) return null;
var attribute = arguments[1] || this.config.task_attribute;
while (trg){
if (trg.getAttribute){ //text nodes has not getAttribute
var test = trg.getAttribute(attribute);
if (test) return test;
}
trg=trg.parentNode;
}
return null;
};
gantt._get_target_node = function(e){
var trg;
if (e.tagName)
trg = e;
else {
e=e||window.event;
trg=e.target||e.srcElement;
}
return trg;
};
gantt._trim = function(str){
var func = String.prototype.trim || function(){ return this.replace(/^\s+|\s+$/g, ""); };
return func.apply(str);
};
gantt._locate_css = function(e, classname, strict){
if(strict === undefined)
strict = true;
var trg = gantt._get_target_node(e);
var css = '';
var test = false;
while (trg){
css = trg.className;
if(css){
var ind = css.indexOf(classname);
if (ind >= 0){
if (!strict)
return trg;
//check that we have exact match
var left = (ind === 0) || (!gantt._trim(css.charAt(ind - 1)));
var right = ((ind + classname.length >= css.length)) || (!gantt._trim(css.charAt(ind + classname.length)));
if (left && right)
return trg;
}
}
trg=trg.parentNode;
}
return null;
};
gantt._locateHTML = function(e, attribute) {
var trg = gantt._get_target_node(e);
attribute = attribute || this.config.task_attribute;
while (trg){
if (trg.getAttribute){ //text nodes has not getAttribute
var test = trg.getAttribute(attribute);
if (test) return trg;
}
trg=trg.parentNode;
}
return null;
};
gantt.getTaskRowNode = function(id) {
var els = this.$grid_data.childNodes;
var attribute = this.config.task_attribute;
for (var i = 0; i < els.length; i++) {
if (els[i].getAttribute) {
var value = els[i].getAttribute(attribute);
if (value == id) return els[i];
}
}
return null;
};
gantt.getState = function(){
return {
drag_id : this._tasks_dnd.drag.id,
drag_mode : this._tasks_dnd.drag.mode,
drag_from_start : this._tasks_dnd.drag.left,
selected_task : this._selected_task,
min_date : new Date(this._min_date),
max_date : new Date(this._max_date),
lightbox : this._lightbox_id,
touch_drag : this._touch_drag
};
};
gantt._checkTimeout = function(host, updPerSecond){
if(!updPerSecond)
return true;
var timeout = 1000/updPerSecond;
if(timeout < 1) return true;
if(host._on_timeout)
return false;
setTimeout(function(){
delete host._on_timeout;
}, timeout);
host._on_timeout = true;
return true;
};
gantt.selectTask = function(id){
if(!this.config.select_task)
return false;
if (id){
if(this._selected_task == id)
return this._selected_task;
if(!this.callEvent("onBeforeTaskSelected", [id])){
return false;
}
this.unselectTask();
this._selected_task = id;
this.refreshTask(id);
this.callEvent("onTaskSelected", [id]);
}
return this._selected_task;
};
gantt.unselectTask = function(){
var id = this._selected_task;
if(!id)
return;
this._selected_task = null;
this.refreshTask(id);
this.callEvent("onTaskUnselected", [id]);
};
gantt.getSelectedId = function() {
return dhtmlx.defined(this._selected_task) ? this._selected_task : null;
};
gantt.changeLightboxType = function(type){
if(this.getLightboxType() == type)
return true;
gantt._silent_redraw_lightbox(type);
};
gantt._is_render_active = function(){
return !this._skip_render;
};
gantt.batchUpdate = function (callback) {
var call_dp = (this._dp && this._dp.updateMode != "off");
var dp_mode;
if (call_dp){
dp_mode = this._dp.updateMode;
this._dp.setUpdateMode("off");
}
this._skip_render = true;
try{
callback();
}catch(e){
}
this._skip_render = false;
this.render();
if (call_dp) {
this._dp.setUpdateMode(dp_mode);
this._dp.sendData();
}
};
gantt.date={
init:function(){
var s = gantt.locale.date.month_short;
var t = gantt.locale.date.month_short_hash = {};
for (var i = 0; i < s.length; i++)
t[s[i]]=i;
var s = gantt.locale.date.month_full;
var t = gantt.locale.date.month_full_hash = {};
for (var i = 0; i < s.length; i++)
t[s[i]]=i;
},
date_part:function(date){
var old = new Date(date);
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
if (date.getHours() && //shift to yesterday on dst
(date.getDate() < old.getDate() || date.getMonth() < old.getMonth() || date.getFullYear() < old.getFullYear()) )
date.setTime(date.getTime() + 60 * 60 * 1000 * (24 - date.getHours()));
return date;
},
time_part:function(date){
return (date.valueOf()/1000 - date.getTimezoneOffset()*60)%86400;
},
week_start:function(date){
var shift=date.getDay();
if (gantt.config.start_on_monday){
if (shift===0) shift=6;
else shift--;
}
return this.date_part(this.add(date,-1*shift,"day"));
},
month_start:function(date){
date.setDate(1);
return this.date_part(date);
},
year_start:function(date){
date.setMonth(0);
return this.month_start(date);
},
day_start:function(date){
return this.date_part(date);
},
hour_start:function(date){
var hour = date.getHours();
this.day_start(date);
date.setHours(hour);
return date;
},
minute_start:function(date){
var min = date.getMinutes();
this.hour_start(date);
date.setMinutes(min);
return date;
},
_add_days:function(date, inc){
var ndate = new Date(date.valueOf());
ndate.setDate(ndate.getDate() + inc);
if (inc >= 0 && (!date.getHours() && ndate.getHours()) &&//shift to yesterday on dst
(ndate.getDate() < date.getDate() || ndate.getMonth() < date.getMonth() || ndate.getFullYear() < date.getFullYear()) )
ndate.setTime(ndate.getTime() + 60 * 60 * 1000 * (24 - ndate.getHours()));
return ndate;
},
add:function(date,inc,mode){
/*jsl:ignore*/
var ndate=new Date(date.valueOf());
switch(mode){
case "day":
ndate = gantt.date._add_days(ndate, inc);
break;
case "week":
ndate = gantt.date._add_days(ndate, inc * 7);
break;
case "month": ndate.setMonth(ndate.getMonth()+inc); break;
case "year": ndate.setYear(ndate.getFullYear()+inc); break;
case "hour":
/*
adding hours/minutes via setHour(getHour() + inc) gives weird result when
adding one hour to the time before switch to a Daylight Saving time
example: //Sun Mar 30 2014 01:00:00 GMT+0100 (W. Europe Standard Time)
new Date(2014, 02, 30, 1).setHours(2)
>>Sun Mar 30 2014 01:00:00 GMT+0100 (W. Europe Standard Time)
setTime seems working as expected
*/
ndate.setTime(ndate.getTime()+inc * 60 * 60 * 1000);
break;
case "minute":
ndate.setTime(ndate.getTime() + inc * 60 * 1000);
break;
default:
return gantt.date["add_"+mode](date,inc,mode);
}
return ndate;
/*jsl:end*/
},
to_fixed:function(num){
if (num<10) return "0"+num;
return num;
},
copy:function(date){
return new Date(date.valueOf());
},
date_to_str:function(format,utc){
format=format.replace(/%[a-zA-Z]/g,function(a){
switch(a){
case "%d": return "\"+gantt.date.to_fixed(date.getDate())+\"";
case "%m": return "\"+gantt.date.to_fixed((date.getMonth()+1))+\"";
case "%j": return "\"+date.getDate()+\"";
case "%n": return "\"+(date.getMonth()+1)+\"";
case "%y": return "\"+gantt.date.to_fixed(date.getFullYear()%100)+\"";
case "%Y": return "\"+date.getFullYear()+\"";
case "%D": return "\"+gantt.locale.date.day_short[date.getDay()]+\"";
case "%l": return "\"+gantt.locale.date.day_full[date.getDay()]+\"";
case "%M": return "\"+gantt.locale.date.month_short[date.getMonth()]+\"";
case "%F": return "\"+gantt.locale.date.month_full[date.getMonth()]+\"";
case "%h": return "\"+gantt.date.to_fixed((date.getHours()+11)%12+1)+\"";
case "%g": return "\"+((date.getHours()+11)%12+1)+\"";
case "%G": return "\"+date.getHours()+\"";
case "%H": return "\"+gantt.date.to_fixed(date.getHours())+\"";
case "%i": return "\"+gantt.date.to_fixed(date.getMinutes())+\"";
case "%a": return "\"+(date.getHours()>11?\"pm\":\"am\")+\"";
case "%A": return "\"+(date.getHours()>11?\"PM\":\"AM\")+\"";
case "%s": return "\"+gantt.date.to_fixed(date.getSeconds())+\"";
case "%W": return "\"+gantt.date.to_fixed(gantt.date.getISOWeek(date))+\"";
default: return a;
}
});
if (utc) format=format.replace(/date\.get/g,"date.getUTC");
return new Function("date","return \""+format+"\";");
},
str_to_date:function(format,utc){
var splt="var temp=date.match(/[a-zA-Z]+|[0-9]+/g);";
var mask=format.match(/%[a-zA-Z]/g);
for (var i=0; i<mask.length; i++){
switch(mask[i]){
case "%j":
case "%d": splt+="set[2]=temp["+i+"]||1;";
break;
case "%n":
case "%m": splt+="set[1]=(temp["+i+"]||1)-1;";
break;
case "%y": splt+="set[0]=temp["+i+"]*1+(temp["+i+"]>50?1900:2000);";
break;
case "%g":
case "%G":
case "%h":
case "%H":
splt+="set[3]=temp["+i+"]||0;";
break;
case "%i":
splt+="set[4]=temp["+i+"]||0;";
break;
case "%Y": splt+="set[0]=temp["+i+"]||0;";
break;
case "%a":
case "%A": splt+="set[3]=set[3]%12+((temp["+i+"]||'').toLowerCase()=='am'?0:12);";
break;
case "%s": splt+="set[5]=temp["+i+"]||0;";
break;
case "%M": splt+="set[1]=gantt.locale.date.month_short_hash[temp["+i+"]]||0;";
break;
case "%F": splt+="set[1]=gantt.locale.date.month_full_hash[temp["+i+"]]||0;";
break;
default:
break;
}
}
var code ="set[0],set[1],set[2],set[3],set[4],set[5]";
if (utc) code =" Date.UTC("+code+")";
return new Function("date","var set=[0,0,1,0,0,0]; "+splt+" return new Date("+code+");");
},
getISOWeek: function(ndate) {
if(!ndate) return false;
var nday = ndate.getDay();
if (nday === 0) {
nday = 7;
}
var first_thursday = new Date(ndate.valueOf());
first_thursday.setDate(ndate.getDate() + (4 - nday));
var year_number = first_thursday.getFullYear(); // year of the first Thursday
var ordinal_date = Math.round( (first_thursday.getTime() - new Date(year_number, 0, 1).getTime()) / 86400000); //ordinal date of the first Thursday - 1 (so not really ordinal date)
var week_number = 1 + Math.floor( ordinal_date / 7);
return week_number;
},
getUTCISOWeek: function(ndate){
return this.getISOWeek(ndate);
},
convert_to_utc: function(date) {
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
},
parseDate: function(date, format) {
if (typeof(date) == "string") {
if (dhtmlx.defined(format)){
if (typeof(format) == "string")
format = dhtmlx.defined(gantt.templates[format]) ? gantt.templates[format] : gantt.date.str_to_date(format);
else
format = gantt.templates.xml_date;
}
if(date)
date = format(date);
else
date = null;
}
return date;
}
};
gantt.date.quarter_start = function(date){
gantt.date.month_start(date);
var m = date.getMonth(),
res_month;
if(m >= 9){
res_month = 9;
}else if(m >= 6){
res_month = 6;
}else if(m >= 3){
res_month = 3;
}else{
res_month = 0;
}
date.setMonth(res_month);
return date;
};
gantt.date.add_quarter = function(date, inc){
return gantt.date.add(date, inc*3, "month");
};
/*
%d - the day as a number with a leading zero ( 01 to 31 );
%j - the day as a number without a leading zero ( 1 to 31 );
%D - the day as an abbreviation ( Sun to Sat );
%l - the day as a full name ( Sunday to Saturday );
%W - the ISO-8601 week number of the year. Weeks start on Monday; 1)
%m - the month as a number without a leading zero ( 1 to 12 );
%n - the month as a number with a leading zero ( 01 to 12);
%M - the month as an abbreviation ( Jan to Dec );
%F - the month as a full name ( January to December );
%y - the year as a two-digit number ( 00 to 99 );
%Y - the year as a four-digit number ( 19009999 );
%h - the hour based on the 12-hour clock ( 00 to 11 );
%H - the hour based on the 24-hour clock ( 00 to 23 );
%i - the minute as a number with a leading zero ( 00 to 59 );
%s - the second as a number without a leading zero ( 00 to 59 ); 2)
%a - displays am (for times from midnight until noon) and pm (for times from noon until midnight);
%A - displays AM (for times from midnight until noon) and PM (for times from noon until midnight).
*/
if(!gantt.config) gantt.config = {};
if(!gantt.config) gantt.config = {};
if(!gantt.templates) gantt.templates = {};
(function(){
dhtmlx.mixin(gantt.config,
{links : {
"finish_to_start":"0",
"start_to_start":"1",
"finish_to_finish":"2",
"start_to_finish":"3"
},
types : {
'task':'task',
'project':'project',
'milestone':'milestone'
},
duration_unit : "day",
work_time:false,
correct_work_time:false,
skip_off_time:false,
autosize:false,
autosize_min_width: 0,
show_links : true,
show_task_cells : true,
// replace backgroung of the task area with a canvas img
static_background: false,
branch_loading: false,
show_loading: false,
show_chart : true,
show_grid : true,
min_duration : 60*60*1000,
xml_date : "%d-%m-%Y %H:%i",
api_date : "%d-%m-%Y %H:%i",
start_on_monday: true,
server_utc : false,
show_progress:true,
fit_tasks : false,
select_task:true,
scroll_on_click: true,
preserve_scroll: true,
readonly:false,
/*grid */
date_grid: "%Y-%m-%d",
drag_links : true,
drag_progress:true,
drag_resize:true,
drag_move:true,
drag_mode:{
"resize":"resize",
"progress":"progress",
"move":"move",
"ignore":"ignore"
},
round_dnd_dates:true,
link_wrapper_width:20,
root_id:0,
autofit: false, // grid column automatic fit grid_width config
columns: [
{name:"text", tree:true, width:'*', resize:true },
{name:"start_date", align: "center", resize:true },
{name:"duration", align: "center" },
{name:"add", width:'44' }
],
/*scale*/
step: 1,
scale_unit: "day",
scale_offset_minimal:true,
subscales : [
],
inherit_scale_class:false,
time_step: 60,
duration_step: 1,
date_scale: "%d %M",
task_date: "%d %F %Y",
time_picker: "%H:%i",
task_attribute: "task_id",
link_attribute: "link_id",
layer_attribute: "data-layer",
buttons_left: [
"gantt_save_btn",
"gantt_cancel_btn"
],
_migrate_buttons: {
"dhx_save_btn":"gantt_save_btn",
"dhx_cancel_btn":"gantt_cancel_btn",
"dhx_delete_btn":"gantt_delete_btn"
},
buttons_right: [
"gantt_delete_btn"
],
lightbox: {
sections: [
{name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
{name: "time", type: "duration", map_to: "auto"}
],
project_sections: [
{name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
{name: "type", type: "typeselect", map_to: "type"},
{name: "time", type: "duration", readonly:true, map_to: "auto"}
],
milestone_sections: [
{name: "description", height: 70, map_to: "text", type: "textarea", focus: true},
{name: "type", type: "typeselect", map_to: "type"},
{name: "time", type: "duration", single_date:true, map_to: "auto"}
]
},
drag_lightbox: true,
sort: false,
details_on_create: true,
details_on_dblclick:true,
initial_scroll : true,
task_scroll_offset : 100,
task_height: "full",//number px of 'full' for row height
min_column_width:70,
// min width for grid column (when resizing)
min_grid_column_width:70,
// name of the attribute with column index for resize element
grid_resizer_column_attribute: "column_index",
// name of the attribute with column index for resize element
grid_resizer_attribute: "grid_resizer",
// grid width can be increased after the column has been resized
keep_grid_width:false,
// grid width can be adjusted
grid_resize:false,
//
readonly_property: "readonly",
editable_property: "editable",
type_renderers:{},
open_tree_initially: false,
optimize_render: 'auto',
prevent_default_scroll: false
});
gantt.keys={
edit_save:13,
edit_cancel:27
};
gantt._init_template = function(name, initial){
var registeredTemplates = this._reg_templates || {};
if(this.config[name] && registeredTemplates[name] != this.config[name]){
if(!(initial && this.templates[name])){
this.templates[name] = this.date.date_to_str(this.config[name]);
registeredTemplates[name] = this.config[name];
}
}
this._reg_templates = registeredTemplates;
};
gantt._init_templates = function(){
var labels = gantt.locale.labels;
labels.gantt_save_btn = labels.icon_save;
labels.gantt_cancel_btn = labels.icon_cancel;
labels.gantt_delete_btn = labels.icon_delete;
//build configuration based templates
var d = this.date.date_to_str;
var c = this.config;
gantt._init_template("date_scale", true);
gantt._init_template("date_grid", true);
gantt._init_template("task_date", true);
dhtmlx.mixin(this.templates,{
xml_date:this.date.str_to_date(c.xml_date,c.server_utc),
xml_format:d(c.xml_date,c.server_utc),
api_date:this.date.str_to_date(c.api_date),
progress_text:function(start, end, task){return "";},
grid_header_class : function(column, config){
return "";
},
task_text:function(start, end, task){
return task.text;
},
task_class:function(start, end, task){return "";},
grid_row_class:function(start, end, task){
return "";
},
task_row_class:function(start, end, task){
return "";
},
task_cell_class:function(item, date){return "";},
scale_cell_class:function(date){return "";},
scale_row_class:function(date){return "";},
grid_indent:function(item) {
return "<div class='gantt_tree_indent'></div>";
},
grid_folder:function(item) {
return "<div class='gantt_tree_icon gantt_folder_" + (item.$open ? "open" : "closed") + "'></div>";
},
grid_file:function(item) {
return "<div class='gantt_tree_icon gantt_file'></div>";
},
grid_open:function(item) {
return "<div class='gantt_tree_icon gantt_" + (item.$open ? "close" : "open") + "'></div>";
},
grid_blank:function(item) {
return "<div class='gantt_tree_icon gantt_blank'></div>";
},
task_time:function(start,end,ev){
return gantt.templates.task_date(start)+" - "+gantt.templates.task_date(end);
},
time_picker:d(c.time_picker),
link_class : function(link){
return "";
},
link_description : function(link){
var from = gantt.getTask(link.source),
to = gantt.getTask(link.target);
return "<b>" + from.text + "</b> &ndash; <b>" + to.text+"</b>";
},
drag_link : function(from, from_start, to, to_start) {
from = gantt.getTask(from);
var labels = gantt.locale.labels;
var text = "<b>" + from.text + "</b> " + (from_start ? labels.link_start : labels.link_end)+"<br/>";
if(to){
to = gantt.getTask(to);
text += "<b> " + to.text + "</b> "+ (to_start ? labels.link_start : labels.link_end)+"<br/>";
}
return text;
},
drag_link_class: function(from, from_start, to, to_start) {
var add = "";
if(from && to){
var allowed = gantt.isLinkAllowed(from, to, from_start, to_start);
add = " " + (allowed ? "gantt_link_allow" : "gantt_link_deny");
}
return "gantt_link_tooltip" + add;
}
});
this.callEvent("onTemplatesReady",[]);
};
})();
if (window.jQuery){
(function( $ ){
var methods = [];
$.fn.dhx_gantt = function(config){
config = config || {};
if (typeof(config) === 'string') {
if (methods[config] ) {
return methods[config].apply(this, []);
}else {
$.error('Method ' + config + ' does not exist on jQuery.dhx_gantt');
}
} else {
var views = [];
this.each(function() {
if (this && this.getAttribute){
if (!this.getAttribute("dhxgantt")){
for (var key in config)
if (key!="data")
gantt.config[key] = config[key];
gantt.init(this);
if (config.data)
gantt.parse(config.data);
views.push(gantt);
}
}
});
if (views.length === 1) return views[0];
return views;
}
};
})(jQuery);
}
if (window.dhtmlx){
if (!dhtmlx.attaches)
dhtmlx.attaches = {};
dhtmlx.attaches.attachGantt=function(start, end){
var obj = document.createElement("DIV");
obj.id = "gantt_"+dhtmlx.uid();
obj.style.width = "100%";
obj.style.height = "100%";
obj.cmp = "grid";
document.body.appendChild(obj);
this.attachObject(obj.id);
var that = this.vs[this.av];
that.grid = gantt;
gantt.init(obj.id, start, end);
obj.firstChild.style.border = "none";
that.gridId = obj.id;
that.gridObj = obj;
var method_name="_viewRestore";
return this.vs[this[method_name]()].grid;
};
}
gantt.locale = {
date:{
month_full:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
month_short:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
day_full:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
day_short:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
},
labels:{
new_task:"New task",
icon_save:"Save",
icon_cancel:"Cancel",
icon_details:"Details",
icon_edit:"Edit",
icon_delete:"Delete",
confirm_closing:"",//Your changes will be lost, are your sure ?
confirm_deleting:"Task will be deleted permanently, are you sure?",
section_description:"Description",
section_time:"Time period",
section_type:"Type",
/* grid columns */
column_text : "Task name",
column_start_date : "Start time",
column_duration : "Duration",
column_add : "",
/* link confirmation */
link: "Link",
confirm_link_deleting:"will be deleted",
link_start: " (start)",
link_end: " (end)",
type_task: "Task",
type_project: "Project",
type_milestone: "Milestone",
minutes: "Minutes",
hours: "Hours",
days: "Days",
weeks: "Week",
months: "Months",
years: "Years"
}
};
gantt.skins.skyblue = {
config:{
grid_width:350,
row_height: 27,
scale_height: 27,
link_line_width:1,
link_arrow_size:8,
lightbox_additional_height:75
},
_second_column_width:95,
_third_column_width:80
};
gantt.skins.meadow = {
config:{
grid_width:350,
row_height: 27,
scale_height: 30,
link_line_width:2,
link_arrow_size:6,
lightbox_additional_height:72
},
_second_column_width:95,
_third_column_width:80
};
gantt.skins.terrace = {
config:{
grid_width:360,
row_height: 35,
scale_height: 35,
link_line_width:2,
link_arrow_size:6,
lightbox_additional_height:75
},
_second_column_width:90,
_third_column_width:70
};
gantt.skins.broadway = {
config:{
grid_width:360,
row_height: 35,
scale_height: 35,
link_line_width:1,
link_arrow_size:7,
lightbox_additional_height:86
},
_second_column_width:90,
_third_column_width:80,
_lightbox_template:"<div class='gantt_cal_ltitle'><span class='gantt_mark'>&nbsp;</span><span class='gantt_time'></span><span class='gantt_title'></span><div class='gantt_cancel_btn'></div></div><div class='gantt_cal_larea'></div>",
_config_buttons_left: {},
_config_buttons_right: {
"gantt_delete_btn": "icon_delete",
"gantt_save_btn": "icon_save"
}
};
gantt.config.touch_drag = 500; //nearly immediate dnd
gantt.config.touch = true;
gantt.config.touch_feedback = true;
gantt._touch_feedback = function(){
if(gantt.config.touch_feedback){
if(navigator.vibrate)
navigator.vibrate(1);
}
};
gantt._init_touch_events = function(){
if (this.config.touch != "force")
this.config.touch = this.config.touch &&
((navigator.userAgent.indexOf("Mobile")!=-1) ||
(navigator.userAgent.indexOf("iPad")!=-1) ||
(navigator.userAgent.indexOf("Android")!=-1) ||
(navigator.userAgent.indexOf("Touch")!=-1));
if (this.config.touch){
if (window.navigator.msPointerEnabled){
this._touch_events(["MSPointerMove", "MSPointerDown", "MSPointerUp"], function(ev){
if (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE ) return null;
return ev;
}, function(ev){
return (!ev || ev.pointerType == ev.MSPOINTER_TYPE_MOUSE);
});
} else
this._touch_events(["touchmove", "touchstart", "touchend"], function(ev){
if (ev.touches && ev.touches.length > 1) return null;
if (ev.touches[0])
return {
target: ev.target,
pageX: ev.touches[0].pageX,
pageY: ev.touches[0].pageY,
clientX:ev.touches[0].clientX,
clientY:ev.touches[0].clientY
};
else
return ev;
}, function(){ return false; });
}
};
//we can't use native scrolling, as we need to sync momentum between different parts
//so we will block native scroll and use the custom one
//in future we can add custom momentum
gantt._touch_events = function(names, accessor, ignore){
//webkit on android need to be handled separately
var dblclicktime = 0;
var action_mode = false;
var scroll_mode = false;
var dblclick_timer = 0;
var action_start = null;
var scroll_state;
var long_tap_timer = null;
var current_target = null;
//touch move
if (!this._gantt_touch_event_ready){
this._gantt_touch_event_ready = 1;
dhtmlxEvent(gantt.$container, names[0], function(e){
if (ignore(e)) return;
//ignore common and scrolling moves
if (!action_mode) return;
if (long_tap_timer) clearTimeout(long_tap_timer);
var source = accessor(e);
if (gantt._tasks_dnd.drag.id || gantt._tasks_dnd.drag.start_drag) {
gantt._tasks_dnd.on_mouse_move(source);
if (e.preventDefault)
e.preventDefault();
e.cancelBubble = true;
return false;
}
if (source && action_start){
var dx = action_start.pageX - source.pageX;
var dy = action_start.pageY - source.pageY;
if (!scroll_mode && (Math.abs(dx) > 5 || Math.abs(dy) > 5)){
gantt._touch_scroll_active = scroll_mode = true;
dblclicktime = 0;
scroll_state = gantt.getScrollState();
}
if (scroll_mode){
gantt.scrollTo(scroll_state.x + dx, scroll_state.y + dy);
}
}
return block_action(e);
});
}
//block touch context menu in IE10
dhtmlxEvent(this.$container, "contextmenu", function(e){
if (action_mode)
return block_action(e);
});
//touch start
dhtmlxEvent(this.$container, names[1], function(e){
if (ignore(e)) return;
if (e.touches && e.touches.length > 1){
action_mode = false;
return;
}
action_mode = true;
action_start = accessor(e);
//dbl-tap handling
if (action_start && dblclicktime){
var now = new Date();
if ((now - dblclicktime) < 500 ){
gantt._on_dblclick(action_start);
block_action(e);
} else
dblclicktime = now;
} else {
dblclicktime = new Date();
}
//long tap
long_tap_timer = setTimeout(function(){
var taskId = gantt.locate(action_start);
if(taskId && action_start.target.className.indexOf("gantt_link_point") == -1) {
gantt._tasks_dnd.on_mouse_down(action_start);
gantt._tasks_dnd._start_dnd(action_start);
gantt._touch_drag = true;
cloneTaskRendered(taskId);
gantt.refreshTask(taskId);
gantt._touch_feedback();
}
long_tap_timer = null;
}, gantt.config.touch_drag);
});
//touch end
dhtmlxEvent(this.$container, names[2], function(e){
if (ignore(e)) return;
if (long_tap_timer) clearTimeout(long_tap_timer);
gantt._touch_drag = false;
action_mode = false;
var source = accessor(e);
gantt._tasks_dnd.on_mouse_up(source);
if(current_target) {
gantt.refreshTask(gantt.locate(current_target));
current_target.parentNode.removeChild(current_target);
gantt._touch_feedback();
}
gantt._touch_scroll_active = action_mode = scroll_mode = false;
current_target = null;
});
//common helper, prevents event
function block_action(e){
if (e && e.preventDefault)
e.preventDefault();
(e||event).cancelBubble = true;
return false;
}
function cloneTaskRendered(taskId) {
var renders = gantt._task_area_pulls;
var task = gantt.getTask(taskId);
if(task && gantt.isTaskVisible(taskId)){
for(var i in renders) {
task = renders[i][taskId];
if(task && task.getAttribute("task_id") && task.getAttribute("task_id") == taskId) {
var copy = task.cloneNode(true);
current_target = task;
renders[i][taskId] = copy;
task.style.display="none";
copy.className += " gantt_drag_move ";
task.parentNode.appendChild(copy);
return copy;
}
}
}
}
};