You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1706 lines
52 KiB
1706 lines
52 KiB
3 years ago
|
/*
|
||
|
Copyright (c) 2012-2018 Open Lab
|
||
|
Written by Roberto Bicchierai and Silvia Chelazzi http://roberto.open-lab.com
|
||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||
|
a copy of this software and associated documentation files (the
|
||
|
"Software"), to deal in the Software without restriction, including
|
||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
permit persons to whom the Software is furnished to do so, subject to
|
||
|
the following conditions:
|
||
|
|
||
|
The above copyright notice and this permission notice shall be
|
||
|
included in all copies or substantial portions of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
*/
|
||
|
function GanttMaster() {
|
||
|
this.tasks = [];
|
||
|
this.deletedTaskIds = [];
|
||
|
this.links = [];
|
||
|
|
||
|
this.editor; //element for editor
|
||
|
this.gantt; //element for gantt
|
||
|
this.splitter; //element for splitter
|
||
|
|
||
|
this.isMultiRoot=false; // set to true in case of tasklist
|
||
|
|
||
|
this.workSpace; // the original element used for containing everything
|
||
|
this.element; // editor and gantt box without buttons
|
||
|
|
||
|
|
||
|
this.resources; //list of resources
|
||
|
this.roles; //list of roles
|
||
|
this.material; //list of material
|
||
|
this.tools; //list of tools
|
||
|
|
||
|
this.minEditableDate = 0;
|
||
|
this.maxEditableDate = Infinity;
|
||
|
this.set100OnClose=false;
|
||
|
this.shrinkParent=false;
|
||
|
|
||
|
this.fillWithEmptyLines=true; //when is used by widget it could be usefull to do not fill with empty lines
|
||
|
|
||
|
this.rowHeight = 30; // todo get it from css?
|
||
|
this.minRowsInEditor=30; // number of rows always visible in editor
|
||
|
this.numOfVisibleRows=0; //number of visible rows in the editor
|
||
|
this.firstScreenLine=0; //first visible row ignoring collapsed tasks
|
||
|
this.rowBufferSize=5;
|
||
|
this.firstVisibleTaskIndex=-1; //index of first task visible
|
||
|
this.lastVisibleTaskIndex=-1; //index of last task visible
|
||
|
|
||
|
this.baselines={}; // contains {taskId:{taskId,start,end,status,progress}}
|
||
|
this.showBaselines=false; //allows to draw baselines
|
||
|
this.baselineMillis; //millis of the current baseline loaded
|
||
|
|
||
|
|
||
|
this.permissions = {
|
||
|
canWriteOnParent: true,
|
||
|
canWrite: true,
|
||
|
canAdd: true,
|
||
|
canDelete: true,
|
||
|
canInOutdent: true,
|
||
|
canMoveUpDown: true,
|
||
|
canSeePopEdit: true,
|
||
|
canSeeFullEdit: true,
|
||
|
canSeeDep: true,
|
||
|
canSeeCriticalPath: true,
|
||
|
canAddIssue: false,
|
||
|
cannotCloseTaskIfIssueOpen: false
|
||
|
};
|
||
|
|
||
|
this.firstDayOfWeek = Date.firstDayOfWeek;
|
||
|
this.serverClientTimeOffset = 0;
|
||
|
|
||
|
this.currentTask; // task currently selected;
|
||
|
|
||
|
this.resourceUrl = "res/"; // URL to resources (images etc.)
|
||
|
this.__currentTransaction; // a transaction object holds previous state during changes
|
||
|
this.__undoStack = [];
|
||
|
this.__redoStack = [];
|
||
|
this.__inUndoRedo = false; // a control flag to avoid Undo/Redo stacks reset when needed
|
||
|
|
||
|
Date.workingPeriodResolution=1; //by default 1 day
|
||
|
|
||
|
var self = this;
|
||
|
}
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.init = function (workSpace) {
|
||
|
var place=$("<div>").prop("id","TWGanttArea").css( {padding:0, "overflow-y":"auto", "overflow-x":"hidden","border":"1px solid #e5e5e5",position:"relative"});
|
||
|
workSpace.append(place).addClass("TWGanttWorkSpace");
|
||
|
|
||
|
this.workSpace=workSpace;
|
||
|
this.element = place;
|
||
|
this.numOfVisibleRows=Math.ceil(this.element.height()/this.rowHeight);
|
||
|
|
||
|
//by default task are coloured by status
|
||
|
this.element.addClass('colorByStatus' )
|
||
|
|
||
|
var self = this;
|
||
|
//load templates
|
||
|
$("#gantEditorTemplates").loadTemplates().remove();
|
||
|
|
||
|
//create editor
|
||
|
this.editor = new GridEditor(this);
|
||
|
place.append(this.editor.gridified);
|
||
|
|
||
|
//create gantt
|
||
|
this.gantt = new Ganttalendar(new Date().getTime() - 3600000 * 24 * 2, new Date().getTime() + 3600000 * 24 * 5, this, place.width() * .6);
|
||
|
|
||
|
//setup splitter
|
||
|
self.splitter = $.splittify.init(place, this.editor.gridified, this.gantt.element, 60);
|
||
|
self.splitter.firstBoxMinWidth = 5;
|
||
|
self.splitter.secondBoxMinWidth = 20;
|
||
|
|
||
|
//prepend buttons
|
||
|
var ganttButtons = $.JST.createFromTemplate({}, "GANTBUTTONS");
|
||
|
place.before(ganttButtons);
|
||
|
this.checkButtonPermissions();
|
||
|
|
||
|
|
||
|
//bindings
|
||
|
workSpace.bind("deleteFocused.gantt", function (e) {
|
||
|
//delete task or link?
|
||
|
var focusedSVGElement=self.gantt.element.find(".focused.focused.linkGroup");
|
||
|
if (focusedSVGElement.size()>0)
|
||
|
self.removeLink(focusedSVGElement.data("from"), focusedSVGElement.data("to"));
|
||
|
else
|
||
|
self.deleteCurrentTask();
|
||
|
}).bind("addAboveCurrentTask.gantt", function () {
|
||
|
self.addAboveCurrentTask();
|
||
|
}).bind("addBelowCurrentTask.gantt", function () {
|
||
|
self.addBelowCurrentTask();
|
||
|
}).bind("indentCurrentTask.gantt", function () {
|
||
|
self.indentCurrentTask();
|
||
|
}).bind("outdentCurrentTask.gantt", function () {
|
||
|
self.outdentCurrentTask();
|
||
|
}).bind("moveUpCurrentTask.gantt", function () {
|
||
|
self.moveUpCurrentTask();
|
||
|
}).bind("moveDownCurrentTask.gantt", function () {
|
||
|
self.moveDownCurrentTask();
|
||
|
}).bind("collapseAll.gantt", function () {
|
||
|
self.collapseAll();
|
||
|
}).bind("expandAll.gantt", function () {
|
||
|
self.expandAll();
|
||
|
}).bind("fullScreen.gantt", function () {
|
||
|
self.fullScreen();
|
||
|
}).bind("print.gantt", function () {
|
||
|
self.print();
|
||
|
|
||
|
|
||
|
}).bind("zoomPlus.gantt", function () {
|
||
|
self.gantt.zoomGantt(true);
|
||
|
}).bind("zoomMinus.gantt", function () {
|
||
|
self.gantt.zoomGantt(false);
|
||
|
|
||
|
}).bind("openFullEditor.gantt", function () {
|
||
|
self.editor.openFullEditor(self.currentTask,false, false, false);
|
||
|
}).bind("openAssignmentEditor.gantt", function () {
|
||
|
self.editor.openFullEditor(self.currentTask,true);
|
||
|
}).bind("addIssue.gantt", function () {
|
||
|
self.addIssue();
|
||
|
}).bind("openExternalEditor.gantt", function () {
|
||
|
self.openExternalEditor();
|
||
|
|
||
|
}).bind("undo.gantt", function () {
|
||
|
self.undo();
|
||
|
}).bind("redo.gantt", function () {
|
||
|
self.redo();
|
||
|
}).bind("resize.gantt", function () {
|
||
|
self.resize();
|
||
|
});
|
||
|
|
||
|
|
||
|
//bind editor scroll
|
||
|
self.splitter.firstBox.scroll(function () {
|
||
|
|
||
|
//notify scroll to editor and gantt
|
||
|
self.gantt.element.stopTime("test").oneTime(10, "test", function () {
|
||
|
var oldFirstRow = self.firstScreenLine;
|
||
|
var newFirstRow = Math.floor(self.splitter.firstBox.scrollTop() / self.rowHeight);
|
||
|
if (Math.abs(oldFirstRow - newFirstRow) >= self.rowBufferSize) {
|
||
|
self.firstScreenLine = newFirstRow;
|
||
|
self.scrolled(oldFirstRow);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
|
||
|
//keyboard management bindings
|
||
|
$("body").bind("keydown.body", function (e) {
|
||
|
//console.debug(e.keyCode+ " "+e.target.nodeName, e.ctrlKey)
|
||
|
|
||
|
var eventManaged = true;
|
||
|
var isCtrl = e.ctrlKey || e.metaKey;
|
||
|
var bodyOrSVG = e.target.nodeName.toLowerCase() == "body" || e.target.nodeName.toLowerCase() == "svg";
|
||
|
var inWorkSpace=$(e.target).closest("#TWGanttArea").length>0;
|
||
|
|
||
|
//store focused field
|
||
|
var focusedField=$(":focus");
|
||
|
var focusedSVGElement = self.gantt.element.find(".focused.focused");// orrible hack for chrome that seems to keep in memory a cached object
|
||
|
|
||
|
var isFocusedSVGElement=focusedSVGElement.length >0;
|
||
|
|
||
|
if ((inWorkSpace ||isFocusedSVGElement) && isCtrl && e.keyCode == 37) { // CTRL+LEFT on the grid
|
||
|
self.outdentCurrentTask();
|
||
|
focusedField.focus();
|
||
|
|
||
|
} else if (inWorkSpace && isCtrl && e.keyCode == 38) { // CTRL+UP on the grid
|
||
|
self.moveUpCurrentTask();
|
||
|
focusedField.focus();
|
||
|
|
||
|
} else if (inWorkSpace && isCtrl && e.keyCode == 39) { //CTRL+RIGHT on the grid
|
||
|
self.indentCurrentTask();
|
||
|
focusedField.focus();
|
||
|
|
||
|
} else if (inWorkSpace && isCtrl && e.keyCode == 40) { //CTRL+DOWN on the grid
|
||
|
self.moveDownCurrentTask();
|
||
|
focusedField.focus();
|
||
|
|
||
|
} else if (isCtrl && e.keyCode == 89) { //CTRL+Y
|
||
|
self.redo();
|
||
|
|
||
|
} else if (isCtrl && e.keyCode == 90) { //CTRL+Y
|
||
|
self.undo();
|
||
|
|
||
|
|
||
|
} else if ( (isCtrl && inWorkSpace) && (e.keyCode == 8 || e.keyCode == 46) ) { //CTRL+DEL CTRL+BACKSPACE on grid
|
||
|
self.deleteCurrentTask();
|
||
|
|
||
|
} else if ( focusedSVGElement.is(".taskBox") && (e.keyCode == 8 || e.keyCode == 46) ) { //DEL BACKSPACE svg task
|
||
|
self.deleteCurrentTask();
|
||
|
|
||
|
} else if ( focusedSVGElement.is(".linkGroup") && (e.keyCode == 8 || e.keyCode == 46) ) { //DEL BACKSPACE svg link
|
||
|
self.removeLink(focusedSVGElement.data("from"), focusedSVGElement.data("to"));
|
||
|
|
||
|
} else {
|
||
|
eventManaged=false;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (eventManaged) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
//ask for comment input
|
||
|
$("#saveGanttButton").after($('#LOG_CHANGES_CONTAINER'));
|
||
|
|
||
|
//ask for comment management
|
||
|
this.element.on("saveRequired.gantt",this.manageSaveRequired);
|
||
|
|
||
|
|
||
|
//resize
|
||
|
$(window).resize(function () {
|
||
|
place.css({width: "100%", height: $(window).height() - place.position().top});
|
||
|
place.trigger("resize.gantt");
|
||
|
}).oneTime(2, "resize", function () {$(window).trigger("resize")});
|
||
|
|
||
|
|
||
|
};
|
||
|
|
||
|
GanttMaster.messages = {
|
||
|
"CANNOT_WRITE": "CANNOT_WRITE",
|
||
|
"CHANGE_OUT_OF_SCOPE": "NO_RIGHTS_FOR_UPDATE_PARENTS_OUT_OF_EDITOR_SCOPE",
|
||
|
"START_IS_MILESTONE": "START_IS_MILESTONE",
|
||
|
"END_IS_MILESTONE": "END_IS_MILESTONE",
|
||
|
"TASK_HAS_CONSTRAINTS": "TASK_HAS_CONSTRAINTS",
|
||
|
"GANTT_ERROR_DEPENDS_ON_OPEN_TASK": "GANTT_ERROR_DEPENDS_ON_OPEN_TASK",
|
||
|
"GANTT_ERROR_DESCENDANT_OF_CLOSED_TASK": "GANTT_ERROR_DESCENDANT_OF_CLOSED_TASK",
|
||
|
"TASK_HAS_EXTERNAL_DEPS": "TASK_HAS_EXTERNAL_DEPS",
|
||
|
"GANTT_ERROR_LOADING_DATA_TASK_REMOVED": "GANTT_ERROR_LOADING_DATA_TASK_REMOVED",
|
||
|
"CIRCULAR_REFERENCE": "CIRCULAR_REFERENCE",
|
||
|
"CANNOT_MOVE_TASK": "CANNOT_MOVE_TASK",
|
||
|
"CANNOT_DEPENDS_ON_ANCESTORS": "CANNOT_DEPENDS_ON_ANCESTORS",
|
||
|
"CANNOT_DEPENDS_ON_DESCENDANTS": "CANNOT_DEPENDS_ON_DESCENDANTS",
|
||
|
"INVALID_DATE_FORMAT": "INVALID_DATE_FORMAT",
|
||
|
"GANTT_SEMESTER_SHORT": "GANTT_SEMESTER_SHORT",
|
||
|
"GANTT_SEMESTER": "GANTT_SEMESTER",
|
||
|
"GANTT_QUARTER_SHORT": "GANTT_QUARTER_SHORT",
|
||
|
"GANTT_QUARTER": "GANTT_QUARTER",
|
||
|
"GANTT_WEEK": "GANTT_WEEK",
|
||
|
"GANTT_WEEK_SHORT": "GANTT_WEEK_SHORT",
|
||
|
"CANNOT_CLOSE_TASK_IF_OPEN_ISSUE": "CANNOT_CLOSE_TASK_IF_OPEN_ISSUE",
|
||
|
"PLEASE_SAVE_PROJECT": "PLEASE_SAVE_PROJECT",
|
||
|
"CANNOT_CREATE_SAME_LINK": "CANNOT_CREATE_SAME_LINK"
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.createTask = function (id, name, code, level, start, duration) {
|
||
|
var factory = new TaskFactory();
|
||
|
return factory.build(id, name, code, level, start, duration);
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.getOrCreateResource = function (id, name) {
|
||
|
var res= this.getResource(id);
|
||
|
if (!res && id && name) {
|
||
|
res = this.createResource(id, name);
|
||
|
}
|
||
|
return res
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.createResource = function (id, name) {
|
||
|
var res = new Resource(id, name);
|
||
|
this.resources.push(res);
|
||
|
return res;
|
||
|
};
|
||
|
|
||
|
|
||
|
//update depends strings
|
||
|
GanttMaster.prototype.updateDependsStrings = function () {
|
||
|
//remove all deps
|
||
|
for (var i = 0; i < this.tasks.length; i++) {
|
||
|
this.tasks[i].depends = "";
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < this.links.length; i++) {
|
||
|
var link = this.links[i];
|
||
|
var dep = link.to.depends;
|
||
|
link.to.depends = link.to.depends + (link.to.depends == "" ? "" : ",") + (link.from.getRow() + 1) + (link.lag ? ":" + link.lag : "");
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.removeLink = function (fromTask, toTask) {
|
||
|
//console.debug("removeLink");
|
||
|
if (!this.permissions.canWrite || (!fromTask.canWrite && !toTask.canWrite))
|
||
|
return;
|
||
|
|
||
|
this.beginTransaction();
|
||
|
var found = false;
|
||
|
for (var i = 0; i < this.links.length; i++) {
|
||
|
if (this.links[i].from == fromTask && this.links[i].to == toTask) {
|
||
|
this.links.splice(i, 1);
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (found) {
|
||
|
this.updateDependsStrings();
|
||
|
if (this.updateLinks(toTask))
|
||
|
this.changeTaskDates(toTask, toTask.start, toTask.end); // fake change to force date recomputation from dependencies
|
||
|
}
|
||
|
this.endTransaction();
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.__removeAllLinks = function (task, openTrans) {
|
||
|
|
||
|
if (openTrans)
|
||
|
this.beginTransaction();
|
||
|
var found = false;
|
||
|
for (var i = 0; i < this.links.length; i++) {
|
||
|
if (this.links[i].from == task || this.links[i].to == task) {
|
||
|
this.links.splice(i, 1);
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (found) {
|
||
|
this.updateDependsStrings();
|
||
|
}
|
||
|
if (openTrans)
|
||
|
this.endTransaction();
|
||
|
};
|
||
|
|
||
|
//------------------------------------ ADD TASK --------------------------------------------
|
||
|
GanttMaster.prototype.addTask = function (task, row) {
|
||
|
//console.debug("master.addTask",task,row,this);
|
||
|
|
||
|
task.master = this; // in order to access controller from task
|
||
|
|
||
|
//replace if already exists
|
||
|
var pos = -1;
|
||
|
for (var i = 0; i < this.tasks.length; i++) {
|
||
|
if (task.id == this.tasks[i].id) {
|
||
|
pos = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pos >= 0) {
|
||
|
this.tasks.splice(pos, 1);
|
||
|
row = parseInt(pos);
|
||
|
}
|
||
|
|
||
|
//add task in collection
|
||
|
if (typeof(row) != "number") {
|
||
|
this.tasks.push(task);
|
||
|
} else {
|
||
|
this.tasks.splice(row, 0, task);
|
||
|
|
||
|
//recompute depends string
|
||
|
this.updateDependsStrings();
|
||
|
}
|
||
|
|
||
|
//add Link collection in memory
|
||
|
var linkLoops = !this.updateLinks(task);
|
||
|
|
||
|
//set the status according to parent
|
||
|
if (task.getParent())
|
||
|
task.status = task.getParent().status;
|
||
|
else
|
||
|
task.status = "STATUS_ACTIVE";
|
||
|
|
||
|
var ret = task;
|
||
|
if (linkLoops || !task.setPeriod(task.start, task.end)) {
|
||
|
//remove task from in-memory collection
|
||
|
//console.debug("removing task from memory",task);
|
||
|
this.tasks.splice(task.getRow(), 1);
|
||
|
ret = undefined;
|
||
|
} else {
|
||
|
//append task to editor
|
||
|
this.editor.addTask(task, row);
|
||
|
//append task to gantt
|
||
|
this.gantt.addTask(task);
|
||
|
}
|
||
|
|
||
|
//trigger addedTask event
|
||
|
$(this.element).trigger("addedTask.gantt", task);
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* a project contais tasks, resources, roles, and info about permisions
|
||
|
* @param project
|
||
|
*/
|
||
|
GanttMaster.prototype.loadProject = function (project) {
|
||
|
//console.debug("loadProject", project)
|
||
|
this.beginTransaction();
|
||
|
this.serverClientTimeOffset = typeof project.serverTimeOffset !="undefined"? (parseInt(project.serverTimeOffset) + new Date().getTimezoneOffset() * 60000) : 0;
|
||
|
this.resources = project.resources;
|
||
|
this.roles = project.roles;
|
||
|
this.material = typeof project.material !="undefined"? project.material : [];
|
||
|
this.tools = typeof project.tools !="undefined"? project.tools : [];
|
||
|
|
||
|
//permissions from loaded project
|
||
|
this.permissions.canWrite = project.canWrite;
|
||
|
this.permissions.canAdd = project.canAdd;
|
||
|
this.permissions.canWriteOnParent = project.canWriteOnParent;
|
||
|
this.permissions.cannotCloseTaskIfIssueOpen = project.cannotCloseTaskIfIssueOpen;
|
||
|
this.permissions.canAddIssue = project.canAddIssue;
|
||
|
this.permissions.canDelete = project.canDelete;
|
||
|
//repaint button bar basing on permissions
|
||
|
this.checkButtonPermissions();
|
||
|
|
||
|
|
||
|
|
||
|
if (project.minEditableDate)
|
||
|
this.minEditableDate = computeStart(project.minEditableDate);
|
||
|
else
|
||
|
this.minEditableDate = -Infinity;
|
||
|
|
||
|
if (project.maxEditableDate)
|
||
|
this.maxEditableDate = computeEnd(project.maxEditableDate);
|
||
|
else
|
||
|
this.maxEditableDate = Infinity;
|
||
|
|
||
|
|
||
|
//recover stored ccollapsed statuas
|
||
|
var collTasks=this.loadCollapsedTasks();
|
||
|
|
||
|
//shift dates in order to have client side the same hour (e.g.: 23:59) of the server side
|
||
|
for (var i = 0; i < project.tasks.length; i++) {
|
||
|
var task = project.tasks[i];
|
||
|
task.start += this.serverClientTimeOffset;
|
||
|
task.end += this.serverClientTimeOffset;
|
||
|
//set initial collapsed status
|
||
|
task.collapsed=collTasks.indexOf(task.id)>=0;
|
||
|
}
|
||
|
|
||
|
|
||
|
this.loadTasks(project.tasks, project.selectedRow);
|
||
|
this.deletedTaskIds = [];
|
||
|
|
||
|
|
||
|
//recover saved zoom level
|
||
|
if (project.zoom){
|
||
|
this.gantt.zoom = project.zoom;
|
||
|
} else {
|
||
|
this.gantt.shrinkBoundaries();
|
||
|
this.gantt.setBestFittingZoom();
|
||
|
}
|
||
|
|
||
|
|
||
|
this.endTransaction();
|
||
|
var self = this;
|
||
|
this.gantt.element.oneTime(200, function () {self.gantt.centerOnToday()});
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.loadTasks = function (tasks, selectedRow) {
|
||
|
//console.debug("GanttMaster.prototype.loadTasks")
|
||
|
//var prof=new Profiler("ganttMaster.loadTasks");
|
||
|
var factory = new TaskFactory();
|
||
|
|
||
|
//reset
|
||
|
this.reset();
|
||
|
|
||
|
for (var i = 0; i < tasks.length; i++) {
|
||
|
var task = tasks[i];
|
||
|
if (!(task instanceof Task)) {
|
||
|
var t = factory.build(task.id, task.name, task.code, task.level, task.start, task.duration, task.collapsed);
|
||
|
for (var key in task) {
|
||
|
if (key != "end" && key != "start")
|
||
|
t[key] = task[key]; //copy all properties
|
||
|
}
|
||
|
task = t;
|
||
|
}
|
||
|
task.master = this; // in order to access controller from task
|
||
|
this.tasks.push(task); //append task at the end
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < this.tasks.length; i++) {
|
||
|
var task = this.tasks[i];
|
||
|
|
||
|
|
||
|
var numOfError = this.__currentTransaction && this.__currentTransaction.errors ? this.__currentTransaction.errors.length : 0;
|
||
|
//add Link collection in memory
|
||
|
while (!this.updateLinks(task)) { // error on update links while loading can be considered as "warning". Can be displayed and removed in order to let transaction commits.
|
||
|
if (this.__currentTransaction && numOfError != this.__currentTransaction.errors.length) {
|
||
|
var msg = "ERROR:\n";
|
||
|
while (numOfError < this.__currentTransaction.errors.length) {
|
||
|
var err = this.__currentTransaction.errors.pop();
|
||
|
msg = msg + err.msg + "\n\n";
|
||
|
}
|
||
|
alert(msg);
|
||
|
}
|
||
|
this.__removeAllLinks(task, false);
|
||
|
}
|
||
|
|
||
|
if (!task.setPeriod(task.start, task.end)) {
|
||
|
alert(GanttMaster.messages.GANNT_ERROR_LOADING_DATA_TASK_REMOVED + "\n" + task.name );
|
||
|
//remove task from in-memory collection
|
||
|
this.tasks.splice(task.getRow(), 1);
|
||
|
} else {
|
||
|
//append task to editor
|
||
|
this.editor.addTask(task, null, true);
|
||
|
//append task to gantt
|
||
|
this.gantt.addTask(task);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//this.editor.fillEmptyLines();
|
||
|
//prof.stop();
|
||
|
|
||
|
// re-select old row if tasks is not empty
|
||
|
if (this.tasks && this.tasks.length > 0) {
|
||
|
selectedRow = selectedRow ? selectedRow : 0;
|
||
|
this.tasks[selectedRow].rowElement.click();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.getTask = function (taskId) {
|
||
|
var ret;
|
||
|
for (var i = 0; i < this.tasks.length; i++) {
|
||
|
var tsk = this.tasks[i];
|
||
|
if (tsk.id == taskId) {
|
||
|
ret = tsk;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.getResource = function (resId) {
|
||
|
var ret;
|
||
|
for (var i = 0; i < this.resources.length; i++) {
|
||
|
var res = this.resources[i];
|
||
|
if (res.id == resId) {
|
||
|
ret = res;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.changeTaskDeps = function (task) {
|
||
|
return task.moveTo(task.start,false,true);
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.changeTaskDates = function (task, start, end) {
|
||
|
//console.debug("changeTaskDates",task, start, end)
|
||
|
return task.setPeriod(start, end);
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.moveTask = function (task, newStart) {
|
||
|
return task.moveTo(newStart, true,true);
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.taskIsChanged = function () {
|
||
|
//console.debug("taskIsChanged");
|
||
|
var master = this;
|
||
|
|
||
|
//refresh is executed only once every 50ms
|
||
|
this.element.stopTime("gnnttaskIsChanged");
|
||
|
//var profilerext = new Profiler("gm_taskIsChangedRequest");
|
||
|
this.element.oneTime(50, "gnnttaskIsChanged", function () {
|
||
|
//console.debug("task Is Changed real call to redraw");
|
||
|
//var profiler = new Profiler("gm_taskIsChangedReal");
|
||
|
master.redraw();
|
||
|
master.element.trigger("gantt.redrawCompleted");
|
||
|
//profiler.stop();
|
||
|
});
|
||
|
//profilerext.stop();
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.checkButtonPermissions = function () {
|
||
|
var ganttButtons=$(".ganttButtonBar");
|
||
|
//hide buttons basing on permissions
|
||
|
if (!this.permissions.canWrite)
|
||
|
ganttButtons.find(".requireCanWrite").hide();
|
||
|
|
||
|
if (!this.permissions.canAdd)
|
||
|
ganttButtons.find(".requireCanAdd").hide();
|
||
|
|
||
|
if (!this.permissions.canInOutdent)
|
||
|
ganttButtons.find(".requireCanInOutdent").hide();
|
||
|
|
||
|
if (!this.permissions.canMoveUpDown)
|
||
|
ganttButtons.find(".requireCanMoveUpDown").hide();
|
||
|
|
||
|
if (!this.permissions.canDelete)
|
||
|
ganttButtons.find(".requireCanDelete").hide();
|
||
|
|
||
|
if (!this.permissions.canSeeCriticalPath)
|
||
|
ganttButtons.find(".requireCanSeeCriticalPath").hide();
|
||
|
|
||
|
if (!this.permissions.canAddIssue)
|
||
|
ganttButtons.find(".requireCanAddIssue").hide();
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.redraw = function () {
|
||
|
this.editor.redraw();
|
||
|
this.gantt.redraw();
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.reset = function () {
|
||
|
//console.debug("GanttMaster.prototype.reset");
|
||
|
this.tasks = [];
|
||
|
this.links = [];
|
||
|
this.deletedTaskIds = [];
|
||
|
if (!this.__inUndoRedo) {
|
||
|
this.__undoStack = [];
|
||
|
this.__redoStack = [];
|
||
|
} else { // don't reset the stacks if we're in an Undo/Redo, but restart the inUndoRedo control
|
||
|
this.__inUndoRedo = false;
|
||
|
}
|
||
|
delete this.currentTask;
|
||
|
|
||
|
this.editor.reset();
|
||
|
this.gantt.reset();
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.showTaskEditor = function (taskId) {
|
||
|
var task = this.getTask(taskId);
|
||
|
task.rowElement.find(".edit").click();
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.saveProject = function () {
|
||
|
return this.saveGantt(false);
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.saveGantt = function (forTransaction) {
|
||
|
//var prof = new Profiler("gm_saveGantt");
|
||
|
var saved = [];
|
||
|
for (var i = 0; i < this.tasks.length; i++) {
|
||
|
var task = this.tasks[i];
|
||
|
var cloned = task.clone();
|
||
|
|
||
|
//shift back to server side timezone
|
||
|
if (!forTransaction) {
|
||
|
cloned.start -= this.serverClientTimeOffset;
|
||
|
cloned.end -= this.serverClientTimeOffset;
|
||
|
}
|
||
|
|
||
|
saved.push(cloned);
|
||
|
}
|
||
|
|
||
|
var ret = {tasks: saved};
|
||
|
if (this.currentTask) {
|
||
|
ret.selectedRow = this.currentTask.getRow();
|
||
|
}
|
||
|
|
||
|
ret.deletedTaskIds = this.deletedTaskIds; //this must be consistent with transactions and undo
|
||
|
|
||
|
if (!forTransaction) {
|
||
|
ret.resources = this.resources;
|
||
|
ret.roles = this.roles;
|
||
|
ret.canAdd = this.permissions.canAdd;
|
||
|
ret.canWrite = this.permissions.canWrite;
|
||
|
ret.canWriteOnParent = this.permissions.canWriteOnParent;
|
||
|
ret.zoom = this.gantt.zoom;
|
||
|
ret.material = this.material;
|
||
|
ret.tools = this.tools;
|
||
|
|
||
|
//save collapsed tasks on localStorage
|
||
|
this.storeCollapsedTasks();
|
||
|
|
||
|
//mark un-changed task and assignments
|
||
|
this.markUnChangedTasksAndAssignments(ret);
|
||
|
|
||
|
//si aggiunge il commento al cambiamento di date/status
|
||
|
ret.changesReasonWhy=$("#LOG_CHANGES").val();
|
||
|
|
||
|
}
|
||
|
|
||
|
//prof.stop();
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.markUnChangedTasksAndAssignments=function(newProject){
|
||
|
//console.debug("markUnChangedTasksAndAssignments");
|
||
|
//si controlla che ci sia qualcosa di cambiato, ovvero che ci sia l'undo stack
|
||
|
if (this.__undoStack.length>0){
|
||
|
var oldProject=JSON.parse(this.__undoStack[0]);
|
||
|
//si looppano i "nuovi" task
|
||
|
for (var i=0;i<newProject.tasks.length;i++){
|
||
|
var newTask=newProject.tasks[i];
|
||
|
//se è un task che c'erà già
|
||
|
if (typeof (newTask.id)=="string" && !newTask.id.startsWith("tmp_")){
|
||
|
//si recupera il vecchio task
|
||
|
var oldTask;
|
||
|
for (var j=0;j<oldProject.tasks.length;j++){
|
||
|
if (oldProject.tasks[j].id==newTask.id){
|
||
|
oldTask=oldProject.tasks[j];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//si controlla se ci sono stati cambiamenti
|
||
|
var taskChanged=
|
||
|
oldTask.id != newTask.id ||
|
||
|
oldTask.code != newTask.code ||
|
||
|
oldTask.name != newTask.name ||
|
||
|
oldTask.start != newTask.start ||
|
||
|
oldTask.startIsMilestone != newTask.startIsMilestone ||
|
||
|
oldTask.end != newTask.end ||
|
||
|
oldTask.endIsMilestone != newTask.endIsMilestone ||
|
||
|
oldTask.duration != newTask.duration ||
|
||
|
oldTask.status != newTask.status ||
|
||
|
oldTask.typeId != newTask.typeId ||
|
||
|
oldTask.relevance != newTask.relevance ||
|
||
|
oldTask.progress != newTask.progress ||
|
||
|
oldTask.progressByWorklog != newTask.progressByWorklog ||
|
||
|
oldTask.description != newTask.description ||
|
||
|
oldTask.level != newTask.level||
|
||
|
oldTask.depends != newTask.depends;
|
||
|
|
||
|
newTask.unchanged=!taskChanged;
|
||
|
|
||
|
|
||
|
//se ci sono assegnazioni
|
||
|
if (newTask.assigs&&newTask.assigs.length>0){
|
||
|
|
||
|
//se abbiamo trovato il vecchio task e questo aveva delle assegnazioni
|
||
|
if (oldTask && oldTask.assigs && oldTask.assigs.length>0){
|
||
|
for (var j=0;j<oldTask.assigs.length;j++){
|
||
|
var oldAssig=oldTask.assigs[j];
|
||
|
//si cerca la nuova assegnazione corrispondente
|
||
|
var newAssig;
|
||
|
for (var k=0;k<newTask.assigs.length;k++){
|
||
|
if(oldAssig.id==newTask.assigs[k].id){
|
||
|
newAssig=newTask.assigs[k];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//se c'è una nuova assig corrispondente
|
||
|
if(newAssig){
|
||
|
//si confrontano i valori per vedere se è cambiata
|
||
|
newAssig.unchanged=
|
||
|
newAssig.resourceId==oldAssig.resourceId &&
|
||
|
newAssig.roleId==oldAssig.roleId &&
|
||
|
newAssig.effort==oldAssig.effort;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.loadCollapsedTasks = function () {
|
||
|
var collTasks=[];
|
||
|
if (localStorage ) {
|
||
|
if (localStorage.getObject("TWPGanttCollTasks"))
|
||
|
collTasks = localStorage.getObject("TWPGanttCollTasks");
|
||
|
return collTasks;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.storeCollapsedTasks = function () {
|
||
|
//console.debug("storeCollapsedTasks");
|
||
|
if (localStorage) {
|
||
|
var collTasks;
|
||
|
if (!localStorage.getObject("TWPGanttCollTasks"))
|
||
|
collTasks = [];
|
||
|
else
|
||
|
collTasks = localStorage.getObject("TWPGanttCollTasks");
|
||
|
|
||
|
|
||
|
for (var i = 0; i < this.tasks.length; i++) {
|
||
|
var task = this.tasks[i];
|
||
|
|
||
|
var pos=collTasks.indexOf(task.id);
|
||
|
if (task.collapsed){
|
||
|
if (pos<0)
|
||
|
collTasks.push(task.id);
|
||
|
} else {
|
||
|
if (pos>=0)
|
||
|
collTasks.splice(pos,1);
|
||
|
}
|
||
|
}
|
||
|
localStorage.setObject("TWPGanttCollTasks", collTasks);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.updateLinks = function (task) {
|
||
|
//console.debug("updateLinks",task);
|
||
|
//var prof= new Profiler("gm_updateLinks");
|
||
|
|
||
|
// defines isLoop function
|
||
|
function isLoop(task, target, visited) {
|
||
|
//var prof= new Profiler("gm_isLoop");
|
||
|
//console.debug("isLoop :"+task.name+" - "+target.name);
|
||
|
if (target == task) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
var sups = task.getSuperiors();
|
||
|
|
||
|
//my parent' superiors are my superiors too
|
||
|
var p = task.getParent();
|
||
|
while (p) {
|
||
|
sups = sups.concat(p.getSuperiors());
|
||
|
p = p.getParent();
|
||
|
}
|
||
|
|
||
|
//my children superiors are my superiors too
|
||
|
var chs = task.getChildren();
|
||
|
for (var i = 0; i < chs.length; i++) {
|
||
|
sups = sups.concat(chs[i].getSuperiors());
|
||
|
}
|
||
|
|
||
|
var loop = false;
|
||
|
//check superiors
|
||
|
for (var i = 0; i < sups.length; i++) {
|
||
|
var supLink = sups[i];
|
||
|
if (supLink.from == target) {
|
||
|
loop = true;
|
||
|
break;
|
||
|
} else {
|
||
|
if (visited.indexOf(supLink.from.id + "x" + target.id) <= 0) {
|
||
|
visited.push(supLink.from.id + "x" + target.id);
|
||
|
if (isLoop(supLink.from, target, visited)) {
|
||
|
loop = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//check target parent
|
||
|
var tpar = target.getParent();
|
||
|
if (tpar) {
|
||
|
if (visited.indexOf(task.id + "x" + tpar.id) <= 0) {
|
||
|
visited.push(task.id + "x" + tpar.id);
|
||
|
if (isLoop(task, tpar, visited)) {
|
||
|
loop = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//prof.stop();
|
||
|
return loop;
|
||
|
}
|
||
|
|
||
|
//remove my depends
|
||
|
this.links = this.links.filter(function (link) {
|
||
|
return link.to != task;
|
||
|
});
|
||
|
|
||
|
var todoOk = true;
|
||
|
if (task.depends) {
|
||
|
|
||
|
//cannot depend from an ancestor
|
||
|
var parents = task.getParents();
|
||
|
//cannot depend from descendants
|
||
|
var descendants = task.getDescendant();
|
||
|
|
||
|
var deps = task.depends.split(",");
|
||
|
var newDepsString = "";
|
||
|
|
||
|
var visited = [];
|
||
|
var depsEqualCheck = [];
|
||
|
for (var j = 0; j < deps.length; j++) {
|
||
|
var depString = deps[j]; // in the form of row(lag) e.g. 2:3,3:4,5
|
||
|
var supStr =depString;
|
||
|
var lag = 0;
|
||
|
var pos = depString.indexOf(":");
|
||
|
if (pos>0){
|
||
|
supStr=depString.substr(0,pos);
|
||
|
var lagStr=depString.substr(pos+1);
|
||
|
lag=Math.ceil((stringToDuration(lagStr)) / Date.workingPeriodResolution) * Date.workingPeriodResolution;
|
||
|
}
|
||
|
|
||
|
var sup = this.tasks[parseInt(supStr)-1];
|
||
|
|
||
|
if (sup) {
|
||
|
if (parents && parents.indexOf(sup) >= 0) {
|
||
|
this.setErrorOnTransaction("\""+task.name + "\"\n" + GanttMaster.messages.CANNOT_DEPENDS_ON_ANCESTORS + "\n\"" + sup.name+"\"");
|
||
|
todoOk = false;
|
||
|
|
||
|
} else if (descendants && descendants.indexOf(sup) >= 0) {
|
||
|
this.setErrorOnTransaction("\""+task.name + "\"\n" + GanttMaster.messages.CANNOT_DEPENDS_ON_DESCENDANTS + "\n\"" + sup.name+"\"");
|
||
|
todoOk = false;
|
||
|
|
||
|
} else if (isLoop(sup, task, visited)) {
|
||
|
todoOk = false;
|
||
|
this.setErrorOnTransaction(GanttMaster.messages.CIRCULAR_REFERENCE + "\n\"" + task.id +" - "+ task.name + "\" -> \"" + sup.id +" - "+sup.name+"\"");
|
||
|
|
||
|
} else if(depsEqualCheck.indexOf(sup)>=0) {
|
||
|
this.setErrorOnTransaction(GanttMaster.messages.CANNOT_CREATE_SAME_LINK + "\n\"" + sup.name+"\" -> \""+task.name+"\"");
|
||
|
todoOk = false;
|
||
|
|
||
|
} else {
|
||
|
this.links.push(new Link(sup, task, lag));
|
||
|
newDepsString = newDepsString + (newDepsString.length > 0 ? "," : "") + supStr+(lag==0?"":":"+durationToString(lag));
|
||
|
}
|
||
|
|
||
|
if (todoOk)
|
||
|
depsEqualCheck.push(sup);
|
||
|
}
|
||
|
}
|
||
|
task.depends = newDepsString;
|
||
|
}
|
||
|
//prof.stop();
|
||
|
|
||
|
return todoOk;
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.moveUpCurrentTask = function () {
|
||
|
var self = this;
|
||
|
//console.debug("moveUpCurrentTask",self.currentTask)
|
||
|
if (self.currentTask) {
|
||
|
if (!(self.permissions.canWrite || self.currentTask.canWrite) || !self.permissions.canMoveUpDown )
|
||
|
return;
|
||
|
|
||
|
self.beginTransaction();
|
||
|
self.currentTask.moveUp();
|
||
|
self.endTransaction();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.moveDownCurrentTask = function () {
|
||
|
var self = this;
|
||
|
//console.debug("moveDownCurrentTask",self.currentTask)
|
||
|
if (self.currentTask) {
|
||
|
if (!(self.permissions.canWrite || self.currentTask.canWrite) || !self.permissions.canMoveUpDown )
|
||
|
return;
|
||
|
|
||
|
self.beginTransaction();
|
||
|
self.currentTask.moveDown();
|
||
|
self.endTransaction();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.outdentCurrentTask = function () {
|
||
|
var self = this;
|
||
|
if (self.currentTask) {
|
||
|
var par = self.currentTask.getParent();
|
||
|
//can outdent if you have canRight on current task and on its parent and canAdd on grandfather
|
||
|
if (!self.currentTask.canWrite || !par.canWrite || !par.getParent() || !par.getParent().canAdd)
|
||
|
return;
|
||
|
|
||
|
self.beginTransaction();
|
||
|
self.currentTask.outdent();
|
||
|
self.endTransaction();
|
||
|
|
||
|
//[expand]
|
||
|
if (par) self.editor.refreshExpandStatus(par);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.indentCurrentTask = function () {
|
||
|
var self = this;
|
||
|
if (self.currentTask) {
|
||
|
|
||
|
//can indent if you have canRight on current and canAdd on the row above
|
||
|
var row = self.currentTask.getRow();
|
||
|
if (!self.currentTask.canWrite || row <= 0 || !self.tasks[row - 1].canAdd)
|
||
|
return;
|
||
|
|
||
|
self.beginTransaction();
|
||
|
self.currentTask.indent();
|
||
|
self.endTransaction();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.addBelowCurrentTask = function () {
|
||
|
var self = this;
|
||
|
//console.debug("addBelowCurrentTask",self.currentTask)
|
||
|
var factory = new TaskFactory();
|
||
|
var ch;
|
||
|
var row = 0;
|
||
|
if (self.currentTask && self.currentTask.name) {
|
||
|
//add below add a brother if current task is not already a parent
|
||
|
var addNewBrother = !(self.currentTask.isParent() || self.currentTask.level==0);
|
||
|
|
||
|
var canAddChild=self.currentTask.canAdd;
|
||
|
var canAddBrother=self.currentTask.getParent() && self.currentTask.getParent().canAdd;
|
||
|
|
||
|
//if you cannot add a brother you will try to add a child
|
||
|
addNewBrother=addNewBrother&&canAddBrother;
|
||
|
|
||
|
if (!canAddBrother && !canAddChild)
|
||
|
return;
|
||
|
|
||
|
|
||
|
ch = factory.build("tmp_" + new Date().getTime(), "", "", self.currentTask.level+ (addNewBrother ?0:1), self.currentTask.start, 1);
|
||
|
row = self.currentTask.getRow() + 1;
|
||
|
|
||
|
if (row>0) {
|
||
|
self.beginTransaction();
|
||
|
var task = self.addTask(ch, row);
|
||
|
if (task) {
|
||
|
task.rowElement.click();
|
||
|
task.rowElement.find("[name=name]").focus();
|
||
|
}
|
||
|
self.endTransaction();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.addAboveCurrentTask = function () {
|
||
|
var self = this;
|
||
|
// console.debug("addAboveCurrentTask",self.currentTask)
|
||
|
|
||
|
//check permissions
|
||
|
if ((self.currentTask.getParent() && !self.currentTask.getParent().canAdd) )
|
||
|
return;
|
||
|
|
||
|
var factory = new TaskFactory();
|
||
|
|
||
|
var ch;
|
||
|
var row = 0;
|
||
|
if (self.currentTask && self.currentTask.name) {
|
||
|
//cannot add brothers to root
|
||
|
if (self.currentTask.level <= 0)
|
||
|
return;
|
||
|
|
||
|
ch = factory.build("tmp_" + new Date().getTime(), "", "", self.currentTask.level, self.currentTask.start, 1);
|
||
|
row = self.currentTask.getRow();
|
||
|
|
||
|
if (row > 0) {
|
||
|
self.beginTransaction();
|
||
|
var task = self.addTask(ch, row);
|
||
|
if (task) {
|
||
|
task.rowElement.click();
|
||
|
task.rowElement.find("[name=name]").focus();
|
||
|
}
|
||
|
self.endTransaction();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.deleteCurrentTask = function (taskId) {
|
||
|
//console.debug("deleteCurrentTask",this.currentTask , this.isMultiRoot)
|
||
|
var self = this;
|
||
|
|
||
|
var task;
|
||
|
if (taskId)
|
||
|
task=self.getTask(taskId);
|
||
|
else
|
||
|
task=self.currentTask;
|
||
|
|
||
|
if (!task || !self.permissions.canDelete && !task.canDelete)
|
||
|
return;
|
||
|
|
||
|
var taskIsEmpty=task.name=="";
|
||
|
|
||
|
var row = task.getRow();
|
||
|
if (task && (row > 0 || self.isMultiRoot || task.isNew()) ) {
|
||
|
var par = task.getParent();
|
||
|
self.beginTransaction();
|
||
|
task.deleteTask();
|
||
|
task = undefined;
|
||
|
|
||
|
//recompute depends string
|
||
|
self.updateDependsStrings();
|
||
|
|
||
|
//redraw
|
||
|
self.taskIsChanged();
|
||
|
|
||
|
//[expand]
|
||
|
if (par)
|
||
|
self.editor.refreshExpandStatus(par);
|
||
|
|
||
|
|
||
|
//focus next row
|
||
|
row = row > self.tasks.length - 1 ? self.tasks.length - 1 : row;
|
||
|
if (!taskIsEmpty && row >= 0) {
|
||
|
task = self.tasks[row];
|
||
|
task.rowElement.click();
|
||
|
task.rowElement.find("[name=name]").focus();
|
||
|
}
|
||
|
self.endTransaction();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.collapseAll = function () {
|
||
|
//console.debug("collapseAll");
|
||
|
if (this.currentTask){
|
||
|
this.currentTask.collapsed=true;
|
||
|
var desc = this.currentTask.getDescendant();
|
||
|
for (var i=0; i<desc.length; i++) {
|
||
|
if (desc[i].isParent()) // set collapsed only if is a parent
|
||
|
desc[i].collapsed = true;
|
||
|
desc[i].rowElement.hide();
|
||
|
}
|
||
|
|
||
|
this.redraw();
|
||
|
|
||
|
//store collapse statuses
|
||
|
this.storeCollapsedTasks();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.fullScreen = function () {
|
||
|
//console.debug("fullScreen");
|
||
|
this.workSpace.toggleClass("ganttFullScreen").resize();
|
||
|
$("#fullscrbtn .teamworkIcon").html(this.workSpace.is(".ganttFullScreen")?"€":"@");
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.expandAll = function () {
|
||
|
//console.debug("expandAll");
|
||
|
if (this.currentTask){
|
||
|
this.currentTask.collapsed=false;
|
||
|
var desc = this.currentTask.getDescendant();
|
||
|
for (var i=0; i<desc.length; i++) {
|
||
|
desc[i].collapsed = false;
|
||
|
desc[i].rowElement.show();
|
||
|
}
|
||
|
|
||
|
this.redraw();
|
||
|
|
||
|
//store collapse statuses
|
||
|
this.storeCollapsedTasks();
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.collapse = function (task, all) {
|
||
|
//console.debug("collapse",task)
|
||
|
task.collapsed=true;
|
||
|
task.rowElement.addClass("collapsed");
|
||
|
|
||
|
var descs = task.getDescendant();
|
||
|
for (var i = 0; i < descs.length; i++)
|
||
|
descs[i].rowElement.hide();
|
||
|
|
||
|
this.redraw();
|
||
|
|
||
|
//store collapse statuses
|
||
|
this.storeCollapsedTasks();
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.expand = function (task,all) {
|
||
|
//console.debug("expand",task)
|
||
|
task.collapsed=false;
|
||
|
task.rowElement.removeClass("collapsed");
|
||
|
|
||
|
var collapsedDescendant = this.getCollapsedDescendant();
|
||
|
var descs = task.getDescendant();
|
||
|
for (var i = 0; i < descs.length; i++) {
|
||
|
var childTask = descs[i];
|
||
|
if (collapsedDescendant.indexOf(childTask) >= 0) continue;
|
||
|
childTask.rowElement.show();
|
||
|
}
|
||
|
|
||
|
this.redraw();
|
||
|
|
||
|
//store collapse statuses
|
||
|
this.storeCollapsedTasks();
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.getCollapsedDescendant = function () {
|
||
|
var allTasks = this.tasks;
|
||
|
var collapsedDescendant = [];
|
||
|
for (var i = 0; i < allTasks.length; i++) {
|
||
|
var task = allTasks[i];
|
||
|
if (collapsedDescendant.indexOf(task) >= 0) continue;
|
||
|
if (task.collapsed) collapsedDescendant = collapsedDescendant.concat(task.getDescendant());
|
||
|
}
|
||
|
return collapsedDescendant;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.addIssue = function () {
|
||
|
var self = this;
|
||
|
|
||
|
if (self.currentTask && self.currentTask.isNew()){
|
||
|
alert(GanttMaster.messages.PLEASE_SAVE_PROJECT);
|
||
|
return;
|
||
|
}
|
||
|
if (!self.currentTask || !self.currentTask.canAddIssue)
|
||
|
return;
|
||
|
|
||
|
openIssueEditorInBlack('0',"AD","ISSUE_TASK="+self.currentTask.id);
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.openExternalEditor = function () {
|
||
|
//console.debug("openExternalEditor ")
|
||
|
var self = this;
|
||
|
if (!self.currentTask)
|
||
|
return;
|
||
|
|
||
|
if (self.currentTask.isNew()){
|
||
|
alert(GanttMaster.messages.PLEASE_SAVE_PROJECT);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//window.location.href=contextPath+"/applications/teamwork/task/taskEditor.jsp?CM=ED&OBJID="+self.currentTask.id;
|
||
|
};
|
||
|
|
||
|
//<%----------------------------- TRANSACTION MANAGEMENT ---------------------------------%>
|
||
|
GanttMaster.prototype.beginTransaction = function () {
|
||
|
if (!this.__currentTransaction) {
|
||
|
this.__currentTransaction = {
|
||
|
snapshot: JSON.stringify(this.saveGantt(true)),
|
||
|
errors: []
|
||
|
};
|
||
|
} else {
|
||
|
console.error("Cannot open twice a transaction");
|
||
|
}
|
||
|
return this.__currentTransaction;
|
||
|
};
|
||
|
|
||
|
|
||
|
//this function notify an error to a transaction -> transaction will rollback
|
||
|
GanttMaster.prototype.setErrorOnTransaction = function (errorMessage, task) {
|
||
|
if (this.__currentTransaction) {
|
||
|
this.__currentTransaction.errors.push({msg: errorMessage, task: task});
|
||
|
} else {
|
||
|
console.error(errorMessage);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.isTransactionInError = function () {
|
||
|
if (!this.__currentTransaction) {
|
||
|
console.error("Transaction never started.");
|
||
|
return true;
|
||
|
} else {
|
||
|
return this.__currentTransaction.errors.length > 0
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.endTransaction = function () {
|
||
|
if (!this.__currentTransaction) {
|
||
|
console.error("Transaction never started.");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
var ret = true;
|
||
|
|
||
|
//no error -> commit
|
||
|
if (this.__currentTransaction.errors.length <= 0) {
|
||
|
//console.debug("committing transaction");
|
||
|
|
||
|
//put snapshot in undo
|
||
|
this.__undoStack.push(this.__currentTransaction.snapshot);
|
||
|
//clear redo stack
|
||
|
this.__redoStack = [];
|
||
|
|
||
|
//shrink gantt bundaries
|
||
|
this.gantt.shrinkBoundaries();
|
||
|
this.taskIsChanged(); //enqueue for gantt refresh
|
||
|
|
||
|
|
||
|
//error -> rollback
|
||
|
} else {
|
||
|
ret = false;
|
||
|
//console.debug("rolling-back transaction");
|
||
|
|
||
|
//compose error message
|
||
|
var msg = "ERROR:\n";
|
||
|
for (var i = 0; i < this.__currentTransaction.errors.length; i++) {
|
||
|
var err = this.__currentTransaction.errors[i];
|
||
|
msg = msg + err.msg + "\n\n";
|
||
|
}
|
||
|
alert(msg);
|
||
|
|
||
|
|
||
|
//try to restore changed tasks
|
||
|
var oldTasks = JSON.parse(this.__currentTransaction.snapshot);
|
||
|
this.deletedTaskIds = oldTasks.deletedTaskIds;
|
||
|
this.__inUndoRedo = true; // avoid Undo/Redo stacks reset
|
||
|
this.loadTasks(oldTasks.tasks, oldTasks.selectedRow);
|
||
|
this.redraw();
|
||
|
|
||
|
}
|
||
|
//reset transaction
|
||
|
this.__currentTransaction = undefined;
|
||
|
|
||
|
//show/hide save button
|
||
|
this.saveRequired();
|
||
|
|
||
|
//[expand]
|
||
|
this.editor.refreshExpandStatus(this.currentTask);
|
||
|
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
// inhibit undo-redo
|
||
|
GanttMaster.prototype.checkpoint = function () {
|
||
|
//console.debug("GanttMaster.prototype.checkpoint");
|
||
|
this.__undoStack = [];
|
||
|
this.__redoStack = [];
|
||
|
this.saveRequired();
|
||
|
};
|
||
|
|
||
|
//----------------------------- UNDO/REDO MANAGEMENT ---------------------------------%>
|
||
|
|
||
|
GanttMaster.prototype.undo = function () {
|
||
|
//console.debug("undo before:",this.__undoStack,this.__redoStack);
|
||
|
if (this.__undoStack.length > 0) {
|
||
|
var his = this.__undoStack.pop();
|
||
|
this.__redoStack.push(JSON.stringify(this.saveGantt()));
|
||
|
var oldTasks = JSON.parse(his);
|
||
|
this.deletedTaskIds = oldTasks.deletedTaskIds;
|
||
|
this.__inUndoRedo = true; // avoid Undo/Redo stacks reset
|
||
|
this.loadTasks(oldTasks.tasks, oldTasks.selectedRow);
|
||
|
this.redraw();
|
||
|
//show/hide save button
|
||
|
this.saveRequired();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
GanttMaster.prototype.redo = function () {
|
||
|
//console.debug("redo before:",undoStack,redoStack);
|
||
|
if (this.__redoStack.length > 0) {
|
||
|
var his = this.__redoStack.pop();
|
||
|
this.__undoStack.push(JSON.stringify(this.saveGantt()));
|
||
|
var oldTasks = JSON.parse(his);
|
||
|
this.deletedTaskIds = oldTasks.deletedTaskIds;
|
||
|
this.__inUndoRedo = true; // avoid Undo/Redo stacks reset
|
||
|
this.loadTasks(oldTasks.tasks, oldTasks.selectedRow);
|
||
|
this.redraw();
|
||
|
//console.debug("redo after:",undoStack,redoStack);
|
||
|
|
||
|
this.saveRequired();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.saveRequired = function () {
|
||
|
//console.debug("saveRequired")
|
||
|
//show/hide save button
|
||
|
if(this.__undoStack.length>0 ) {
|
||
|
$("#saveGanttButton").removeClass("disabled");
|
||
|
$("form[alertOnChange] #Gantt").val(new Date().getTime()); // set a fake variable as dirty
|
||
|
this.element.trigger("saveRequired.gantt",[true]);
|
||
|
|
||
|
|
||
|
} else {
|
||
|
$("#saveGanttButton").addClass("disabled");
|
||
|
$("form[alertOnChange] #Gantt").updateOldValue(); // set a fake variable as clean
|
||
|
this.element.trigger("saveRequired.gantt",[false]);
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.print = function () {
|
||
|
this.gantt.redrawTasks(true);
|
||
|
print();
|
||
|
};
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.resize = function () {
|
||
|
var self=this;
|
||
|
//console.debug("GanttMaster.resize")
|
||
|
this.element.stopTime("resizeRedraw").oneTime(50,"resizeRedraw",function(){
|
||
|
self.splitter.resize();
|
||
|
self.numOfVisibleRows=Math.ceil(self.element.height()/self.rowHeight);
|
||
|
self.firstScreenLine=Math.floor(self.splitter.firstBox.scrollTop()/self.rowHeight) ;
|
||
|
self.gantt.redrawTasks();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
GanttMaster.prototype.scrolled = function (oldFirstRow) {
|
||
|
var self=this;
|
||
|
var newFirstRow=self.firstScreenLine;
|
||
|
|
||
|
//if scroll something
|
||
|
if (newFirstRow!=oldFirstRow){
|
||
|
//console.debug("Ganttalendar.scrolled oldFirstRow:"+oldFirstRow+" new firstScreenLine:"+newFirstRow);
|
||
|
|
||
|
var collapsedDescendant = self.getCollapsedDescendant();
|
||
|
|
||
|
var scrollDown=newFirstRow>oldFirstRow;
|
||
|
var startRowDel;
|
||
|
var endRowDel;
|
||
|
var startRowAdd;
|
||
|
var endRowAdd;
|
||
|
|
||
|
if(scrollDown){
|
||
|
startRowDel=oldFirstRow-self.rowBufferSize;
|
||
|
endRowDel=newFirstRow-self.rowBufferSize;
|
||
|
startRowAdd=Math.max(oldFirstRow+self.numOfVisibleRows+self.rowBufferSize,endRowDel);
|
||
|
endRowAdd =newFirstRow+self.numOfVisibleRows+self.rowBufferSize;
|
||
|
} else {
|
||
|
startRowDel=newFirstRow+self.numOfVisibleRows+self.rowBufferSize;
|
||
|
endRowDel=oldFirstRow+self.numOfVisibleRows+self.rowBufferSize;
|
||
|
startRowAdd=newFirstRow-self.rowBufferSize;
|
||
|
endRowAdd =Math.min(oldFirstRow-self.rowBufferSize,startRowDel);
|
||
|
}
|
||
|
|
||
|
var firstVisibleRow=newFirstRow-self.rowBufferSize; //ignoring collapsed tasks
|
||
|
var lastVisibleRow =newFirstRow+self.numOfVisibleRows+self.rowBufferSize;
|
||
|
|
||
|
|
||
|
//console.debug("remove startRowDel:"+startRowDel+" endRowDel:"+endRowDel )
|
||
|
//console.debug("add startRowAdd:"+startRowAdd+" endRowAdd:"+endRowAdd)
|
||
|
|
||
|
var row=0;
|
||
|
self.firstVisibleTaskIndex=-1;
|
||
|
for (var i=0;i<self.tasks.length;i++){
|
||
|
var task=self.tasks[i];
|
||
|
if (collapsedDescendant.indexOf(task) >=0){
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//remove rows on top
|
||
|
if (row>=startRowDel && row<endRowDel) {
|
||
|
if (task.ganttElement)
|
||
|
task.ganttElement.remove();
|
||
|
if (task.ganttBaselineElement)
|
||
|
task.ganttBaselineElement.remove();
|
||
|
|
||
|
//add missing ones
|
||
|
} else if (row>=startRowAdd && row<endRowAdd) {
|
||
|
self.gantt.drawTask(task);
|
||
|
}
|
||
|
|
||
|
if (row>=firstVisibleRow && row<lastVisibleRow) {
|
||
|
self.firstVisibleTaskIndex=self.firstVisibleTaskIndex==-1?i:self.firstVisibleTaskIndex;
|
||
|
self.lastVisibleTaskIndex = i;
|
||
|
}
|
||
|
|
||
|
row++
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Compute the critical path using Backflow algorithm.
|
||
|
* Translated from Java code supplied by M. Jessup here http://stackoverflow.com/questions/2985317/critical-path-method-algorithm
|
||
|
*
|
||
|
* For each task computes:
|
||
|
* earlyStart, earlyFinish, latestStart, latestFinish, criticalCost
|
||
|
*
|
||
|
* A task on the critical path has isCritical=true
|
||
|
* A task not in critical path can float by latestStart-earlyStart days
|
||
|
*
|
||
|
* If you use critical path avoid usage of dependencies between different levels of tasks
|
||
|
*
|
||
|
* WARNNG: It ignore milestones!!!!
|
||
|
* @return {*}
|
||
|
*/
|
||
|
GanttMaster.prototype.computeCriticalPath = function () {
|
||
|
|
||
|
if (!this.tasks)
|
||
|
return false;
|
||
|
|
||
|
// do not consider grouping tasks
|
||
|
var tasks = this.tasks.filter(function (t) {
|
||
|
//return !t.isParent()
|
||
|
return (t.getRow() > 0) && (!t.isParent() || (t.isParent() && !t.isDependent()));
|
||
|
});
|
||
|
|
||
|
// reset values
|
||
|
for (var i = 0; i < tasks.length; i++) {
|
||
|
var t = tasks[i];
|
||
|
t.earlyStart = -1;
|
||
|
t.earlyFinish = -1;
|
||
|
t.latestStart = -1;
|
||
|
t.latestFinish = -1;
|
||
|
t.criticalCost = -1;
|
||
|
t.isCritical = false;
|
||
|
}
|
||
|
|
||
|
// tasks whose critical cost has been calculated
|
||
|
var completed = [];
|
||
|
// tasks whose critical cost needs to be calculated
|
||
|
var remaining = tasks.concat(); // put all tasks in remaining
|
||
|
|
||
|
|
||
|
// Backflow algorithm
|
||
|
// while there are tasks whose critical cost isn't calculated.
|
||
|
while (remaining.length > 0) {
|
||
|
var progress = false;
|
||
|
|
||
|
// find a new task to calculate
|
||
|
for (var i = 0; i < remaining.length; i++) {
|
||
|
var task = remaining[i];
|
||
|
var inferiorTasks = task.getInferiorTasks();
|
||
|
|
||
|
if (containsAll(completed, inferiorTasks)) {
|
||
|
// all dependencies calculated, critical cost is max dependency critical cost, plus our cost
|
||
|
var critical = 0;
|
||
|
for (var j = 0; j < inferiorTasks.length; j++) {
|
||
|
var t = inferiorTasks[j];
|
||
|
if (t.criticalCost > critical) {
|
||
|
critical = t.criticalCost;
|
||
|
}
|
||
|
}
|
||
|
task.criticalCost = critical + task.duration;
|
||
|
// set task as calculated an remove
|
||
|
completed.push(task);
|
||
|
remaining.splice(i, 1);
|
||
|
|
||
|
// note we are making progress
|
||
|
progress = true;
|
||
|
}
|
||
|
}
|
||
|
// If we haven't made any progress then a cycle must exist in
|
||
|
// the graph and we wont be able to calculate the critical path
|
||
|
if (!progress) {
|
||
|
console.error("Cyclic dependency, algorithm stopped!");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set earlyStart, earlyFinish, latestStart, latestFinish
|
||
|
computeMaxCost(tasks);
|
||
|
var initialNodes = initials(tasks);
|
||
|
calculateEarly(initialNodes);
|
||
|
calculateCritical(tasks);
|
||
|
|
||
|
return tasks;
|
||
|
|
||
|
|
||
|
function containsAll(set, targets) {
|
||
|
for (var i = 0; i < targets.length; i++) {
|
||
|
if (set.indexOf(targets[i]) < 0)
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function computeMaxCost(tasks) {
|
||
|
var max = -1;
|
||
|
for (var i = 0; i < tasks.length; i++) {
|
||
|
var t = tasks[i];
|
||
|
|
||
|
if (t.criticalCost > max)
|
||
|
max = t.criticalCost;
|
||
|
}
|
||
|
//console.debug("Critical path length (cost): " + max);
|
||
|
for (var i = 0; i < tasks.length; i++) {
|
||
|
var t = tasks[i];
|
||
|
t.setLatest(max);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function initials(tasks) {
|
||
|
var initials = [];
|
||
|
for (var i = 0; i < tasks.length; i++) {
|
||
|
if (!tasks[i].depends || tasks[i].depends == "")
|
||
|
initials.push(tasks[i]);
|
||
|
}
|
||
|
return initials;
|
||
|
}
|
||
|
|
||
|
function calculateEarly(initials) {
|
||
|
for (var i = 0; i < initials.length; i++) {
|
||
|
var initial = initials[i];
|
||
|
initial.earlyStart = 0;
|
||
|
initial.earlyFinish = initial.duration;
|
||
|
setEarly(initial);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setEarly(initial) {
|
||
|
var completionTime = initial.earlyFinish;
|
||
|
var inferiorTasks = initial.getInferiorTasks();
|
||
|
for (var i = 0; i < inferiorTasks.length; i++) {
|
||
|
var t = inferiorTasks[i];
|
||
|
if (completionTime >= t.earlyStart) {
|
||
|
t.earlyStart = completionTime;
|
||
|
t.earlyFinish = completionTime + t.duration;
|
||
|
}
|
||
|
setEarly(t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function calculateCritical(tasks) {
|
||
|
for (var i = 0; i < tasks.length; i++) {
|
||
|
var t = tasks[i];
|
||
|
t.isCritical = (t.earlyStart == t.latestStart)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
//------------------------------------------- MANAGE CHANGE LOG INPUT ---------------------------------------------------
|
||
|
GanttMaster.prototype.manageSaveRequired=function(ev, showSave) {
|
||
|
//console.debug("manageSaveRequired", showSave);
|
||
|
|
||
|
var self=this;
|
||
|
function checkChanges() {
|
||
|
var changes = false;
|
||
|
//there is somethin in the redo stack?
|
||
|
if (self.__undoStack.length > 0) {
|
||
|
var oldProject = JSON.parse(self.__undoStack[0]);
|
||
|
//si looppano i "nuovi" task
|
||
|
for (var i = 0; !changes && i < self.tasks.length; i++) {
|
||
|
var newTask = self.tasks[i];
|
||
|
//se è un task che c'erà già
|
||
|
if (!(""+newTask.id).startsWith("tmp_")) {
|
||
|
//si recupera il vecchio task
|
||
|
var oldTask;
|
||
|
for (var j = 0; j < oldProject.tasks.length; j++) {
|
||
|
if (oldProject.tasks[j].id == newTask.id) {
|
||
|
oldTask = oldProject.tasks[j];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
// chack only status or dateChanges
|
||
|
if (oldTask && (oldTask.status != newTask.status || oldTask.start != newTask.start || oldTask.end != newTask.end)) {
|
||
|
changes = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$("#LOG_CHANGES_CONTAINER").css("display", changes ? "inline-block" : "none");
|
||
|
}
|
||
|
|
||
|
|
||
|
if (showSave) {
|
||
|
$("body").stopTime("gantt.manageSaveRequired").oneTime(200, "gantt.manageSaveRequired", checkChanges);
|
||
|
} else {
|
||
|
$("#LOG_CHANGES_CONTAINER").hide();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* workStartHour,endStartHour : millis from 00:00
|
||
|
* dateFormat dd/MM/yyyy HH:mm
|
||
|
* working period resolution in millis or days
|
||
|
*/
|
||
|
GanttMaster.prototype.setHoursOn = function(startWorkingHour,endWorkingHour,dateFormat,resolution){
|
||
|
//console.debug("resolution",resolution)
|
||
|
Date.defaultFormat= dateFormat;
|
||
|
Date.startWorkingHour=startWorkingHour;
|
||
|
Date.endWorkingHour=endWorkingHour;
|
||
|
Date.useMillis=resolution>=1000;
|
||
|
Date.workingPeriodResolution=resolution;
|
||
|
millisInWorkingDay=endWorkingHour-startWorkingHour;
|
||
|
};
|