gantt_rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ };