/* dhtmlxGantt v.2.1.1 Standard 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 (!window.dhtmlx) { dhtmlx = function(obj){ for (var a in obj) dhtmlx[a]=obj[a]; return dhtmlx; //simple singleton }; } dhtmlx.extend_api=function(name,map,ext){ var t = window[name]; if (!t) return; //component not defined window[name]=function(obj){ var that; if (obj && typeof obj == "object" && !obj.tagName){ 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 that = t.apply(this,arguments); if (map._patch) map._patch(this); return that||this; }; window[name].prototype=t.prototype; if (ext) dhtmlXHeir(window[name].prototype,ext); }; dhtmlxAjax={ get:function(url,callback){ var t=new dtmlXMLLoaderObject(true); t.async=(arguments.length<3); t.waitCall=callback; t.loadXML(url); return t; }, post:function(url,post,callback){ var t=new dtmlXMLLoaderObject(true); t.async=(arguments.length<4); t.waitCall=callback; t.loadXML(url,true,post); return t; }, getSync:function(url){ return this.get(url,null,true); }, postSync:function(url,post){ return this.post(url,post,null,true); } }; /** * @desc: xmlLoader object * @type: private * @param: funcObject - xml parser function * @param: object - jsControl object * @param: async - sync/async mode (async by default) * @param: rSeed - enable/disable random seed ( prevent IE caching) * @topic: 0 */ function dtmlXMLLoaderObject(funcObject, dhtmlObject, async, rSeed){ this.xmlDoc=""; if (typeof (async) != "undefined") this.async=async; else this.async=true; this.onloadAction=funcObject||null; this.mainObject=dhtmlObject||null; this.waitCall=null; this.rSeed=rSeed||false; return this; } dtmlXMLLoaderObject.count = 0; /** * @desc: xml loading handler * @type: private * @param: dtmlObject - xmlLoader object * @topic: 0 */ dtmlXMLLoaderObject.prototype.waitLoadFunction=function(dhtmlObject){ var once = true; this.check=function (){ if ((dhtmlObject)&&(dhtmlObject.onloadAction)){ if ((!dhtmlObject.xmlDoc.readyState)||(dhtmlObject.xmlDoc.readyState == 4)){ if (!once) return; once=false; //IE 5 fix dtmlXMLLoaderObject.count++; if (typeof dhtmlObject.onloadAction == "function") dhtmlObject.onloadAction(dhtmlObject.mainObject, null, null, null, dhtmlObject); if (dhtmlObject.waitCall){ dhtmlObject.waitCall.call(this,dhtmlObject); dhtmlObject.waitCall=null; } } } }; return this.check; }; /** * @desc: return XML top node * @param: tagName - top XML node tag name (not used in IE, required for Safari and Mozilla) * @type: private * @returns: top XML node * @topic: 0 */ dtmlXMLLoaderObject.prototype.getXMLTopNode=function(tagName, oldObj){ var z; if (this.xmlDoc.responseXML){ var temp = this.xmlDoc.responseXML.getElementsByTagName(tagName); if(temp.length === 0 && tagName.indexOf(":")!=-1) var temp = this.xmlDoc.responseXML.getElementsByTagName((tagName.split(":"))[1]); z = temp[0]; } else z = this.xmlDoc.documentElement; if (z){ this._retry=false; return z; } if (!this._retry&&_isIE){ this._retry=true; var oldObj = this.xmlDoc; this.loadXMLString(this.xmlDoc.responseText.replace(/^[\s]+/,""), true); return this.getXMLTopNode(tagName, oldObj); } dhtmlxError.throwError("LoadXML", "Incorrect XML", [ (oldObj||this.xmlDoc), this.mainObject ]); return document.createElement("DIV"); }; /** * @desc: load XML from string * @type: private * @param: xmlString - xml string * @topic: 0 */ dtmlXMLLoaderObject.prototype.loadXMLString=function(xmlString, silent){ if (!_isIE){ var parser = new DOMParser(); this.xmlDoc=parser.parseFromString(xmlString, "text/xml"); } else { this.xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); this.xmlDoc.async=this.async; this.xmlDoc.onreadystatechange = function(){}; this.xmlDoc["loadXM"+"L"](xmlString); } if (silent) return; if (this.onloadAction) this.onloadAction(this.mainObject, null, null, null, this); if (this.waitCall){ this.waitCall(); this.waitCall=null; } }; /** * @desc: load XML * @type: private * @param: filePath - xml file path * @param: postMode - send POST request * @param: postVars - list of vars for post request * @topic: 0 */ dtmlXMLLoaderObject.prototype.loadXML=function(filePath, postMode, postVars, rpc){ if (this.rSeed) filePath+=((filePath.indexOf("?") != -1) ? "&" : "?")+"a_dhx_rSeed="+(new Date()).valueOf(); this.filePath=filePath; if ((!_isIE)&&(window.XMLHttpRequest)) this.xmlDoc=new XMLHttpRequest(); else { this.xmlDoc=new ActiveXObject("Microsoft.XMLHTTP"); } if (this.async) this.xmlDoc.onreadystatechange=new this.waitLoadFunction(this); this.xmlDoc.open(postMode ? "POST" : "GET", filePath, this.async); if (rpc){ this.xmlDoc.setRequestHeader("User-Agent", "dhtmlxRPC v0.1 ("+navigator.userAgent+")"); this.xmlDoc.setRequestHeader("Content-type", "text/xml"); } else if (postMode) this.xmlDoc.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); this.xmlDoc.setRequestHeader("X-Requested-With","XMLHttpRequest"); this.xmlDoc.send(null||postVars); if (!this.async) (new this.waitLoadFunction(this))(); }; /** * @desc: destructor, cleans used memory * @type: private * @topic: 0 */ dtmlXMLLoaderObject.prototype.destructor=function(){ this._filterXPath = null; this._getAllNamedChilds = null; this._retry = null; this.async = null; this.rSeed = null; this.filePath = null; this.onloadAction = null; this.mainObject = null; this.xmlDoc = null; this.doXPath = null; this.doXPathOpera = null; this.doXSLTransToObject = null; this.doXSLTransToString = null; this.loadXML = null; this.loadXMLString = null; // this.waitLoadFunction = null; this.doSerialization = null; this.xmlNodeToJSON = null; this.getXMLTopNode = null; this.setXSLParamValue = null; return null; }; dtmlXMLLoaderObject.prototype.xmlNodeToJSON = function(node){ var t={}; for (var i=0; i<node.attributes.length; i++) t[node.attributes[i].name]=node.attributes[i].value; t["_tagvalue"]=node.firstChild?node.firstChild.nodeValue:""; for (var i=0; i<node.childNodes.length; i++){ var name=node.childNodes[i].tagName; if (name){ if (!t[name]) t[name]=[]; t[name].push(this.xmlNodeToJSON(node.childNodes[i])); } } return t; }; /** * @desc: Call wrapper * @type: private * @param: funcObject - action handler * @param: dhtmlObject - user data * @returns: function handler * @topic: 0 */ function callerFunction(funcObject, dhtmlObject){ this.handler=function(e){ if (!e) e=window.event; funcObject(e, dhtmlObject); return true; }; return this.handler; } /** * @desc: Calculate absolute position of html object * @type: private * @param: htmlObject - html object * @topic: 0 */ function getAbsoluteLeft(htmlObject){ return getOffset(htmlObject).left; } /** * @desc: Calculate absolute position of html object * @type: private * @param: htmlObject - html object * @topic: 0 */ function getAbsoluteTop(htmlObject){ return getOffset(htmlObject).top; } function getOffsetSum(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}; } function getOffsetRect(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) }; } function getOffset(elem) { if (elem.getBoundingClientRect) { return getOffsetRect(elem); } else { return getOffsetSum(elem); } } /** * @desc: Convert string to it boolean representation * @type: private * @param: inputString - string for covertion * @topic: 0 */ function convertStringToBoolean(inputString){ if (typeof (inputString) == "string") inputString=inputString.toLowerCase(); switch (inputString){ case "1": case "true": case "yes": case "y": case 1: case true: return true; default: return false; } } /** * @desc: find out what symbol to use as url param delimiters in further params * @type: private * @param: str - current url string * @topic: 0 */ function getUrlSymbol(str){ if (str.indexOf("?") != -1) return "&"; else return "?"; } function dhtmlDragAndDropObject(){ if (window.dhtmlDragAndDrop) return window.dhtmlDragAndDrop; this.lastLanding=0; this.dragNode=0; this.dragStartNode=0; this.dragStartObject=0; this.tempDOMU=null; this.tempDOMM=null; this.waitDrag=0; window.dhtmlDragAndDrop=this; return this; } dhtmlDragAndDropObject.prototype.removeDraggableItem=function(htmlNode){ htmlNode.onmousedown=null; htmlNode.dragStarter=null; htmlNode.dragLanding=null; }; dhtmlDragAndDropObject.prototype.addDraggableItem=function(htmlNode, dhtmlObject){ htmlNode.onmousedown=this.preCreateDragCopy; htmlNode.dragStarter=dhtmlObject; this.addDragLanding(htmlNode, dhtmlObject); }; dhtmlDragAndDropObject.prototype.addDragLanding=function(htmlNode, dhtmlObject){ htmlNode.dragLanding=dhtmlObject; }; dhtmlDragAndDropObject.prototype.preCreateDragCopy=function(e){ if ((e||window.event) && (e||event).button == 2) return; if (window.dhtmlDragAndDrop.waitDrag){ window.dhtmlDragAndDrop.waitDrag=0; document.body.onmouseup=window.dhtmlDragAndDrop.tempDOMU; document.body.onmousemove=window.dhtmlDragAndDrop.tempDOMM; return false; } if (window.dhtmlDragAndDrop.dragNode) window.dhtmlDragAndDrop.stopDrag(e); window.dhtmlDragAndDrop.waitDrag=1; window.dhtmlDragAndDrop.tempDOMU=document.body.onmouseup; window.dhtmlDragAndDrop.tempDOMM=document.body.onmousemove; window.dhtmlDragAndDrop.dragStartNode=this; window.dhtmlDragAndDrop.dragStartObject=this.dragStarter; document.body.onmouseup=window.dhtmlDragAndDrop.preCreateDragCopy; document.body.onmousemove=window.dhtmlDragAndDrop.callDrag; window.dhtmlDragAndDrop.downtime = new Date().valueOf(); if ((e)&&(e.preventDefault)){ e.preventDefault(); return false; } return false; }; dhtmlDragAndDropObject.prototype.callDrag=function(e){ if (!e) e=window.event; var dragger=window.dhtmlDragAndDrop; if ((new Date()).valueOf()-dragger.downtime<100) return; //if ((e.button == 0)&&(_isIE)) // return dragger.stopDrag(); if (!dragger.dragNode){ if (dragger.waitDrag){ dragger.dragNode=dragger.dragStartObject._createDragNode(dragger.dragStartNode, e); if (!dragger.dragNode) return dragger.stopDrag(); dragger.dragNode.onselectstart=function(){return false;}; dragger.gldragNode=dragger.dragNode; document.body.appendChild(dragger.dragNode); document.body.onmouseup=dragger.stopDrag; dragger.waitDrag=0; dragger.dragNode.pWindow=window; dragger.initFrameRoute(); } else return dragger.stopDrag(e, true); } if (dragger.dragNode.parentNode != window.document.body && dragger.gldragNode){ var grd = dragger.gldragNode; if (dragger.gldragNode.old) grd=dragger.gldragNode.old; //if (!document.all) dragger.calculateFramePosition(); grd.parentNode.removeChild(grd); var oldBody = dragger.dragNode.pWindow; if (grd.pWindow && grd.pWindow.dhtmlDragAndDrop.lastLanding) grd.pWindow.dhtmlDragAndDrop.lastLanding.dragLanding._dragOut(grd.pWindow.dhtmlDragAndDrop.lastLanding); // var oldp=dragger.dragNode.parentObject; if (_isIE){ var div = document.createElement("Div"); div.innerHTML=dragger.dragNode.outerHTML; dragger.dragNode=div.childNodes[0]; } else dragger.dragNode=dragger.dragNode.cloneNode(true); dragger.dragNode.pWindow=window; // dragger.dragNode.parentObject=oldp; dragger.gldragNode.old=dragger.dragNode; document.body.appendChild(dragger.dragNode); oldBody.dhtmlDragAndDrop.dragNode=dragger.dragNode; } dragger.dragNode.style.left=e.clientX+15 + (dragger.fx ? dragger.fx*(-1) : 0) + (document.body.scrollLeft||document.documentElement.scrollLeft)+"px"; dragger.dragNode.style.top=e.clientY+3+ (dragger.fy ? dragger.fy*(-1) : 0) + (document.body.scrollTop||document.documentElement.scrollTop)+"px"; var z; if (!e.srcElement) z = e.target; else z=e.srcElement; dragger.checkLanding(z, e); }; dhtmlDragAndDropObject.prototype.calculateFramePosition=function(n){ //this.fx = 0, this.fy = 0; if (window.name){ var el = parent.frames[window.name].frameElement.offsetParent; var fx = 0; var fy = 0; while (el){ fx+=el.offsetLeft; fy+=el.offsetTop; el=el.offsetParent; } if ((parent.dhtmlDragAndDrop)){ var ls = parent.dhtmlDragAndDrop.calculateFramePosition(1); fx+=ls.split('_')[0]*1; fy+=ls.split('_')[1]*1; } if (n) return fx+"_"+fy; else this.fx=fx; this.fy=fy; } return "0_0"; }; dhtmlDragAndDropObject.prototype.checkLanding=function(htmlObject, e){ if ((htmlObject)&&(htmlObject.dragLanding)){ if (this.lastLanding) this.lastLanding.dragLanding._dragOut(this.lastLanding); this.lastLanding=htmlObject; this.lastLanding=this.lastLanding.dragLanding._dragIn(this.lastLanding, this.dragStartNode, e.clientX, e.clientY, e); this.lastLanding_scr=(_isIE ? e.srcElement : e.target); } else { if ((htmlObject)&&(htmlObject.tagName != "BODY")) this.checkLanding(htmlObject.parentNode, e); else { if (this.lastLanding) this.lastLanding.dragLanding._dragOut(this.lastLanding, e.clientX, e.clientY, e); this.lastLanding=0; if (this._onNotFound) this._onNotFound(); } } }; dhtmlDragAndDropObject.prototype.stopDrag=function(e, mode){ var dragger=window.dhtmlDragAndDrop; if (!mode){ dragger.stopFrameRoute(); var temp = dragger.lastLanding; dragger.lastLanding=null; if (temp) temp.dragLanding._drag(dragger.dragStartNode, dragger.dragStartObject, temp, (_isIE ? event.srcElement : e.target)); } dragger.lastLanding=null; if ((dragger.dragNode)&&(dragger.dragNode.parentNode == document.body)) dragger.dragNode.parentNode.removeChild(dragger.dragNode); dragger.dragNode=0; dragger.gldragNode=0; dragger.fx=0; dragger.fy=0; dragger.dragStartNode=0; dragger.dragStartObject=0; document.body.onmouseup=dragger.tempDOMU; document.body.onmousemove=dragger.tempDOMM; dragger.tempDOMU=null; dragger.tempDOMM=null; dragger.waitDrag=0; }; dhtmlDragAndDropObject.prototype.stopFrameRoute=function(win){ if (win) window.dhtmlDragAndDrop.stopDrag(1, 1); for (var i = 0; i < window.frames.length; i++){ try{ if ((window.frames[i] != win)&&(window.frames[i].dhtmlDragAndDrop)) window.frames[i].dhtmlDragAndDrop.stopFrameRoute(window); } catch(e){} } try{ if ((parent.dhtmlDragAndDrop)&&(parent != window)&&(parent != win)) parent.dhtmlDragAndDrop.stopFrameRoute(window); } catch(e){} }; dhtmlDragAndDropObject.prototype.initFrameRoute=function(win, mode){ if (win){ window.dhtmlDragAndDrop.preCreateDragCopy(); window.dhtmlDragAndDrop.dragStartNode=win.dhtmlDragAndDrop.dragStartNode; window.dhtmlDragAndDrop.dragStartObject=win.dhtmlDragAndDrop.dragStartObject; window.dhtmlDragAndDrop.dragNode=win.dhtmlDragAndDrop.dragNode; window.dhtmlDragAndDrop.gldragNode=win.dhtmlDragAndDrop.dragNode; window.document.body.onmouseup=window.dhtmlDragAndDrop.stopDrag; window.waitDrag=0; if (((!_isIE)&&(mode))&&((!_isFF)||(_FFrv < 1.8))) window.dhtmlDragAndDrop.calculateFramePosition(); } try{ if ((parent.dhtmlDragAndDrop)&&(parent != window)&&(parent != win)) parent.dhtmlDragAndDrop.initFrameRoute(window); }catch(e){} for (var i = 0; i < window.frames.length; i++){ try{ if ((window.frames[i] != win)&&(window.frames[i].dhtmlDragAndDrop)) window.frames[i].dhtmlDragAndDrop.initFrameRoute(window, ((!win||mode) ? 1 : 0)); } catch(e){} } }; _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.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]); } //multibrowser Xpath processor dtmlXMLLoaderObject.prototype.doXPath=function(xpathExp, docObj, namespace, result_type){ if (_isKHTML || (!_isIE && !window.XPathResult)) return this.doXPathOpera(xpathExp, docObj); if (_isIE){ //IE if (!docObj) if (!this.xmlDoc.nodeName) docObj=this.xmlDoc.responseXML; else docObj=this.xmlDoc; if (!docObj) dhtmlxError.throwError("LoadXML", "Incorrect XML", [ (docObj||this.xmlDoc), this.mainObject ]); if (namespace) docObj.setProperty("SelectionNamespaces", "xmlns:xsl='"+namespace+"'"); // if (result_type == 'single'){ return docObj.selectSingleNode(xpathExp); } else { return docObj.selectNodes(xpathExp)||new Array(0); } } else { //Mozilla var nodeObj = docObj; if (!docObj){ if (!this.xmlDoc.nodeName){ docObj=this.xmlDoc.responseXML; } else { docObj=this.xmlDoc; } } if (!docObj) dhtmlxError.throwError("LoadXML", "Incorrect XML", [ (docObj||this.xmlDoc), this.mainObject ]); if (docObj.nodeName.indexOf("document") != -1){ nodeObj=docObj; } else { nodeObj=docObj; docObj=docObj.ownerDocument; } var retType = XPathResult.ANY_TYPE; if (result_type == 'single') retType=XPathResult.FIRST_ORDERED_NODE_TYPE; var rowsCol = []; var col = docObj.evaluate(xpathExp, nodeObj, function(pref){ return namespace; }, retType, null); if (retType == XPathResult.FIRST_ORDERED_NODE_TYPE){ return col.singleNodeValue; } var thisColMemb = col.iterateNext(); while (thisColMemb){ rowsCol[rowsCol.length]=thisColMemb; thisColMemb=col.iterateNext(); } return rowsCol; } }; function _dhtmlxError(type, name, params){ if (!this.catches) this.catches=[]; return this; } _dhtmlxError.prototype.catchError=function(type, func_name){ this.catches[type]=func_name; }; _dhtmlxError.prototype.throwError=function(type, name, params){ if (this.catches[type]) return this.catches[type](type, name, params); if (this.catches["ALL"]) return this.catches["ALL"](type, name, params); window.alert("Error type: "+arguments[0]+"\nDescription: "+arguments[1]); return null; }; window.dhtmlxError=new _dhtmlxError(); //opera fake, while 9.0 not released //multibrowser Xpath processor dtmlXMLLoaderObject.prototype.doXPathOpera=function(xpathExp, docObj){ //this is fake for Opera var z = xpathExp.replace(/[\/]+/gi, "/").split('/'); var obj = null; var i = 1; if (!z.length) return []; if (z[0] == ".") obj=[docObj]; else if (z[0] === ""){ obj=(this.xmlDoc.responseXML||this.xmlDoc).getElementsByTagName(z[i].replace(/\[[^\]]*\]/g, "")); i++; } else return []; for (i; i < z.length; i++)obj=this._getAllNamedChilds(obj, z[i]); if (z[i-1].indexOf("[") != -1) obj=this._filterXPath(obj, z[i-1]); return obj; }; dtmlXMLLoaderObject.prototype._filterXPath=function(a, b){ var c = []; var b = b.replace(/[^\[]*\[\@/g, "").replace(/[\[\]\@]*/g, ""); for (var i = 0; i < a.length; i++) if (a[i].getAttribute(b)) c[c.length]=a[i]; return c; }; dtmlXMLLoaderObject.prototype._getAllNamedChilds=function(a, b){ var c = []; if (_isKHTML) b=b.toUpperCase(); for (var i = 0; i < a.length; i++)for (var j = 0; j < a[i].childNodes.length; j++){ if (_isKHTML){ if (a[i].childNodes[j].tagName&&a[i].childNodes[j].tagName.toUpperCase() == b) c[c.length]=a[i].childNodes[j]; } else if (a[i].childNodes[j].tagName == b) c[c.length]=a[i].childNodes[j]; } return c; }; function dhtmlXHeir(a, b){ for (var c in b) if (typeof (b[c]) == "function") a[c]=b[c]; return a; } function dhtmlxEvent(el, event, handler){ if (el.addEventListener) el.addEventListener(event, handler, false); else if (el.attachEvent) el.attachEvent("on"+event, handler); } //============= XSL Extension =================================== dtmlXMLLoaderObject.prototype.xslDoc=null; dtmlXMLLoaderObject.prototype.setXSLParamValue=function(paramName, paramValue, xslDoc){ if (!xslDoc) xslDoc=this.xslDoc; if (xslDoc.responseXML) xslDoc=xslDoc.responseXML; var item = this.doXPath("/xsl:stylesheet/xsl:variable[@name='"+paramName+"']", xslDoc, "http:/\/www.w3.org/1999/XSL/Transform", "single"); if (item) item.firstChild.nodeValue=paramValue; }; dtmlXMLLoaderObject.prototype.doXSLTransToObject=function(xslDoc, xmlDoc){ if (!xslDoc) xslDoc=this.xslDoc; if (xslDoc.responseXML) xslDoc=xslDoc.responseXML; if (!xmlDoc) xmlDoc=this.xmlDoc; if (xmlDoc.responseXML) xmlDoc=xmlDoc.responseXML; var result; //Mozilla if (!_isIE){ if (!this.XSLProcessor){ this.XSLProcessor=new XSLTProcessor(); this.XSLProcessor.importStylesheet(xslDoc); } result = this.XSLProcessor.transformToDocument(xmlDoc); } else { result = new ActiveXObject("Msxml2.DOMDocument.3.0"); try{ xmlDoc.transformNodeToObject(xslDoc, result); }catch(e){ result = xmlDoc.transformNode(xslDoc); } } return result; }; dtmlXMLLoaderObject.prototype.doXSLTransToString=function(xslDoc, xmlDoc){ var res = this.doXSLTransToObject(xslDoc, xmlDoc); if(typeof(res)=="string") return res; return this.doSerialization(res); }; dtmlXMLLoaderObject.prototype.doSerialization=function(xmlDoc){ if (!xmlDoc) xmlDoc=this.xmlDoc; if (xmlDoc.responseXML) xmlDoc=xmlDoc.responseXML; if (!_isIE){ var xmlSerializer = new XMLSerializer(); return xmlSerializer.serializeToString(xmlDoc); } else return xmlDoc.xml; }; /** * @desc: * @type: private */ dhtmlxEventable=function(obj){ 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){ 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){ this.detachEvent(name); this[name] = null; } } }; obj = null; }; 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:"2.1.1" }; /*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){ 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; }; dhtmlxEventable(gantt); 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); if (id !== null){ var res = !gantt.checkEvent("onTaskClick") || gantt.callEvent("onTaskClick", [id, e]); if(res && gantt.config.select_task){ gantt.selectTask(id); } }else{ gantt.callEvent("onEmptyClick", [e]); } gantt._find_ev_handler(e, trg, gantt._click, 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) e.preventDefault(); return res; }; gantt._find_ev_handler = function(e, trg, hash, id){ var res = true; while (trg && trg.parentNode){ 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]]){ res = hash[css[i]].call(gantt, e, id, trg); res = !(typeof res!="undefined"&&res!==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 default_action = gantt._find_ev_handler(e, trg, gantt._dbl_click, id); if(!default_action) return; if (id !== null){ var res = !gantt.checkEvent("onTaskDblClick") || gantt.callEvent("onTaskDblClick", [id, e]); 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) { 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,e]) === 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,e]); } 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); }, this); this._click.gantt_open = dhtmlx.bind(function(e, id, trg) { this.open(id); }, this); this._click.gantt_row = dhtmlx.bind(function(e, id, trg) { if (id!==null) { var el = this.getTaskNode(id); var left = Math.max(el.offsetLeft - this.config.task_scroll_offset, 0); this.scrollTo(left); 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._render_grid_header(); 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 parent = id ? this.getTask(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; } if(parent) parent.$open = true; var item = { text:gantt.locale.labels.new_task, start_date:this.templates.xml_format(startDate), duration: 1, progress: 0, parent: id }; item.id = dhtmlx.uid(); this.callEvent("onTaskCreated", [item]); if (this.config.details_on_create){ item.$new = true; this._pull[item.id] = this._init_task(item); this._add_branch(item); item.$level = this._item_level(item); this.selectTask(item.id); this.refreshData(); this.showLightbox(item.id); }else{ this.addTask(item); this.showTask(item.id); this.selectTask(item.id); } }, this); }; gantt._render_grid = function(){ if(this._is_grid_visible()){ this._calc_grid_width(); this._render_grid_header(); } }; gantt._calc_grid_width = function() { if (this.config.autofit) { var columns = this.config.columns; 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; } 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]; } } }; gantt._render_grid_header = function() { var columns = this.config.columns; 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.config.columns; 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" && i == columns.length-1) { 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._branches[item.id] && this._branches[item.id].length > 0); 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._branches[this.getTask(id).parent]; 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.getTask(id).parent; tindex = this.getTaskIndex(id); } parent = parent || this.config.root_id; var source = this.getTask(sid); var sbranch = this._branches[source.parent]; var tbranch = this._branches[parent]; if (tindex == -1) tindex = tbranch.length + 1; if (source.parent == parent){ var sindex = this.getTaskIndex(sid); if (sindex == tindex) return; if (sindex < tindex) tindex--; } this._replace_branch_child(source.parent, sid); tbranch = this._branches[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)); source.parent = parent; this._branches[parent] = tbranch; 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); 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); 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.attachEvent("onDragMove", dhtmlx.bind(function(obj,e) { var dd = dnd.config; var pos = this._get_position(this.$grid_data); // row offset var x = pos.x + 10; 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; // setting position of row dd.marker.style.left = x + "px"; dd.marker.style.top = y + "px"; // 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+(over.id == item.id ? 1 : 0)]]; //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){ this.moveTask(item.id, -1, next.parent); dd.target = "next:"+next.id; return; } } } //replacing item under cursor if (over.$level == item.$level && item.id != over.id){ this.moveTask(item.id, 0, 0, over.id); dd.target = over.id; } else { //if item is on different level, check the one before it if (item.id == over.id) return; var index = this.getGlobalTaskIndex(over.id); var prev = this._pull[this._order[index-1]]; if (prev && prev.$level == item.$level && item.id != prev.id){ this.moveTask(item.id, -1, prev.parent); dd.target = "next:"+prev.id; } } } return true; }, this)); dnd.attachEvent("onDragEnd", dhtmlx.bind(function(){ this.getTask(dnd.config.id).$transparent = false; this.refreshData(); this.callEvent("onRowDragEnd", [dnd.config.id, dnd.config.target]); }, this)); }; 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){ return cellSize(a.unit, a.step) < cellSize(b.unit, b.step) ? 1 : -1; }); }, 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]); } return configs; }, _ignore_time_config : function(date){ if(this.config.skip_off_time){ return !this.isWorkTime(date); } return false; }, processIgnores : function(config){ var display_count = config.count; config.ignore_x = {}; if(gantt.ignore_time || gantt.config.skip_off_time){ var ignore = gantt.ignore_time || function(){return false;}; display_count = 0; for(var i=0; i < config.trace_x.length; i++){ if(ignore.call(gantt, config.trace_x[i]) || this._ignore_time_config.call(gantt,config.trace_x[i])){ config.ignore_x[config.trace_x[i].valueOf()] = true; config.ignored_colls = true; }else{ display_count++; } } } config.display_count = display_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:[], 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); while(+curr < +end){ callback.call(this, new Date(curr)); curr = gantt.date.add(curr, step, unit); } }, 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); } } }; 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._date_from_pos(coords_x.start + shift); if(!ev.start_date){ ev.start_date = new Date(gantt.getState().min_date); } }else{ ev.end_date =gantt._date_from_pos(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._date_from_pos(coords_x.start + shift), new_end = gantt._date_from_pos(coords_x.end + shift); if(!new_start){ ev.start_date = new Date(gantt.getState().min_date); ev.end_date = gantt._date_from_pos(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._date_from_pos(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._date_from_pos(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; if (gantt.config.readonly || 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]){ var id = gantt.locate(src), task = dhtmlx.copy(gantt.getTask(id) || {}); 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; } if(drag.mode == gantt.config.drag_mode.resize){ if(drag.left){ task.start_date = gantt._get_closest_date({date:task.start_date, unit:unit, step:step}); }else{ task.end_date = gantt._get_closest_date({date:task.end_date, unit:unit, step:step}); } }else if(drag.mode == gantt.config.drag_mode.move){ task.start_date = gantt._get_closest_date({date:task.start_date, unit:unit, step:step}); task.end_date = gantt.calculateEndDate(task.start_date, task.duration, gantt.config.duration_unit); } }, _fix_working_times:function(task, drag){ 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){ 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.getClosestWorkTime({date:task.end_date, dir:'past'}); task.start_date = gantt.calculateEndDate(task.end_date, task.duration*-1); } } } }, 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); gantt.updateTask(ev.id); this._fireEvent("after_finish", drag.mode, [drag_id, drag.mode, e]); this.clear_drag_state(); } } 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._render_link = function(id){ var link = this.getLink(id); gantt._linkRenderer.render_item(link, this.$task_links); }; 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"; var cssTemplate = this.templates.link_class ? this.templates.link_class(link) : ""; if(cssTemplate){ css += " " + cssTemplate; } 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; } div.appendChild(drawer.render_line(lines[i], lines[i+1])); } var direction = lines[lines.length - 1].direction; var endpoint = gantt._render_link_arrow(dots[dots.length - 1], direction); 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) { if(gantt.config.readonly) return false; 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); var shift = 0; if(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 link = getDndState(); if(link.from && link.to && link.from != link.to){ var type = gantt._get_link_type(link.from_start, link.to_start); if(type) gantt.addLink({source : link.from, target: link.to, type:type}); } resetDndState(); if(link.from) gantt.refreshTask(link.from, false); if(link.to) gantt.refreshTask(link.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._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(); var filter_grid_task = this._create_filter('_filter_task', '_is_grid_visible'); var filter_chart_task = this._create_filter('_filter_task', '_is_chart_visible'); var filter_link = this._create_filter('_filter_link', '_is_chart_visible'); this._taskRenderer = gantt._task_renderer("line", this._render_task_element, this.$task_bars, filter_chart_task); this._linkRenderer = gantt._task_renderer("links", this._render_link_element, this.$task_links, filter_link); this._gridRenderer = gantt._task_renderer("grid_items", this._render_grid_item, this.$grid_data, filter_grid_task); this._bgRenderer = gantt._task_renderer("bg_lines", this._render_bg_line, this.$task_bg, filter_chart_task); 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); } } this.attachEvent("onTaskIdChange", function(oldId, newId){ var render = this._get_task_renderers(); refreshId(render, oldId, newId, this.getTask(newId)); }); 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._get_task_renderers = function(){ return [ this._taskRenderer, this._gridRenderer, this._bgRenderer ]; }; gantt._get_link_renderers = function(){ return [ this._linkRenderer ]; }; gantt._delete_link_handler = function(id, e){ if(id && this.callEvent("onLinkDblClick", [id, e])){ if(this.config.readonly) 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); 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._update_layout_sizes(); 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); }; 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'; //background layer this.$task_bg.style.width = cfg.full_width + "px"; //grid area if(this._is_grid_visible()){ var columns = this.config.columns; 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._init_tasks_range = function(){ var unit = this.config.scale_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; } var data = this._get_tasks_data(); var root = this._init_task({id:this.config.root_id}); data.push(root); var max = -Infinity, min = Infinity; this.eachTask(function(child){ if(child.end_date && +child.end_date > +max){ max = new Date(child.end_date); } }, this.config.root_id); this.eachTask(function(child){ if(child.start_date && +child.start_date < +min){ min = new Date(child.start_date); } }, this.config.root_id); this._min_date = min; this._max_date = max; if(!max || max == -Infinity){ this._min_date = new Date(); this._max_date = new Date(this._min_date); } this._min_date = this.date[unit + "_start"](this._min_date); if(+this._min_date == +min) this._min_date = this.date.add(this.date[unit + "_start"](this._min_date), -1, unit); this._max_date = this.date[unit + "_start"](this._max_date); this._max_date = this.date.add(this._max_date, 1, unit); }; 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 || 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._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 = [helpers.primaryScale()].concat(this.config.subscales); scale_height = (this.config.scale_height-1); helpers.sortScales(scales); var resize = this._get_resize_options(); var avail_width = resize.x ? 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_links.style.width = this.$task_bars.style.width = data_width; this.$task_scale.innerHTML = scales_html; }; gantt._render_bg_line = function(item){ var cfg = gantt._tasks; var count = cfg.count; var cells = []; if(gantt.config.show_task_cells){ for (var j = 0; j < count; j++) { var width = cfg.width[j], style = "", cssclass = ""; if(width > 0){//do not render skipped columns 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; var cell = "<div class='" + cssclass + "' style='" + style + "'></div>"; cells.push(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>"; var row = document.createElement("div"); row.className = css; row.style.height = (gantt.config.row_height)+"px"; row.setAttribute(this.config.task_attribute, item.id); row.innerHTML = cells.join(""); return row; }; 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){ 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); }else{ for(var i =0; i < renders.length; i++) renders[i].remove_item(taskId); } if(refresh_links !== undefined && !refresh_links) return; var task = this.getTask(taskId); 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]); } }; gantt.refreshLink = function(linkId){ if(this.isLinkExists(linkId)) gantt._render_link(linkId); else gantt._linkRenderer.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); 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(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); }; 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._render_task_element = 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(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); //use separate div to display content above progress bar div.appendChild(gantt._render_task_content(task, width)); div.className = this._combine_item_class("gantt_task_line", this.templates.task_class(task.start_date, task.end_date, task), task.id); div.style.cssText = [ "left:" + pos.x + "px", "top:" + (padd + pos.y) + 'px', "height:" + height + 'px', "line-height:" + height + 'px', "width:" + width + 'px' ].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(cfg.show_progress && type != this.config.types.milestone){ this._render_task_progress(task,div, width); } if(!this.config.readonly){ 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){ 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_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); 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.config.readonly){ 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._date_from_pos = function(x){ var scale = this._tasks; if(x < 0 || x > scale.full_width){ return null; } var ind = 0; var summ = 0; while(summ + scale.width[ind] < x){ summ += scale.width[ind]; ind++; } var part = (x - summ)/scale.width[ind]; var 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 = 0; 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); 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; for (var xind = 0; xind < days.length-1; 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 if (+pos < days[xind+1] && !ignores[+days[xind+1]]) break; } return xind + ((date - days[xind]) / gantt._get_coll_duration(gantt._tasks, days[xind])); }; 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._get_task_coord = function(task, to_start, x_correction){ to_start = to_start !== false; x_correction = x_correction || 0; var isMilestone = (task.type == this.config.types.milestone); var x = this.posFromDate((to_start || isMilestone) ? task.start_date : task.end_date), y = this._y_from_ind(this._get_visible_order(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; }; //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"); 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); } }, render_items : function(items, container){ this.rendered = gantt._task_area_pulls[id] = {}; container = container || node; container.innerHTML = ""; 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 }; return this._task_area_renderers[id]; }; gantt._pull = {}; gantt._branches = {}; gantt._order = []; gantt._lpull = {}; gantt.load = function(url, type, callback){ 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]; } dhtmlxAjax.get(url, dhtmlx.bind(function(l) { this.on_load(l, tp); 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){ 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); this.callEvent("onLoadEnd", []); }; gantt._process_loading = function(data){ if(data.collections) this._load_collections(data.collections); var tasks = data.data; for (var i = 0; i < tasks.length; i++) { var task = tasks[i]; this._init_task(task); if (!this.callEvent("onTaskLoading", [task])) continue; this._pull[task.id] = task; this._add_branch(task); } this._sync_order(); // calculating $level for each item for (var i in this._pull) this._pull[i].$level = this._item_level(this._pull[i]); this._init_links(data.links || (data.collections ? data.collections.links : [])); }; 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() { this._order = []; this._sync_order_item({parent:this.config.root_id, $open:true, $ignore:true, id:this.config.root_id}); 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._branches[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._branches[parent]; if (branch) for (var i=0; i<branch.length; i++){ var item = this._pull[branch[i]]; code.call(master, item); if (this._branches[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]; } copy.start_date = gantt.templates.xml_format(copy.start_date); if (copy.end_date) copy.end_date = gantt.templates.xml_format(copy.end_date); return copy; }, serialize:function(){ var tasks = []; var links = []; gantt.eachTask(function(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 = loader.doXPath("//coll_options"); for (var i = 0; i < opts.length; i++) { var bind = opts[i].getAttribute("for"); var arr = collection[bind] = []; var itms = loader.doXPath(".//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 = new dtmlXMLLoaderObject(function() {}); loader.loadXMLString(text); } var xml = loader.getXMLTopNode(toptag); if (xml.tagName != toptag) throw "Invalid XML data"; var skey = xml.getAttribute("dhx_security"); if (skey) dhtmlx.security_key = skey; return loader; }, parse:function(text, loader){ loader = this._getXML(text, loader); var data = { }; var evs = data.data = []; var xml = loader.doXPath("//task"); 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){ var start_date = gantt.templates.xml_format(obj.start_date); var end_date = gantt.templates.xml_format(obj.end_date); return "<task id='"+obj.id+"' parent='"+(obj.parent||"")+"' start_date='"+start_date+"' duration='"+obj.duration+"' open='"+(!!obj.open)+"' progress='"+obj.progress+"' end_date='"+end_date+"'><![CDATA["+obj.text+"]]></task>"; }, serialize:function(){ var tasks = []; var links = []; gantt.eachTask(function(obj){ tasks.push(this._copyObject(obj)); },this.config.root_id, this); for (var key in gantt._lpull) links.push(this._copyLink(gantt._lpull[key])); 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 = loader.doXPath("//task"); 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 = loader.doXPath("//project"); 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 }, _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; } }, unset_time:function(settings){ if(!settings){ this.hours = []; }else{ var timestamp = this._timestamp(settings); if(timestamp !== null){ delete this.dates[timestamp]; } } }, is_working_unit : function(date, unit, order){ if(!gantt.config.work_time) return true; 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; }, add_worktime : function(from, duration, unit, step){ if(!unit) return false; var start = new Date(from), added = 0, step = step || 1, duration = duration*1; 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); 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(this._pull[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){ if (!this._branches[task.parent]) this._branches[task.parent] = []; var branch = this._branches[task.parent]; 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(); }; gantt._move_branch = function(task, old_parent, new_parent){ task.parent = new_parent; this._sync_parent(task); this._replace_branch_child(old_parent, task.id); if(new_parent){ this._add_branch(task); }else{ delete this._branches[task.id]; } task.$level = this._item_level(task); this._sync_order(); }; gantt._resync_parent = function(task){ this._move_branch(task, task.$rendered_parent, task.parent); }; gantt._sync_parent = function(task){ task.$rendered_parent = task.parent; }; gantt._is_parent_sync = function(task){ return (task.$rendered_parent == task.parent); }; gantt._replace_branch_child = function(node, old_id, new_id){ var branch = this._branches[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 = item.parent || 0; if (!dhtmlx.defined(this._pull[parent])) parent = 0; item.parent = 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.callEvent("onAfterTaskAdd", [item.id, item]); this._adjust_scales(); return item.id; }; gantt.deleteTask = function(id) { return this._deleteTask(id); }; gantt._deleteTask = function(id, silent) { var item = this.getTask(id); if (!silent && this.callEvent("onBeforeTaskDelete", [id, item])===false) return false; if (!silent && this._dp) this._dp.setUpdateMode("off"); var branches = this._branches[item.id] || []; this._update_flags(id, false); for (var i = 0; i < branches.length; i++) { this._silentStart(); this._deleteTask(branches[i], true); // add deleted subrow into dataprocessor update list manually // because silent mode is on if (this._dp) { this._dp._ganttMode = "tasks"; this._dp.setUpdated(branches[i],true,"deleted"); } this._silentEnd(); } if (!silent && this._dp) this._dp.setUpdateMode("cell"); while (item.$source.length > 0) this.deleteLink(item.$source[0]); while (item.$target.length > 0) this.deleteLink(item.$target[0]); delete this._pull[id]; this._move_branch(item, item.parent, null); if (!silent) { this.callEvent("onAfterTaskDelete", [id, item]); this.refreshData(); } return true; }; gantt.clearAll = function() { this._pull = {}; this._branches = {}; this._order = []; this._order_full = []; this._lpull = {}; this.refreshData(); this.callEvent("onClear", []); }; gantt._update_flags = function(oldid, newid){ // TODO: need a proper way to update all possible flags 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) { if (this._pull[id].parent == oldid) this._pull[id].parent = newid; } this._update_flags(oldid, newid); this._replace_branch_child(item.parent, 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){ for(var i in this.config.types){ if(this.config.types[i] == type){ return type; } } return gantt.config.types.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.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.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), 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); } } if(gantt.config.work_time && gantt.config.correct_work_time){ if(task.start_date) task.start_date = gantt.getClosestWorkTime(task.start_date); if(task.end_date) task.end_date = gantt.getClosestWorkTime(task.end_date); } gantt._init_task_timing(task); task.$source = []; task.$target = []; task.parent = task.parent || this.config.root_id; task.$open = dhtmlx.defined(task.open) ? task.open : false; task.$level = this._item_level(task); return task; }; gantt._init_task_timing = function(task){ 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; }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); }; gantt._update_parents = function(taskId, silent){ if(!taskId) return; var task = this.getTask(taskId); while(!(task.$no_end || task.$no_start) && task.parent && this.isTaskExists(task.parent)){ task = this.getTask(task.parent); } if(task.$no_end){ var max = 0; this.eachTask(function(child){ if(child.end_date && +child.end_date > +max){ max = new Date(child.end_date); } }, task.id); if(max){ task.end_date = max; } } if(task.$no_start){ var min = Infinity; this.eachTask(function(child){ if(child.start_date && +child.start_date < +min){ min = new Date(child.start_date); } }, task.id); if(min != Infinity){ task.start_date = min; } } if((task.$no_end || task.$no_start)){ this._init_task_timing(task); if(!silent) this.refreshTask(task.id, true); } if(task.parent && this.isTaskExists(task.parent)){ this._update_parents(task.parent, 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); while(task && this.isTaskExists(task.parent)){ task = this.getTask(task.parent); if(task && task.id == parent_id) return true; } return false; }; gantt._get_closest_date = function(config){ 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.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._item_level = function(item) { var level = 0; while (item.parent) { if (!dhtmlx.defined(this._pull[item.parent])) break; item = this._pull[item.parent]; level++; } return level; }; gantt.sort = function(field, desc, parent) { var render = !arguments[3];//4th argument to cancel redraw after sorting if (!dhtmlx.defined(parent)) { parent = this.config.root_id; } if (!dhtmlx.defined(field)) field = "order"; var criteria = (typeof(field) == "string") ? (function(a, b) { var result = a[field] > b[field]; if (desc) result = !result; return result ? 1 : -1; }) : field; var els = this._branches[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.refreshData(); } }; 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._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"]; this.attachEvent("onAfterTaskAdd", function(id, item) { dp._ganttMode = "tasks"; dp.setUpdated(id,true,"inserted"); }); this.attachEvent("onAfterTaskUpdate", function(id, item) { dp._ganttMode = "tasks"; dp.setUpdated(id,true); }); this.attachEvent("onAfterTaskDelete", function(id, item) { dp._ganttMode = "tasks"; dp.setUpdated(id,true,"deleted"); }); this.attachEvent("onAfterLinkUpdate", function(id, item) { dp._ganttMode = "links"; dp.setUpdated(id, true); }); this.attachEvent("onAfterLinkAdd", function(id, item) { dp._ganttMode = "links"; dp.setUpdated(id, true,"inserted"); }); this.attachEvent("onAfterLinkDelete", function(id, item) { dp._ganttMode = "links"; dp.setUpdated(id, true,"deleted"); }); this.attachEvent("onRowDragEnd", function(id, target) { dp._ganttMode = "tasks"; this.getTask(id).target = target; dp.setUpdated(id, true,"order"); }); dp.attachEvent("onBeforeDataSending", function() { this.serverProcessor = this._serverProcessor + getUrlSymbol(this._serverProcessor) + "gantt_mode=" + this._ganttMode; return true; }); 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 }; 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 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.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]); }; gantt.refreshData = function(){ this._sync_order(); this._render_data(); }; gantt._configure = function(col, data){ for (var key in data) if (typeof col[key] == "undefined") col[key] = data[key]; }; gantt._init_skin = function(){ if (!gantt.skin){ 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); var config = gantt.config.columns; 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._init_skin = function(){}; }; gantt.skins = {}; gantt._lightbox_methods = {}; gantt._lightbox_template="<div class='dhx_cal_ltitle'><span class='dhx_mark'> </span><span class='dhx_time'></span><span class='dhx_title'></span></div><div class='dhx_cal_larea'></div>"; gantt.showLightbox=function(id){ if (!id || this.config.readonly) 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){ 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) { var 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="dhx_cal_light"; var full_width = this._is_lightbox_timepicker(); if (gantt.config.wide_form || full_width) d.className+=" dhx_cal_light_wide"; if (full_width) { gantt.config.wide_form = true; d.className+=" dhx_cal_light_full"; } d.style.visibility="hidden"; var html = this._lightbox_template; var buttons = this.config.buttons_left; for (var i in buttons) html+="<div class='dhx_btn_set dhx_left_btn_set "+buttons[i]+"_set'><div dhx_button='1' class='"+buttons[i]+"'></div><div>"+this.locale.labels[buttons[i]]+"</div></div>"; buttons = this.config.buttons_right; for (var i in buttons) html+="<div class='dhx_btn_set dhx_right_btn_set "+buttons[i]+"_set' style='float:right;'><div dhx_button='1' class='"+buttons[i]+"'></div><div>"+this.locale.labels[buttons[i]]+"</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 == "dhx_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='dhx_custom_button' index='"+i+"'><div class='dhx_custom_button_"+sns[i].button+"'></div><div>"+this.locale.labels["button_"+sns[i].button]+"</div></div>"; } if (this.config.wide_form){ html+="<div class='dhx_wrap_section' " + display+">"; } html+="<div id='"+sns[i].id+"' class='dhx_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="dhx_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["dhx_save_btn"] = function(e) { gantt._save_lightbox(); }; gantt.lightbox_events["dhx_delete_btn"] = function(e) { if(!gantt.callEvent("onLightboxDelete", [gantt._lightbox_id])) return; gantt.$click.buttons["delete"](gantt._lightbox_id); }; gantt.lightbox_events["dhx_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("dhx_custom_button") != -1) { if (src.className.indexOf("dhx_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("dhx_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(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{ 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.getLightboxValues=function(){ var 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]; var res=block.get_value.call(this,node,task, sns[i]); if (sns[i].map_to!="auto") task[sns[i].map_to]=res; } return task; }; gantt.hideLightbox=function(id){ 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 value = dhtmlx.defined(task[section.map_to]) ? task[section.map_to] : section.default_value; block.set_value.call(this, node, value, task, section); if (section.focus) block.focus.call(this, 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 (!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 this.form_blocks[section.type].get_value(node, (ev||{}), section); }, setValue:function(value, ev){ return this.form_blocks[section.type].set_value(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.dhx_unselectable){ document.body.className += " dhx_unselectable"; document.dhx_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(" dhx_unselectable",""); document.dhx_unselectable = false; } }; gantt._focus = function(node, select){ if (node && node.focus){ if (gantt.config.touch){ //do not focus editor, to prevent auto-zoom } else { if (select && node.select) node.select(); node.focus(); } } }; 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 year = dt.getFullYear()-5; //maybe take from config? for (var i=0; i < 10; i++) options+="<option value='"+(year+i)+"'>"+(year+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(); s[i+map[3]].value= v; } }, template:{ render: function(sns){ var height=(sns.height||"30")+"px"; return "<div class='dhx_cal_ltext dhx_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='dhx_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='dhx_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:30px;padding-top:0px;font-size:inherit;text-align:center;' class='dhx_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;'> – </span>"); } parts.push(time); parts.push("</div>"); return parts.join(''); }, set_value:function(node,value,ev,config){ var cfg = this.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() { var start_date = new Date(s[map[2]].value,s[map[1]].value,s[map[0]].value,0,0); var 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; } } this.form_blocks._fill_lightbox_select(s,0,ev.start_date,map,cfg); this.form_blocks._fill_lightbox_select(s,map.size,ev.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; } ev.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; } ev.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 (ev.end_date<=ev.start_date) ev.end_date=gantt.date.add(ev.start_date, gantt._get_timepicker_step(),"minute"); return { start_date: new Date(ev.start_date), end_date: new Date(ev.end_date) }; }, 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='dhx_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='dhx_gantt_duration' "+singleDate+">" + "<input type='button' class='dhx_gantt_duration_dec' value='-'"+readonly+">" + "<input type='text' value='5' class='dhx_gantt_duration_value'"+readonly+">" + "<input type='button' class='dhx_gantt_duration_inc' value='+'"+readonly+"> " + label + " <span></span>" + "</div>"; var html = "<div style='height:30px;padding-top:0px;font-size:inherit;' class='dhx_section_time'>"+time+" "+duration+"</div>"; return html; }, set_value:function(node,value,ev,config){ var cfg = this.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); this.form_blocks._fill_lightbox_select(s,0,ev.start_date,map,cfg); var final_value; if (!ev.end_date) final_value = ev.duration; else final_value = gantt.calculateDuration(ev.start_date, ev.end_date); final_value = Math.round(final_value); duration.value = final_value; _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 time = parseInt(s[map[3]].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 (window.isNaN(duration)) duration = 1; if (duration < 0) duration *= -1; return duration; }, get_value:function(node, ev, config) { ev.start_date = this.form_blocks.duration._get_start_date(node, config); var duration = this.form_blocks.duration._get_duration(node, config); ev.end_date = this.calculateEndDate(ev.start_date, duration); ev.duration = duration; return { start_date: new Date(ev.start_date), end_date: new Date(ev.end_date) }; }, focus:function(node){ gantt._focus(node.getElementsByTagName("select")[0]); } }, typeselect : { render : function(sns){ var types = gantt.config.types, locale = gantt.locale.labels, options = []; for(var i in types){ options.push({key: types[i], label:locale["type_" + i]}); } sns.options = options; var oldOnChange = sns.onchange; sns.onchange = function(){ var tId = gantt.getState().lightbox; gantt.changeLightboxType(this.value); if(typeof oldOnChange == 'function'){ oldOnChange.apply(this, arguments); } }; return gantt.form_blocks.select.render.apply(this, arguments); }, set_value:function(){ return gantt.form_blocks.select.set_value.apply(this, arguments); }, get_value:function(){ return gantt.form_blocks.select.get_value.apply(this, arguments); }, focus:function(){ return gantt.form_blocks.select.focus.apply(this, arguments); } }, 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(this, [newOptions,value,ev,config]); }, get_value:function(){ return gantt.form_blocks.select.get_value.apply(this, arguments); }, focus:function(){ return gantt.form_blocks.select.focus.apply(this, 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._set_lightbox_values(updTask, box); this._center_lightbox(this.getLightbox()); this.callEvent("onLightboxChange", [oldType, this.getLightboxType()]); }else{ this.resetLightbox(); this.getLightbox(type ? type : undefined); } this.callEvent("onLightboxChange", [oldType, this.getLightboxType()]); }; /** * @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.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); dhtmlxEventable(this); return this; } dataProcessor.prototype={ /** * @desc: select GET or POST transaction model * @param: mode - GET/POST * @param: total - true/false - send records row by row or all at once (for grid only) * @type: public */ setTransactionMode:function(mode,total){ this._tMode=mode; this._tSend=total; }, 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=convertStringToBoolean(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=convertStringToBoolean(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=convertStringToBoolean(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)) 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 a2=new dtmlXMLLoaderObject(this.afterUpdate,this,true); var a3 = this.serverProcessor+(this._user?(getUrlSymbol(this.serverProcessor)+["dhx_user="+this._user,"dhx_version="+this.obj.getUserData(0,"version")].join("&")):""); if (this._tMode!="POST") a2.loadXML(a3+((a3.indexOf("?")!=-1)?"&":"?")+this.serialize(a1,rowId)); else a2.loadXML(a3,true,this.serialize(a1,rowId)); 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)])) 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]); } 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,b,c,d,xml){ xml.getXMLTopNode("data"); //fix incorrect content type in IE if (!xml.xmlDoc.responseXML) return; var atag=xml.doXPath("//data/action"); 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(); }, 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+getUrlSymbol(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; }); } }; /* 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){ 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(); this._reinit(node); this.attachEvent("onLoadEnd", this.render); dhtmlxEvent(window, "resize", this._on_resize); //can be called only once this.init = function(node){ if (this.$container) this.$container.innerHTML = ""; this._reinit(node); }; this.callEvent("onGanttReady", []); }; gantt._reinit = function(node){ this._init_html_area(node); this._set_sizes(); this._task_area_pulls = {}; this._task_area_renderers = {}; this._init_touch_events(); this._init_templates(); this._init_grid(); this._init_tasks(); this.render(); 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); }; //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; }; //set sizes to top level html element gantt._set_sizes = function(){ var resize = this._get_resize_options(); if(resize.y){ this._obj.style.height = this._calculate_content_height() + 'px'; } if(resize.x){ this._obj.style.width = this._calculate_content_width() + 'px'; } this._y = this._obj.clientHeight; 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' : ''; this._x = this._obj.clientWidth; 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(){ return { x:this.$task.scrollLeft, y:this.$task_data.scrollTop }; }; gantt.scrollTo = function(left, top){ if (left*1 == left) this.$task.scrollLeft = left; if(top*1 == top){ this.$task_data.scrollTop = top; this.$grid_data.scrollTop = 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(){ this._render_grid(); //grid.js this._render_tasks_scales(); //tasks.js this._scroll_resize(); this._on_resize(); this._render_data(); if(this.config.initial_scroll){ var id = (this._order[0] || this.config.root_id); if(id) this.showTask(id); } this.callEvent("onGanttRender", []); }; gantt._set_scroll_events = function(){ dhtmlxEvent(this.$scroll_hor, "scroll", function() { 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; }); dhtmlxEvent(gantt.$container, "mousewheel", function(e){ var res = gantt._get_resize_options(); if (e.wheelDeltaX){ if(res.x) return true;//no horisontal scroll, must not block scrolling var dir = e.wheelDeltaX/-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 = e.wheelDelta/-40; if (typeof e.wheelDelta == "undefined") dir = e.detail; var top = gantt.$grid_data.scrollTop+dir*30; gantt.scrollTo(null, top); gantt.$scroll_ver.scrollTop = top; } if (e.preventDefault) e.preventDefault(); e.cancelBubble=true; return false; }); }; gantt._scroll_resize = function() { if (this._x < 20 || this._y < 20) return; var grid_width = this._get_grid_width(); var task_width = this._x - grid_width; var task_height = this._y - this.config.scale_height; var scroll_size = this.config.scroll_size + 1;//1px for inner content var task_data_width = this.$task_data.offsetWidth - scroll_size; 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 = (this._x - (scroll_ver ? scroll_size : 2)) + "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 = (this._y - (scroll_hor ? scroll_size : 0) - this.config.scale_height) + "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 == "gantt_task_cell") 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 }; }; 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.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){ date.setHours(0); date.setMinutes(0); date.setSeconds(0); date.setMilliseconds(0); if (date.getHours()) 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 (!date.getHours() && ndate.getHours()) //shift to yesterday 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; } date = format(date); } return date; } }; /* %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 ( 1900–9999 ); %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, show_links : true, show_task_cells : true, 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, 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: true, // grid column automatic fit columns: [ {name:"text", tree:true, width:'*' }, {name:"start_date", align: "center" }, {name:"duration", align: "center" }, {name:"add", width:'44' } ], /*scale*/ step: 1, scale_unit: "day", subscales : [ ], 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", buttons_left: [ "dhx_save_btn", "dhx_cancel_btn" ], buttons_right: [ "dhx_delete_btn" ], lightbox: { sections: [ {name: "description", height: 70, map_to: "text", type: "textarea", focus: true}, {name: "time", height: 72, 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", height: 72, 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", height: 72, 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 }); 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.dhx_save_btn = labels.icon_save; labels.dhx_cancel_btn = labels.icon_cancel; labels.dhx_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> – <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, task_height: 24, 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, task_height:24, 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, task_height: 24, 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, task_height: 24, link_line_width:1, link_arrow_size:7, lightbox_additional_height:86 }, _second_column_width:90, _third_column_width:80, _lightbox_template:"<div class='dhx_cal_ltitle'><span class='dhx_mark'> </span><span class='dhx_time'></span><span class='dhx_title'></span><div class='dhx_cancel_btn'></div></div><div class='dhx_cal_larea'></div>", _config_buttons_left: {}, _config_buttons_right: { "dhx_delete_btn": "icon_delete", "dhx_save_btn": "icon_save" } }; gantt.config.touch_drag = 50; //nearly immediate dnd gantt.config.touch = true; 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 }; 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; //touch move if (!this._gantt_touch_event_ready){ this._gantt_touch_event_ready = 1; dhtmlxEvent(document.body, names[0], function(e){ if (ignore(e)) return; //ignore common and scrolling moves if (!action_mode) return; var source = accessor(e); 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); }); } //common helper, prevents event function block_action(e){ if (e && e.preventDefault) e.preventDefault(); (e||event).cancelBubble = true; return false; } //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(); } }); //touch end dhtmlxEvent(this.$container, names[2], function(e){ if (ignore(e)) return; gantt._touch_scroll_active = action_mode = scroll_mode = false; }); };