gantt_rails 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1131 @@
1
+ /*
2
+ Copyright (c) 2012-2014 Open Lab
3
+ Written by Roberto Bicchierai and Silvia Chelazzi http://roberto.open-lab.com
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+ function GanttMaster() {
24
+ this.tasks = [];
25
+ this.deletedTaskIds = [];
26
+ this.links = [];
27
+
28
+ this.editor; //element for editor
29
+ this.gantt; //element for gantt
30
+ this.splitter; //element for splitter
31
+
32
+ this.element;
33
+
34
+
35
+ this.resources; //list of resources
36
+ this.roles; //list of roles
37
+
38
+ this.minEditableDate = 0;
39
+ this.maxEditableDate = Infinity;
40
+
41
+ this.canWriteOnParent = true;
42
+ this.canWrite = true;
43
+
44
+ this.firstDayOfWeek = Date.firstDayOfWeek;
45
+
46
+ this.currentTask; // task currently selected;
47
+
48
+ this.resourceUrl = 'res/'; // URL to resources (images etc.)
49
+ this.__currentTransaction; // a transaction object holds previous state during changes
50
+ this.__undoStack = [];
51
+ this.__redoStack = [];
52
+ this.__inUndoRedo = false; // a control flag to avoid Undo/Redo stacks reset when needed
53
+
54
+ var self = this;
55
+ }
56
+
57
+
58
+
59
+ GanttMaster.prototype.init = function (place) {
60
+ this.element = place;
61
+ var self = this;
62
+ //load templates
63
+ $("#gantEditorTemplates").loadTemplates().remove();
64
+
65
+ //create editor
66
+ this.editor = new GridEditor(this);
67
+ place.append(this.editor.gridified);
68
+
69
+ //create gantt
70
+ this.gantt = new Ganttalendar("m", new Date().getTime() - 3600000 * 24 * 2, new Date().getTime() + 3600000 * 24 * 15, this, place.width() * .6);
71
+
72
+ //setup splitter
73
+ self.splitter = $.splittify.init(place, this.editor.gridified, this.gantt.element, 60);
74
+ self.splitter.firstBoxMinWidth=30;
75
+
76
+ //prepend buttons
77
+ place.before($.JST.createFromTemplate({}, "GANTBUTTONS"));
78
+
79
+
80
+ //bindings
81
+ place.bind("refreshTasks.gantt",function () {
82
+ self.redrawTasks();
83
+ }).bind("refreshTask.gantt",function (e, task) {
84
+ self.drawTask(task);
85
+
86
+ }).bind("deleteCurrentTask.gantt",function (e) {
87
+ self.deleteCurrentTask();
88
+ }).bind("addAboveCurrentTask.gantt",function () {
89
+ self.addAboveCurrentTask();
90
+ }).bind("addBelowCurrentTask.gantt",function () {
91
+ self.addBelowCurrentTask();
92
+ }).bind("indentCurrentTask.gantt",function () {
93
+ self.indentCurrentTask();
94
+ }).bind("outdentCurrentTask.gantt",function () {
95
+ self.outdentCurrentTask();
96
+
97
+ }).bind("moveUpCurrentTask.gantt",function () {
98
+ self.moveUpCurrentTask();
99
+
100
+ }).bind("moveDownCurrentTask.gantt",function () {
101
+ self.moveDownCurrentTask();
102
+
103
+ }).bind("zoomPlus.gantt",function () {
104
+ self.gantt.zoomGantt(true);
105
+ }).bind("zoomMinus.gantt",function () {
106
+ self.gantt.zoomGantt(false);
107
+
108
+ }).bind("undo.gantt",function () {
109
+ if(!self.canWrite)
110
+ return;
111
+ self.undo();
112
+ }).bind("redo.gantt", function () {
113
+ if(!self.canWrite)
114
+ return;
115
+ self.redo();
116
+ }).bind("resize.gantt", function () {
117
+ self.resize();
118
+ });
119
+
120
+ //keyboard management bindings
121
+ $("body").bind("keydown.body", function (e) {
122
+ //console.debug(e.keyCode+ " "+e.target.nodeName)
123
+
124
+ //manage only events for body -> not from inputs
125
+ if (e.target.nodeName.toLowerCase() == "body" || e.target.nodeName.toLowerCase() == "svg") { // chrome,ff receive "body" ie "svg"
126
+ //something focused?
127
+ //console.debug(e.keyCode, e.ctrlKey)
128
+ var eventManaged=true;
129
+ switch (e.keyCode) {
130
+ case 46: //del
131
+ case 8: //backspace
132
+ var focused = self.gantt.element.find(".focused.focused");// orrible hack for chrome that seems to keep in memory a cached object
133
+ if (focused.is(".taskBox")) { // remove task
134
+ self.deleteCurrentTask();
135
+ } else if (focused.is(".linkGroup")) {
136
+ self.removeLink(focused.data("from"), focused.data("to"));
137
+ }
138
+ break;
139
+
140
+ case 38: //up
141
+ if (self.currentTask) {
142
+ if (self.currentTask.ganttElement.is(".focused")) {
143
+ self.moveUpCurrentTask();
144
+ self.gantt.element.oneTime(100, function () {self.currentTask.ganttElement.addClass("focused");});
145
+
146
+ } else {
147
+ self.currentTask.rowElement.prev().click();
148
+ }
149
+ }
150
+ break;
151
+
152
+ case 40: //down
153
+ if (self.currentTask) {
154
+ if (self.currentTask.ganttElement.is(".focused")) {
155
+ self.moveDownCurrentTask();
156
+ self.gantt.element.oneTime(100, function () {self.currentTask.ganttElement.addClass("focused");});
157
+ } else {
158
+ self.currentTask.rowElement.next().click();
159
+ }
160
+ }
161
+ break;
162
+
163
+ case 39: //right
164
+ if (self.currentTask) {
165
+ if (self.currentTask.ganttElement.is(".focused")) {
166
+ self.indentCurrentTask();
167
+ self.gantt.element.oneTime(100, function () {self.currentTask.ganttElement.addClass("focused");});
168
+ }
169
+ }
170
+ break;
171
+
172
+ case 37: //left
173
+ if (self.currentTask) {
174
+ if (self.currentTask.ganttElement.is(".focused")) {
175
+ self.outdentCurrentTask();
176
+ self.gantt.element.oneTime(100, function () {self.currentTask.ganttElement.addClass("focused");});
177
+ }
178
+ }
179
+ break;
180
+
181
+
182
+ case 89: //Y
183
+ if (e.ctrlKey) {
184
+ self.redo();
185
+ }
186
+ break;
187
+
188
+ case 90: //Z
189
+ if (e.ctrlKey) {
190
+ self.undo();
191
+ }
192
+ break;
193
+
194
+ default :{
195
+ eventManaged=false;
196
+ }
197
+
198
+ }
199
+ if (eventManaged){
200
+ e.preventDefault();
201
+ e.stopPropagation();
202
+ }
203
+ }
204
+ });
205
+ };
206
+
207
+ GanttMaster.messages = {
208
+ "CANNOT_WRITE": "CANNOT_WRITE",
209
+ "CHANGE_OUT_OF_SCOPE": "NO_RIGHTS_FOR_UPDATE_PARENTS_OUT_OF_EDITOR_SCOPE",
210
+ "START_IS_MILESTONE": "START_IS_MILESTONE",
211
+ "END_IS_MILESTONE": "END_IS_MILESTONE",
212
+ "TASK_HAS_CONSTRAINTS": "TASK_HAS_CONSTRAINTS",
213
+ "GANTT_ERROR_DEPENDS_ON_OPEN_TASK": "GANTT_ERROR_DEPENDS_ON_OPEN_TASK",
214
+ "GANTT_ERROR_DESCENDANT_OF_CLOSED_TASK":"GANTT_ERROR_DESCENDANT_OF_CLOSED_TASK",
215
+ "TASK_HAS_EXTERNAL_DEPS": "TASK_HAS_EXTERNAL_DEPS",
216
+ "GANTT_ERROR_LOADING_DATA_TASK_REMOVED":"GANTT_ERROR_LOADING_DATA_TASK_REMOVED",
217
+ "CIRCULAR_REFERENCE": "CIRCULAR_REFERENCE",
218
+ "ERROR_SETTING_DATES": "ERROR_SETTING_DATES",
219
+ "CANNOT_DEPENDS_ON_ANCESTORS": "CANNOT_DEPENDS_ON_ANCESTORS",
220
+ "CANNOT_DEPENDS_ON_DESCENDANTS": "CANNOT_DEPENDS_ON_DESCENDANTS",
221
+ "INVALID_DATE_FORMAT": "INVALID_DATE_FORMAT",
222
+ "GANTT_QUARTER_SHORT": "GANTT_QUARTER_SHORT",
223
+ "GANTT_SEMESTER_SHORT": "GANTT_SEMESTER_SHORT",
224
+ "CANNOT_CLOSE_TASK_IF_OPEN_ISSUE": "CANNOT_CLOSE_TASK_IF_OPEN_ISSUE"
225
+ };
226
+
227
+
228
+ GanttMaster.prototype.createTask = function (id, name, code, level, start, duration) {
229
+ var factory = new TaskFactory();
230
+ return factory.build(id, name, code, level, start, duration);
231
+ };
232
+
233
+
234
+ GanttMaster.prototype.createResource = function (id, name) {
235
+ var res = new Resource(id, name);
236
+ return res;
237
+ };
238
+
239
+
240
+ //update depends strings
241
+ GanttMaster.prototype.updateDependsStrings = function () {
242
+ //remove all deps
243
+ for (var i = 0; i < this.tasks.length; i++) {
244
+ this.tasks[i].depends = "";
245
+ }
246
+
247
+ for (var i = 0; i < this.links.length; i++) {
248
+ var link = this.links[i];
249
+ var dep = link.to.depends;
250
+ link.to.depends = link.to.depends + (link.to.depends == "" ? "" : ",") + (link.from.getRow() + 1) + (link.lag ? ":" + link.lag : "");
251
+ }
252
+
253
+ };
254
+
255
+ GanttMaster.prototype.removeLink = function (fromTask,toTask) {
256
+ //console.debug("removeLink");
257
+ if (!this.canWrite || (!fromTask.canWrite && !toTask.canWrite))
258
+ return;
259
+
260
+ this.beginTransaction();
261
+ var found = false;
262
+ for (var i = 0; i < this.links.length; i++) {
263
+ if (this.links[i].from == fromTask && this.links[i].to == toTask) {
264
+ this.links.splice(i, 1);
265
+ found = true;
266
+ break;
267
+ }
268
+ }
269
+
270
+ if (found) {
271
+ this.updateDependsStrings();
272
+ if (this.updateLinks(toTask))
273
+ this.changeTaskDates(toTask, toTask.start, toTask.end); // fake change to force date recomputation from dependencies
274
+ }
275
+ this.endTransaction();
276
+ };
277
+
278
+ GanttMaster.prototype.removeAllLinks = function (task,openTrans) {
279
+ //console.debug("removeLink");
280
+ if (!this.canWrite || (!task.canWrite && !task.canWrite))
281
+ return;
282
+
283
+ if (openTrans)
284
+ this.beginTransaction();
285
+ var found = false;
286
+ for (var i = 0; i < this.links.length; i++) {
287
+ if (this.links[i].from == task || this.links[i].to == task) {
288
+ this.links.splice(i, 1);
289
+ found = true;
290
+ }
291
+ }
292
+
293
+ if (found) {
294
+ this.updateDependsStrings();
295
+ }
296
+ if (openTrans)
297
+ this.endTransaction();
298
+ };
299
+
300
+ //------------------------------------ ADD TASK --------------------------------------------
301
+ GanttMaster.prototype.addTask = function (task, row) {
302
+ //console.debug("master.addTask",task,row,this);
303
+ task.master = this; // in order to access controller from task
304
+
305
+ //replace if already exists
306
+ var pos = -1;
307
+ for (var i = 0; i < this.tasks.length; i++) {
308
+ if (task.id == this.tasks[i].id) {
309
+ pos = i;
310
+ break;
311
+ }
312
+ }
313
+
314
+ if (pos >= 0) {
315
+ this.tasks.splice(pos, 1);
316
+ row = parseInt(pos);
317
+ }
318
+
319
+ //add task in collection
320
+ if (typeof(row) != "number") {
321
+ this.tasks.push(task);
322
+ } else {
323
+ this.tasks.splice(row, 0, task);
324
+
325
+ //recompute depends string
326
+ this.updateDependsStrings();
327
+ }
328
+
329
+ //add Link collection in memory
330
+ var linkLoops = !this.updateLinks(task);
331
+
332
+ //set the status according to parent
333
+ if (task.getParent())
334
+ task.status = task.getParent().status;
335
+ else
336
+ task.status = "STATUS_ACTIVE";
337
+
338
+ var ret = task;
339
+ if (linkLoops || !task.setPeriod(task.start, task.end)) {
340
+ //remove task from in-memory collection
341
+ //console.debug("removing task from memory",task);
342
+ this.tasks.splice(task.getRow(), 1);
343
+ ret = undefined;
344
+ } else {
345
+ //append task to editor
346
+ this.editor.addTask(task, row);
347
+ //append task to gantt
348
+ this.gantt.addTask(task);
349
+ }
350
+ return ret;
351
+ };
352
+
353
+
354
+ /**
355
+ * a project contains tasks, resources, roles, and info about permissions
356
+ * @param project
357
+ */
358
+ GanttMaster.prototype.loadProject = function (project) {
359
+ //console.debug("loadProject",project)
360
+ this.beginTransaction();
361
+ this.resources = project.resources;
362
+ this.roles = project.roles;
363
+ this.canWrite = project.canWrite;
364
+ this.canWriteOnParent = project.canWriteOnParent;
365
+ this.cannotCloseTaskIfIssueOpen = project.cannotCloseTaskIfIssueOpen;
366
+
367
+ if (project.minEditableDate)
368
+ this.minEditableDate = computeStart(project.minEditableDate);
369
+ else
370
+ this.minEditableDate = -Infinity;
371
+
372
+ if (project.maxEditableDate)
373
+ this.maxEditableDate = computeEnd(project.maxEditableDate);
374
+ else
375
+ this.maxEditableDate = Infinity;
376
+
377
+ this.loadTasks(project.tasks, project.selectedRow);
378
+ this.deletedTaskIds = [];
379
+
380
+ //recover saved splitter position
381
+ if (project.splitterPosition)
382
+ this.splitter.resize(project.splitterPosition);
383
+
384
+ //recover saved zoom level
385
+ if (project.zoom)
386
+ this.gantt.zoom=project.zoom;
387
+
388
+
389
+ //[expand]
390
+ //this.gantt.refreshGantt();
391
+
392
+
393
+ this.endTransaction();
394
+ var self = this;
395
+ this.gantt.element.oneTime(200, function () {self.gantt.centerOnToday()});
396
+ };
397
+
398
+
399
+ GanttMaster.prototype.loadTasks = function (tasks, selectedRow) {
400
+ var factory = new TaskFactory();
401
+ //reset
402
+ this.reset();
403
+
404
+ for (var i = 0; i < tasks.length; i++) {
405
+ var task = tasks[i];
406
+ if (!(task instanceof Task)) {
407
+ var t = factory.build(task.id, task.name, task.code, task.level, task.start, task.duration, task.collapsed);
408
+ for (var key in task) {
409
+ if (key != "end" && key != "start")
410
+ t[key] = task[key]; //copy all properties
411
+ }
412
+ task = t;
413
+ }
414
+ task.master = this; // in order to access controller from task
415
+ this.tasks.push(task); //append task at the end
416
+ }
417
+
418
+ //var prof=new Profiler("gm_loadTasks_addTaskLoop");
419
+ for (var i = 0; i < this.tasks.length; i++) {
420
+ var task = this.tasks[i];
421
+
422
+
423
+ var numOfError=this.__currentTransaction&&this.__currentTransaction.errors?this.__currentTransaction.errors.length:0;
424
+ //add Link collection in memory
425
+ 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.
426
+ if (this.__currentTransaction && numOfError!=this.__currentTransaction.errors.length){
427
+ var msg = "";
428
+ while (numOfError<this.__currentTransaction.errors.length) {
429
+ var err = this.__currentTransaction.errors.pop();
430
+ msg = msg + err.msg + "\n\n";
431
+ }
432
+ alert(msg);
433
+ }
434
+ this.removeAllLinks(task,false);
435
+ }
436
+
437
+ if (!task.setPeriod(task.start, task.end)) {
438
+ alert(GanttMaster.messages.GANNT_ERROR_LOADING_DATA_TASK_REMOVED + "\n" + task.name + "\n" +GanttMaster.messages.ERROR_SETTING_DATES);
439
+ //remove task from in-memory collection
440
+ this.tasks.splice(task.getRow(), 1);
441
+ } else {
442
+ //append task to editor
443
+ this.editor.addTask(task, null, true);
444
+ //append task to gantt
445
+ this.gantt.addTask(task);
446
+ }
447
+ }
448
+
449
+ this.editor.fillEmptyLines();
450
+ //prof.stop();
451
+
452
+ // re-select old row if tasks is not empty
453
+ if (this.tasks && this.tasks.length > 0) {
454
+ selectedRow = selectedRow ? selectedRow : 0;
455
+ this.tasks[selectedRow].rowElement.click();
456
+ }
457
+ };
458
+
459
+
460
+ GanttMaster.prototype.getTask = function (taskId) {
461
+ var ret;
462
+ for (var i = 0; i < this.tasks.length; i++) {
463
+ var tsk = this.tasks[i];
464
+ if (tsk.id == taskId) {
465
+ ret = tsk;
466
+ break;
467
+ }
468
+ }
469
+ return ret;
470
+ };
471
+
472
+
473
+ GanttMaster.prototype.getResource = function (resId) {
474
+ var ret;
475
+ for (var i = 0; i < this.resources.length; i++) {
476
+ var res = this.resources[i];
477
+ if (res.id == resId) {
478
+ ret = res;
479
+ break;
480
+ }
481
+ }
482
+ return ret;
483
+ };
484
+
485
+
486
+ GanttMaster.prototype.changeTaskDates = function (task, start, end) {
487
+ return task.setPeriod(start, end);
488
+ };
489
+
490
+
491
+ GanttMaster.prototype.moveTask = function (task, newStart) {
492
+ return task.moveTo(newStart, true);
493
+ };
494
+
495
+
496
+ GanttMaster.prototype.taskIsChanged = function () {
497
+ //console.debug("taskIsChanged");
498
+ var master = this;
499
+
500
+ //refresh is executed only once every 50ms
501
+ this.element.stopTime("gnnttaskIsChanged");
502
+ //var profilerext = new Profiler("gm_taskIsChangedRequest");
503
+ this.element.oneTime(50, "gnnttaskIsChanged", function () {
504
+ //console.debug("task Is Changed real call to redraw");
505
+ //var profiler = new Profiler("gm_taskIsChangedReal");
506
+ master.editor.redraw();
507
+ master.gantt.refreshGantt();
508
+ //profiler.stop();
509
+ });
510
+ //profilerext.stop();
511
+ };
512
+
513
+
514
+ GanttMaster.prototype.redraw = function () {
515
+ this.editor.redraw();
516
+ this.gantt.refreshGantt();
517
+ };
518
+
519
+ GanttMaster.prototype.reset = function () {
520
+ this.tasks = [];
521
+ this.links = [];
522
+ this.deletedTaskIds = [];
523
+ if (!this.__inUndoRedo) {
524
+ this.__undoStack = [];
525
+ this.__redoStack = [];
526
+ } else { // don't reset the stacks if we're in an Undo/Redo, but restart the inUndoRedo control
527
+ this.__inUndoRedo = false;
528
+ }
529
+ delete this.currentTask;
530
+
531
+ this.editor.reset();
532
+ this.gantt.reset();
533
+ };
534
+
535
+
536
+ GanttMaster.prototype.showTaskEditor = function (taskId) {
537
+ var task = this.getTask(taskId);
538
+ task.rowElement.find(".edit").click();
539
+ };
540
+
541
+ GanttMaster.prototype.saveProject = function () {
542
+ return this.saveGantt(false);
543
+ };
544
+
545
+ GanttMaster.prototype.saveGantt = function (forTransaction) {
546
+ //var prof = new Profiler("gm_saveGantt");
547
+ var saved = [];
548
+ for (var i = 0; i < this.tasks.length; i++) {
549
+ var task = this.tasks[i];
550
+ var cloned = task.clone();
551
+ delete cloned.master;
552
+ delete cloned.rowElement;
553
+ delete cloned.ganttElement;
554
+
555
+ saved.push(cloned);
556
+ }
557
+
558
+ var ret = {tasks:saved};
559
+ if (this.currentTask) {
560
+ ret.selectedRow = this.currentTask.getRow();
561
+ }
562
+
563
+ ret.deletedTaskIds = this.deletedTaskIds; //this must be consistent with transactions and undo
564
+
565
+ if (!forTransaction) {
566
+ ret.resources = this.resources;
567
+ ret.roles = this.roles;
568
+ ret.canWrite = this.canWrite;
569
+ ret.canWriteOnParent = this.canWriteOnParent;
570
+ ret.splitterPosition=this.splitter.perc;
571
+ ret.zoom=this.gantt.zoom;
572
+ }
573
+
574
+ //prof.stop();
575
+ return ret;
576
+ };
577
+
578
+
579
+ GanttMaster.prototype.updateLinks = function (task) {
580
+ //console.debug("updateLinks",task);
581
+ //var prof= new Profiler("gm_updateLinks");
582
+
583
+ // defines isLoop function
584
+ function isLoop(task, target, visited) {
585
+ //var prof= new Profiler("gm_isLoop");
586
+ //console.debug("isLoop :"+task.name+" - "+target.name);
587
+ if (target == task) {
588
+ return true;
589
+ }
590
+
591
+ var sups = task.getSuperiors();
592
+
593
+ //my parent' superiors are my superiors too
594
+ var p= task.getParent();
595
+ while (p){
596
+ sups=sups.concat(p.getSuperiors());
597
+ p= p.getParent();
598
+ }
599
+
600
+ //my children superiors are my superiors too
601
+ var chs=task.getChildren();
602
+ for (var i=0;i<chs.length;i++){
603
+ sups=sups.concat(chs[i].getSuperiors());
604
+ }
605
+
606
+
607
+ var loop = false;
608
+ //check superiors
609
+ for (var i = 0; i < sups.length; i++) {
610
+ var supLink = sups[i];
611
+ if (supLink.from == target) {
612
+ loop = true;
613
+ break;
614
+ } else {
615
+ if (visited.indexOf(supLink.from.id+"x"+target.id) <= 0) {
616
+ visited.push(supLink.from.id+"x"+target.id);
617
+ if (isLoop(supLink.from, target, visited)) {
618
+ loop = true;
619
+ break;
620
+ }
621
+ }
622
+ }
623
+ }
624
+
625
+ //check target parent
626
+ var tpar=target.getParent();
627
+ if (tpar ){
628
+ if (visited.indexOf(task.id+"x"+tpar.id) <= 0) {
629
+ visited.push(task.id+"x"+tpar.id);
630
+ if (isLoop(task,tpar, visited)) {
631
+ loop = true;
632
+ }
633
+ }
634
+ }
635
+
636
+ //prof.stop();
637
+ return loop;
638
+ }
639
+
640
+ //remove my depends
641
+ this.links = this.links.filter(function (link) {
642
+ return link.to != task;
643
+ });
644
+
645
+ var todoOk = true;
646
+ if (task.depends) {
647
+
648
+ //cannot depend from an ancestor
649
+ var parents = task.getParents();
650
+ //cannot depend from descendants
651
+ var descendants = task.getDescendant();
652
+
653
+ var deps = task.depends.split(",");
654
+ var newDepsString = "";
655
+
656
+ var visited = [];
657
+ for (var j = 0; j < deps.length; j++) {
658
+ var dep = deps[j]; // in the form of row(lag) e.g. 2:3,3:4,5
659
+ var par = dep.split(":");
660
+ var lag = 0;
661
+
662
+ if (par.length > 1) {
663
+ lag = parseInt(par[1]);
664
+ }
665
+
666
+ var sup = this.tasks[parseInt(par[0] - 1)];
667
+
668
+ if (sup) {
669
+ if (parents && parents.indexOf(sup) >= 0) {
670
+ this.setErrorOnTransaction(task.name + "\n" + GanttMaster.messages.CANNOT_DEPENDS_ON_ANCESTORS + "\n" + sup.name);
671
+ todoOk = false;
672
+
673
+ } else if (descendants && descendants.indexOf(sup) >= 0) {
674
+ this.setErrorOnTransaction(task.name + "\n" + GanttMaster.messages.CANNOT_DEPENDS_ON_DESCENDANTS + "\n" + sup.name);
675
+ todoOk = false;
676
+
677
+ } else if (isLoop(sup, task, visited)) {
678
+ todoOk = false;
679
+ this.setErrorOnTransaction(GanttMaster.messages.CIRCULAR_REFERENCE + "\n" + task.name + " -> " + sup.name);
680
+ } else {
681
+ this.links.push(new Link(sup, task, lag));
682
+ newDepsString = newDepsString + (newDepsString.length > 0 ? "," : "") + dep;
683
+ }
684
+ }
685
+ }
686
+
687
+ task.depends = newDepsString;
688
+
689
+ }
690
+
691
+ //prof.stop();
692
+
693
+ return todoOk;
694
+ };
695
+
696
+
697
+ GanttMaster.prototype.moveUpCurrentTask=function(){
698
+ var self=this;
699
+ //console.debug("moveUpCurrentTask",self.currentTask)
700
+ if(!self.canWrite )
701
+ return;
702
+
703
+ if (self.currentTask) {
704
+ self.beginTransaction();
705
+ self.currentTask.moveUp();
706
+ self.endTransaction();
707
+ }
708
+ };
709
+
710
+ GanttMaster.prototype.moveDownCurrentTask=function(){
711
+ var self=this;
712
+ //console.debug("moveDownCurrentTask",self.currentTask)
713
+ if(!self.canWrite)
714
+ return;
715
+
716
+ if (self.currentTask) {
717
+ self.beginTransaction();
718
+ self.currentTask.moveDown();
719
+ self.endTransaction();
720
+ }
721
+ };
722
+
723
+ GanttMaster.prototype.outdentCurrentTask=function(){
724
+ var self=this;
725
+ if(!self.canWrite|| !self.currentTask.canWrite)
726
+ return;
727
+
728
+ if (self.currentTask) {
729
+ var par = self.currentTask.getParent();
730
+
731
+ self.beginTransaction();
732
+ self.currentTask.outdent();
733
+ self.endTransaction();
734
+
735
+ //[expand]
736
+ if(par) self.editor.refreshExpandStatus(par);
737
+ }
738
+ };
739
+
740
+ GanttMaster.prototype.indentCurrentTask=function(){
741
+ var self=this;
742
+ if (!self.canWrite|| !self.currentTask.canWrite)
743
+ return;
744
+
745
+ if (self.currentTask) {
746
+ self.beginTransaction();
747
+ self.currentTask.indent();
748
+ self.endTransaction();
749
+ }
750
+ };
751
+
752
+ GanttMaster.prototype.addBelowCurrentTask=function(){
753
+ var self=this;
754
+ if (!self.canWrite)
755
+ return;
756
+
757
+ var factory = new TaskFactory();
758
+ self.beginTransaction();
759
+ var ch;
760
+ var row = 0;
761
+ if (self.currentTask) {
762
+ ch = factory.build("tmp_" + new Date().getTime(), "", "", self.currentTask.level + 1, self.currentTask.start, 1);
763
+ row = self.currentTask.getRow() + 1;
764
+ } else {
765
+ ch = factory.build("tmp_" + new Date().getTime(), "", "", 0, new Date().getTime(), 1);
766
+ }
767
+ var task = self.addTask(ch, row);
768
+ if (task) {
769
+ task.rowElement.click();
770
+ task.rowElement.find("[name=name]").focus();
771
+ }
772
+ self.endTransaction();
773
+ };
774
+
775
+ GanttMaster.prototype.addAboveCurrentTask=function(){
776
+ var self=this;
777
+ if (!self.canWrite)
778
+ return;
779
+ var factory = new TaskFactory();
780
+
781
+ var ch;
782
+ var row = 0;
783
+ if (self.currentTask) {
784
+ //cannot add brothers to root
785
+ if (self.currentTask.level <= 0)
786
+ return;
787
+
788
+ ch = factory.build("tmp_" + new Date().getTime(), "", "", self.currentTask.level, self.currentTask.start, 1);
789
+ row = self.currentTask.getRow();
790
+ } else {
791
+ ch = factory.build("tmp_" + new Date().getTime(), "", "", 0, new Date().getTime(), 1);
792
+ }
793
+ self.beginTransaction();
794
+ var task = self.addTask(ch, row);
795
+ if (task) {
796
+ task.rowElement.click();
797
+ task.rowElement.find("[name=name]").focus();
798
+ }
799
+ self.endTransaction();
800
+ };
801
+
802
+ GanttMaster.prototype.deleteCurrentTask=function(){
803
+ var self=this;
804
+ if (!self.currentTask || !self.canWrite || !self.currentTask.canWrite)
805
+ return;
806
+ var row = self.currentTask.getRow();
807
+ if (self.currentTask && (row > 0 || self.currentTask.isNew())) {
808
+ var par = self.currentTask.getParent();
809
+ self.beginTransaction();
810
+ self.currentTask.deleteTask();
811
+ self.currentTask = undefined;
812
+
813
+ //recompute depends string
814
+ self.updateDependsStrings();
815
+
816
+ //redraw
817
+ self.redraw();
818
+
819
+ //[expand]
820
+ if(par) self.editor.refreshExpandStatus(par);
821
+
822
+
823
+ //focus next row
824
+ row = row > self.tasks.length - 1 ? self.tasks.length - 1 : row;
825
+ if (row >= 0) {
826
+ self.currentTask = self.tasks[row];
827
+ self.currentTask.rowElement.click();
828
+ self.currentTask.rowElement.find("[name=name]").focus();
829
+ }
830
+ self.endTransaction();
831
+ }
832
+ };
833
+
834
+
835
+
836
+ //<%----------------------------- TRANSACTION MANAGEMENT ---------------------------------%>
837
+ GanttMaster.prototype.beginTransaction = function () {
838
+ if (!this.__currentTransaction) {
839
+ this.__currentTransaction = {
840
+ snapshot:JSON.stringify(this.saveGantt(true)),
841
+ errors: []
842
+ };
843
+ } else {
844
+ console.error("Cannot open twice a transaction");
845
+ }
846
+ return this.__currentTransaction;
847
+ };
848
+
849
+
850
+ GanttMaster.prototype.endTransaction = function () {
851
+ if (!this.__currentTransaction) {
852
+ console.error("Transaction never started.");
853
+ return true;
854
+ }
855
+
856
+ var ret = true;
857
+
858
+ //no error -> commit
859
+ if (this.__currentTransaction.errors.length <= 0) {
860
+ //console.debug("committing transaction");
861
+
862
+ //put snapshot in undo
863
+ this.__undoStack.push(this.__currentTransaction.snapshot);
864
+ //clear redo stack
865
+ this.__redoStack = [];
866
+
867
+ //shrink gantt bundaries
868
+ this.gantt.originalStartMillis = Infinity;
869
+ this.gantt.originalEndMillis = -Infinity;
870
+ for (var i = 0; i < this.tasks.length; i++) {
871
+ var task = this.tasks[i];
872
+ if (this.gantt.originalStartMillis > task.start)
873
+ this.gantt.originalStartMillis = task.start;
874
+ if (this.gantt.originalEndMillis < task.end)
875
+ this.gantt.originalEndMillis = task.end;
876
+
877
+ }
878
+ this.taskIsChanged(); //enqueue for gantt refresh
879
+
880
+
881
+ //error -> rollback
882
+ } else {
883
+ ret = false;
884
+ //console.debug("rolling-back transaction");
885
+ //try to restore changed tasks
886
+ var oldTasks = JSON.parse(this.__currentTransaction.snapshot);
887
+ this.deletedTaskIds = oldTasks.deletedTaskIds;
888
+ this.loadTasks(oldTasks.tasks, oldTasks.selectedRow);
889
+ this.redraw();
890
+
891
+ //compose error message
892
+ var msg = "";
893
+ for (var i = 0; i < this.__currentTransaction.errors.length; i++) {
894
+ var err = this.__currentTransaction.errors[i];
895
+ msg = msg + err.msg + "\n\n";
896
+ }
897
+ alert(msg);
898
+ }
899
+ //reset transaction
900
+ this.__currentTransaction = undefined;
901
+
902
+ //[expand]
903
+ this.editor.refreshExpandStatus(this.currentTask);
904
+
905
+ return ret;
906
+ };
907
+
908
+ //this function notify an error to a transaction -> transaction will rollback
909
+ GanttMaster.prototype.setErrorOnTransaction = function (errorMessage, task) {
910
+ if (this.__currentTransaction) {
911
+ this.__currentTransaction.errors.push({msg:errorMessage, task:task});
912
+ } else {
913
+ console.error(errorMessage);
914
+ }
915
+ };
916
+
917
+ // inhibit undo-redo
918
+ GanttMaster.prototype.checkpoint = function () {
919
+ this.__undoStack = [];
920
+ this.__redoStack = [];
921
+ };
922
+
923
+ //----------------------------- UNDO/REDO MANAGEMENT ---------------------------------%>
924
+
925
+ GanttMaster.prototype.undo = function () {
926
+ //console.debug("undo before:",this.__undoStack,this.__redoStack);
927
+ if (this.__undoStack.length > 0) {
928
+ var his = this.__undoStack.pop();
929
+ this.__redoStack.push(JSON.stringify(this.saveGantt()));
930
+ var oldTasks = JSON.parse(his);
931
+ this.deletedTaskIds = oldTasks.deletedTaskIds;
932
+ this.__inUndoRedo = true; // avoid Undo/Redo stacks reset
933
+ this.loadTasks(oldTasks.tasks, oldTasks.selectedRow);
934
+ //console.debug(oldTasks,oldTasks.deletedTaskIds)
935
+ this.redraw();
936
+ //console.debug("undo after:",this.__undoStack,this.__redoStack);
937
+ }
938
+ };
939
+
940
+ GanttMaster.prototype.redo = function () {
941
+ //console.debug("redo before:",undoStack,redoStack);
942
+ if (this.__redoStack.length > 0) {
943
+ var his = this.__redoStack.pop();
944
+ this.__undoStack.push(JSON.stringify(this.saveGantt()));
945
+ var oldTasks = JSON.parse(his);
946
+ this.deletedTaskIds = oldTasks.deletedTaskIds;
947
+ this.__inUndoRedo = true; // avoid Undo/Redo stacks reset
948
+ this.loadTasks(oldTasks.tasks, oldTasks.selectedRow);
949
+ this.redraw();
950
+ //console.debug("redo after:",undoStack,redoStack);
951
+ }
952
+ };
953
+
954
+
955
+ GanttMaster.prototype.resize = function () {
956
+ //console.debug("GanttMaster.resize")
957
+ this.splitter.resize();
958
+ };
959
+
960
+
961
+ GanttMaster.prototype.getCollapsedDescendant = function(){
962
+ var allTasks = this.tasks;
963
+ var collapsedDescendant = [];
964
+ for (var i = 0; i < allTasks.length; i++) {
965
+ var task = allTasks[i];
966
+ if(collapsedDescendant.indexOf(task) >= 0) continue;
967
+ if(task.collapsed) collapsedDescendant = collapsedDescendant.concat(task.getDescendant());
968
+ }
969
+ return collapsedDescendant;
970
+ }
971
+
972
+
973
+ /**
974
+ * Compute the critical path using Backflow algorithm.
975
+ * Translated from Java code supplied by M. Jessup here http://stackoverflow.com/questions/2985317/critical-path-method-algorithm
976
+ *
977
+ * For each task computes:
978
+ * earlyStart, earlyFinish, latestStart, latestFinish, criticalCost
979
+ *
980
+ * A task on the critical path has isCritical=true
981
+ * A task not in critical path can float by latestStart-earlyStart days
982
+ *
983
+ * If you use critical path avoid usage of dependencies between different levels of tasks
984
+ *
985
+ * WARNNG: It ignore milestones!!!!
986
+ * @return {*}
987
+ */
988
+ GanttMaster.prototype.computeCriticalPath = function () {
989
+
990
+ if (!this.tasks)
991
+ return false;
992
+
993
+ // do not consider grouping tasks
994
+ var tasks = this.tasks.filter(function (t) {
995
+ //return !t.isParent()
996
+ return (t.getRow() > 0) && (!t.isParent() || (t.isParent() && !t.isDependent()));
997
+ });
998
+
999
+ // reset values
1000
+ for (var i = 0; i < tasks.length; i++) {
1001
+ var t = tasks[i];
1002
+ t.earlyStart = -1;
1003
+ t.earlyFinish = -1;
1004
+ t.latestStart = -1;
1005
+ t.latestFinish = -1;
1006
+ t.criticalCost = -1;
1007
+ t.isCritical=false;
1008
+ }
1009
+
1010
+ // tasks whose critical cost has been calculated
1011
+ var completed = [];
1012
+ // tasks whose critical cost needs to be calculated
1013
+ var remaining = tasks.concat(); // put all tasks in remaining
1014
+
1015
+
1016
+ // Backflow algorithm
1017
+ // while there are tasks whose critical cost isn't calculated.
1018
+ while (remaining.length > 0) {
1019
+ var progress = false;
1020
+
1021
+ // find a new task to calculate
1022
+ for (var i = 0; i < remaining.length; i++) {
1023
+ var task = remaining[i];
1024
+ var inferiorTasks = task.getInferiorTasks();
1025
+
1026
+ if (containsAll(completed, inferiorTasks)) {
1027
+ // all dependencies calculated, critical cost is max dependency critical cost, plus our cost
1028
+ var critical = 0;
1029
+ for (var j = 0; j < inferiorTasks.length; j++) {
1030
+ var t = inferiorTasks[j];
1031
+ if (t.criticalCost > critical) {
1032
+ critical = t.criticalCost;
1033
+ }
1034
+ }
1035
+ task.criticalCost = critical + task.duration;
1036
+ // set task as calculated an remove
1037
+ completed.push(task);
1038
+ remaining.splice(i, 1);
1039
+
1040
+ // note we are making progress
1041
+ progress = true;
1042
+ }
1043
+ }
1044
+ // If we haven't made any progress then a cycle must exist in
1045
+ // the graph and we wont be able to calculate the critical path
1046
+ if (!progress) {
1047
+ console.error("Cyclic dependency, algorithm stopped!");
1048
+ return false;
1049
+ }
1050
+ }
1051
+
1052
+ // set earlyStart, earlyFinish, latestStart, latestFinish
1053
+ computeMaxCost(tasks);
1054
+ var initialNodes = initials(tasks);
1055
+ calculateEarly(initialNodes);
1056
+ calculateCritical(tasks);
1057
+
1058
+
1059
+
1060
+ /*
1061
+ for (var i = 0; i < tasks.length; i++) {
1062
+ var t = tasks[i];
1063
+ console.debug("Task ", t.name, t.duration, t.earlyStart, t.earlyFinish, t.latestStart, t.latestFinish, t.latestStart - t.earlyStart, t.earlyStart == t.latestStart)
1064
+ }*/
1065
+
1066
+ return tasks;
1067
+
1068
+
1069
+ function containsAll(set, targets) {
1070
+ for (var i = 0; i < targets.length; i++) {
1071
+ if (set.indexOf(targets[i]) < 0)
1072
+ return false;
1073
+ }
1074
+ return true;
1075
+ }
1076
+
1077
+ function computeMaxCost(tasks) {
1078
+ var max = -1;
1079
+ for (var i = 0; i < tasks.length; i++) {
1080
+ var t = tasks[i];
1081
+
1082
+ if (t.criticalCost > max)
1083
+ max = t.criticalCost;
1084
+ }
1085
+ //console.debug("Critical path length (cost): " + max);
1086
+ for (var i = 0; i < tasks.length; i++) {
1087
+ var t = tasks[i];
1088
+ t.setLatest(max);
1089
+ }
1090
+ }
1091
+
1092
+ function initials(tasks) {
1093
+ var initials = [];
1094
+ for (var i = 0; i < tasks.length; i++) {
1095
+ if (!tasks[i].depends || tasks[i].depends == "")
1096
+ initials.push(tasks[i]);
1097
+ }
1098
+ return initials;
1099
+ }
1100
+
1101
+ function calculateEarly(initials) {
1102
+ for (var i = 0; i < initials.length; i++) {
1103
+ var initial = initials[i];
1104
+ initial.earlyStart = 0;
1105
+ initial.earlyFinish = initial.duration;
1106
+ setEarly(initial);
1107
+ }
1108
+ }
1109
+
1110
+ function setEarly(initial) {
1111
+ var completionTime = initial.earlyFinish;
1112
+ var inferiorTasks = initial.getInferiorTasks();
1113
+ for (var i = 0; i < inferiorTasks.length; i++) {
1114
+ var t = inferiorTasks[i];
1115
+ if (completionTime >= t.earlyStart) {
1116
+ t.earlyStart = completionTime;
1117
+ t.earlyFinish = completionTime + t.duration;
1118
+ }
1119
+ setEarly(t);
1120
+ }
1121
+ }
1122
+
1123
+ function calculateCritical(tasks) {
1124
+ for (var i = 0; i < tasks.length; i++) {
1125
+ var t = tasks[i];
1126
+ t.isCritical=(t.earlyStart == t.latestStart)
1127
+ }
1128
+ }
1129
+
1130
+
1131
+ };