/*
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'>&nbsp;</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;'> &nbsp;&ndash;&nbsp; </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> &ndash;  <b>" + to.text+"</b>";
		},

		drag_link : function(from, from_start, to, to_start) {
			from = gantt.getTask(from);
			var labels = gantt.locale.labels;

			var text = "<b>" + from.text + "</b> " + (from_start ? labels.link_start : labels.link_end)+"<br/>";
			if(to){
				to = gantt.getTask(to);
				text += "<b> " + to.text + "</b> "+ (to_start ? labels.link_start : labels.link_end)+"<br/>";
			}
			return text;
		},
		drag_link_class: function(from, from_start, to, to_start) {
			var add = "";

			if(from && to){
				var allowed = gantt.isLinkAllowed(from, to, from_start, to_start);
				add = " " + (allowed ? "gantt_link_allow" : "gantt_link_deny");
			}

			return "gantt_link_tooltip" + add;
		}
    });

	this.callEvent("onTemplatesReady",[]);
};

})();
if (window.jQuery){

(function( $ ){

	var methods = [];
	$.fn.dhx_gantt = function(config){
		config = config || {};
		if (typeof(config) === 'string') {
			if (methods[config] ) {
				return methods[config].apply(this, []);
			}else {
				$.error('Method ' +  config + ' does not exist on jQuery.dhx_gantt');
			}
		} else {
			var views = [];
			this.each(function() {
				if (this && this.getAttribute){
					if (!this.getAttribute("dhxgantt")){
						for (var key in config)
							if (key!="data")
								gantt.config[key] = config[key];

						gantt.init(this);
						if (config.data)
							gantt.parse(config.data);

						views.push(gantt);
					}
				}
			});

		
			if (views.length === 1) return views[0];
			return views;
		}
	};

})(jQuery);

}

if (window.dhtmlx){

	if (!dhtmlx.attaches)
		dhtmlx.attaches = {};
		
	dhtmlx.attaches.attachGantt=function(start, end){
		var obj = document.createElement("DIV");
		obj.id = "gantt_"+dhtmlx.uid();
		obj.style.width = "100%";
		obj.style.height = "100%";
		obj.cmp = "grid";

		document.body.appendChild(obj);
		this.attachObject(obj.id);
		
		var that = this.vs[this.av];
		that.grid = gantt;

		gantt.init(obj.id, start, end);
		obj.firstChild.style.border = "none";
		
		that.gridId = obj.id;
		that.gridObj = obj;
			
		var method_name="_viewRestore";
		return this.vs[this[method_name]()].grid;
	};

}
gantt.locale = {
	date:{
		month_full:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
		month_short:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
		day_full:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
		day_short:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
	},
	labels:{
		new_task:"New task",
		icon_save:"Save",
		icon_cancel:"Cancel",
		icon_details:"Details",
		icon_edit:"Edit",
		icon_delete:"Delete",
		confirm_closing:"",//Your changes will be lost, are your sure ?
		confirm_deleting:"Task will be deleted permanently, are you sure?",
        section_description:"Description",
        section_time:"Time period",
		section_type:"Type",

        /* grid columns */

        column_text : "Task name",
        column_start_date : "Start time",
        column_duration : "Duration",
        column_add : "",

		/* link confirmation */
		link: "Link",
		confirm_link_deleting:"will be deleted",
		link_start: " (start)",
		link_end: " (end)",

		type_task: "Task",
		type_project: "Project",
		type_milestone: "Milestone",

        minutes: "Minutes",
        hours: "Hours",
        days: "Days",
        weeks: "Week",
        months: "Months",
        years: "Years"
	}
};




gantt.skins.skyblue = {
	config:{
		grid_width:350,
		row_height: 27,
		scale_height: 27,
		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'>&nbsp;</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;
	});	
};