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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d86482f7d567c915609dbed5cd99b0bdff6164bc
4
- data.tar.gz: 33d93e318e1ee763cf789bcbe2ef03869764fc01
3
+ metadata.gz: 8efacb168159b06d556d94203b948bb1cda78ab7
4
+ data.tar.gz: 3d7b7ee44264d130e8008a3745fd1576c253168a
5
5
  SHA512:
6
- metadata.gz: a93eaa1b650983cc103ba44e6087657255f59f6f19ccb650c30fe263c8d235256eb735575fd429b99b4df4579f2fa16ee3f6bf9e3fb94551cdbd34cfa934bda0
7
- data.tar.gz: 52b77023491893bb230bbf82b190e06b70838aded0846ed533449129c2a40a1eb9d77397e0c84eaa0f0c58e145533a2df337c32b5a838909af91ff1f9dbea7be
6
+ metadata.gz: b318e3c4e1972c0d4714bdcdd1a47733218c5e57e99b79e6b6028044045eece5f558b19c4f8eb9de7c122db49a4b4cab7f214e13a9eb576dae575305adcebb35
7
+ data.tar.gz: 180cbf6eec4ac22f1a3b8d333ae1fcd4d967a4d6a4889a9a42b1bad69fbf7312cd8d008dffb208e852b7498588180b3b958fc5cb5443313ed34535b793f2fd31
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GanttRails
2
2
 
3
- gantt_rails wraps the [gantt.js](https://github.com/taitems/jQuery.Gantt) library in a rails engine for simple use with the asset pipeline provided by rails 2.3 and up.
3
+ gantt_rails wraps the [gantt.js](https://github.com/taitems/jQuery.Gantt) library in a rails engine for simple use with the asset pipeline provided by Rails 3.1 and up. The gem includes the development (non-minified) source for ease of exploration. the asset pipleline will do the heavy lifting and minify it for you in production.
4
4
 
5
5
 
6
6
  ## Installation
@@ -0,0 +1,992 @@
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 Ganttalendar(zoom, startmillis, endMillis, master, minGanttSize) {
24
+ this.master = master; // is the a GantEditor instance
25
+ this.element; // is the jquery element containing gantt
26
+ this.highlightBar;
27
+
28
+ this.svg; // instance of svg object containing gantt
29
+ this.tasksGroup; //instance of svg group containing tasks
30
+ this.linksGroup; //instance of svg group containing links
31
+
32
+ this.zoom = zoom;
33
+ this.minGanttSize = minGanttSize;
34
+ this.includeToday = true; //when true today is always visible. If false boundaries comes from tasks periods
35
+ this.showCriticalPath = false; //when true critical path is highlighted
36
+
37
+ this.zoomLevels = ["d", "w", "m", "q", "s", "y"];
38
+
39
+ this.element = this.create(zoom, startmillis, endMillis);
40
+
41
+ this.linkOnProgress = false; //set to true when creating a new link
42
+
43
+ this.rowHeight = 30; // todo get it from css?
44
+ this.taskHeight=20;
45
+ this.taskVertOffset=(this.rowHeight-this.taskHeight)/2
46
+
47
+ }
48
+
49
+ Ganttalendar.prototype.zoomGantt = function (isPlus) {
50
+ var curLevel = this.zoom;
51
+ var pos = this.zoomLevels.indexOf(curLevel + "");
52
+
53
+ var newPos = pos;
54
+ if (isPlus) {
55
+ newPos = pos <= 0 ? 0 : pos - 1;
56
+ } else {
57
+ newPos = pos >= this.zoomLevels.length - 1 ? this.zoomLevels.length - 1 : pos + 1;
58
+ }
59
+ if (newPos != pos) {
60
+ curLevel = this.zoomLevels[newPos];
61
+ this.zoom = curLevel;
62
+ this.refreshGantt();
63
+ }
64
+ };
65
+
66
+
67
+ Ganttalendar.prototype.create = function (zoom, originalStartmillis, originalEndMillis) {
68
+ //console.debug("Gantt.create " + new Date(originalStartmillis) + " - " + new Date(originalEndMillis));
69
+
70
+ var self = this;
71
+
72
+ function getPeriod(zoomLevel, stMil, endMillis) {
73
+ var start = new Date(stMil);
74
+ var end = new Date(endMillis);
75
+
76
+
77
+ //reset hours
78
+ if (zoomLevel == "d") {
79
+ start.setHours(0, 0, 0, 0);
80
+ end.setHours(23, 59, 59, 999);
81
+
82
+ start.setFirstDayOfThisWeek();
83
+ end.setFirstDayOfThisWeek();
84
+ end.setDate(end.getDate() + 6);
85
+
86
+
87
+ //reset day of week
88
+ } else if (zoomLevel == "w") {
89
+ start.setHours(0, 0, 0, 0);
90
+ end.setHours(23, 59, 59, 999);
91
+
92
+ start.setFirstDayOfThisWeek();
93
+ end.setFirstDayOfThisWeek();
94
+ end.setDate(end.getDate() + 6);
95
+
96
+ //reset day of month
97
+ } else if (zoomLevel == "m") {
98
+ start.setHours(0, 0, 0, 0);
99
+ end.setHours(23, 59, 59, 999);
100
+
101
+ start.setDate(1);
102
+ end.setDate(1);
103
+ end.setMonth(end.getMonth() + 1);
104
+ end.setDate(end.getDate() - 1);
105
+
106
+ //reset to quarter
107
+ } else if (zoomLevel == "q") {
108
+ start.setHours(0, 0, 0, 0);
109
+ end.setHours(23, 59, 59, 999);
110
+ start.setDate(1);
111
+ start.setMonth(Math.floor(start.getMonth() / 3) * 3);
112
+ end.setDate(1);
113
+ end.setMonth(Math.floor(end.getMonth() / 3) * 3 + 3);
114
+ end.setDate(end.getDate() - 1);
115
+
116
+ //reset to semester
117
+ } else if (zoomLevel == "s") {
118
+ start.setHours(0, 0, 0, 0);
119
+ end.setHours(23, 59, 59, 999);
120
+ start.setDate(1);
121
+
122
+ start.setMonth(Math.floor(start.getMonth() / 6) * 6);
123
+ end.setDate(1);
124
+ end.setMonth(Math.floor(end.getMonth() / 6) * 6 + 6);
125
+ end.setDate(end.getDate() - 1);
126
+
127
+ //reset to year - > gen
128
+ } else if (zoomLevel == "y") {
129
+ start.setHours(0, 0, 0, 0);
130
+ end.setHours(23, 59, 59, 999);
131
+
132
+ start.setDate(1);
133
+ start.setMonth(0);
134
+
135
+ end.setDate(1);
136
+ end.setMonth(12);
137
+ end.setDate(end.getDate() - 1);
138
+ }
139
+ return {start:start.getTime(), end:end.getTime()};
140
+ }
141
+
142
+ function createHeadCell(lbl, span, additionalClass, width) {
143
+ var th = $("<th>").html(lbl).attr("colSpan", span);
144
+ if (width)
145
+ th.width(width);
146
+ if (additionalClass)
147
+ th.addClass(additionalClass);
148
+ return th;
149
+ }
150
+
151
+ function createBodyCell(span, isEnd, additionalClass) {
152
+ var ret = $("<td>").html("").attr("colSpan", span).addClass("ganttBodyCell");
153
+ if (isEnd)
154
+ ret.addClass("end");
155
+ if (additionalClass)
156
+ ret.addClass(additionalClass);
157
+ return ret;
158
+ }
159
+
160
+ function createGantt(zoom, startPeriod, endPeriod) {
161
+ var tr1 = $("<tr>").addClass("ganttHead1");
162
+ var tr2 = $("<tr>").addClass("ganttHead2");
163
+ var trBody = $("<tr>").addClass("ganttBody");
164
+
165
+ function iterate(renderFunction1, renderFunction2) {
166
+ var start = new Date(startPeriod);
167
+ //loop for header1
168
+ while (start.getTime() <= endPeriod) {
169
+ renderFunction1(start);
170
+ }
171
+
172
+ //loop for header2
173
+ start = new Date(startPeriod);
174
+ while (start.getTime() <= endPeriod) {
175
+ renderFunction2(start);
176
+ }
177
+ }
178
+
179
+ //this is computed by hand in order to optimize cell size
180
+ var computedTableWidth;
181
+
182
+ // year
183
+ if (zoom == "y") {
184
+ computedTableWidth = Math.floor(((endPeriod - startPeriod) / (3600000 * 24 * 180)) * 100); //180gg = 1 sem = 100px
185
+ iterate(function (date) {
186
+ tr1.append(createHeadCell(date.format("yyyy"), 2));
187
+ date.setFullYear(date.getFullYear() + 1);
188
+ }, function (date) {
189
+ var sem = (Math.floor(date.getMonth() / 6) + 1);
190
+ tr2.append(createHeadCell(GanttMaster.messages["GANTT_SEMESTER_SHORT"] + sem, 1, null, 100));
191
+ trBody.append(createBodyCell(1, sem == 2));
192
+ date.setMonth(date.getMonth() + 6);
193
+ });
194
+
195
+ //semester
196
+ } else if (zoom == "s") {
197
+ computedTableWidth = Math.floor(((endPeriod - startPeriod) / (3600000 * 24 * 90)) * 100); //90gg = 1 quarter = 100px
198
+ iterate(function (date) {
199
+ var end = new Date(date.getTime());
200
+ end.setMonth(end.getMonth() + 6);
201
+ end.setDate(end.getDate() - 1);
202
+ tr1.append(createHeadCell(date.format("MMM") + " - " + end.format("MMM yyyy"), 2));
203
+ date.setMonth(date.getMonth() + 6);
204
+ }, function (date) {
205
+ var quarter = ( Math.floor(date.getMonth() / 3) + 1);
206
+ tr2.append(createHeadCell(GanttMaster.messages["GANTT_QUARTER_SHORT"] + quarter, 1, null, 100));
207
+ trBody.append(createBodyCell(1, quarter % 2 == 0));
208
+ date.setMonth(date.getMonth() + 3);
209
+ });
210
+
211
+ //quarter
212
+ } else if (zoom == "q") {
213
+ computedTableWidth = Math.floor(((endPeriod - startPeriod) / (3600000 * 24 * 30)) * 300); //1 month= 300px
214
+ iterate(function (date) {
215
+ var end = new Date(date.getTime());
216
+ end.setMonth(end.getMonth() + 3);
217
+ end.setDate(end.getDate() - 1);
218
+ tr1.append(createHeadCell(date.format("MMM") + " - " + end.format("MMM yyyy"), 3));
219
+ date.setMonth(date.getMonth() + 3);
220
+ }, function (date) {
221
+ var lbl = date.format("MMM");
222
+ tr2.append(createHeadCell(lbl, 1, null, 300));
223
+ trBody.append(createBodyCell(1, date.getMonth() % 3 == 2));
224
+ date.setMonth(date.getMonth() + 1);
225
+ });
226
+
227
+ //month
228
+ } else if (zoom == "m") {
229
+ computedTableWidth = Math.floor(((endPeriod - startPeriod) / (3600000 * 24 * 1)) * 25); //1 day= 20px
230
+ iterate(function (date) {
231
+ var sm = date.getTime();
232
+ date.setMonth(date.getMonth() + 1);
233
+ var daysInMonth = Math.round((date.getTime() - sm) / (3600000 * 24));
234
+ tr1.append(createHeadCell(new Date(sm).format("MMMM yyyy"), daysInMonth)); //spans mumber of dayn in the month
235
+ }, function (date) {
236
+ tr2.append(createHeadCell(date.format("d"), 1, isHoliday(date) ? "holyH" : null, 25));
237
+ var nd = new Date(date.getTime());
238
+ nd.setDate(date.getDate() + 1);
239
+ trBody.append(createBodyCell(1, nd.getDate() == 1, isHoliday(date) ? "holy" : null));
240
+ date.setDate(date.getDate() + 1);
241
+ });
242
+
243
+ //week
244
+ } else if (zoom == "w") {
245
+ computedTableWidth = Math.floor(((endPeriod - startPeriod) / (3600000 * 24)) * 40); //1 day= 40px
246
+ iterate(function (date) {
247
+ var end = new Date(date.getTime());
248
+ end.setDate(end.getDate() + 6);
249
+ tr1.append(createHeadCell(date.format("MMM d") + " - " + end.format("MMM d'yy"), 7));
250
+ date.setDate(date.getDate() + 7);
251
+ }, function (date) {
252
+ tr2.append(createHeadCell(date.format("EEEE").substr(0, 1), 1, isHoliday(date) ? "holyH" : null, 40));
253
+ trBody.append(createBodyCell(1, date.getDay() % 7 == (self.master.firstDayOfWeek + 6) % 7, isHoliday(date) ? "holy" : null));
254
+ date.setDate(date.getDate() + 1);
255
+ });
256
+
257
+ //days
258
+ } else if (zoom == "d") {
259
+ computedTableWidth = Math.floor(((endPeriod - startPeriod) / (3600000 * 24)) * 100); //1 day= 100px
260
+ iterate(function (date) {
261
+ var end = new Date(date.getTime());
262
+ end.setDate(end.getDate() + 6);
263
+ tr1.append(createHeadCell(date.format("MMMM d") + " - " + end.format("MMMM d yyyy"), 7));
264
+ date.setDate(date.getDate() + 7);
265
+ }, function (date) {
266
+ tr2.append(createHeadCell(date.format("EEE d"), 1, isHoliday(date) ? "holyH" : null, 100));
267
+ trBody.append(createBodyCell(1, date.getDay() % 7 == (self.master.firstDayOfWeek + 6) % 7, isHoliday(date) ? "holy" : null));
268
+ date.setDate(date.getDate() + 1);
269
+ });
270
+
271
+ } else {
272
+ console.error("Wrong level " + zoom);
273
+ }
274
+
275
+ //set a minimal width
276
+ computedTableWidth = Math.max(computedTableWidth, self.minGanttSize);
277
+
278
+ var table = $("<table cellspacing=0 cellpadding=0>");
279
+ table.append(tr1).append(tr2).css({width:computedTableWidth});
280
+
281
+ var head = table.clone().addClass("fixHead");
282
+
283
+ table.append(trBody).addClass("ganttTable");
284
+
285
+
286
+ var height = self.master.editor.element.height();
287
+ table.height(height);
288
+
289
+ var box = $("<div>");
290
+ box.addClass("gantt unselectable").attr("unselectable", "true").css({position:"relative", width:computedTableWidth});
291
+ box.append(table);
292
+
293
+ box.append(head);
294
+
295
+
296
+ //highlightBar
297
+ var hlb = $("<div>").addClass("ganttHighLight");
298
+ box.append(hlb);
299
+ self.highlightBar = hlb;
300
+
301
+ //create the svg
302
+ box.svg({settings:{class:"ganttSVGBox"},
303
+ onLoad: function (svg) {
304
+ //console.debug("svg loaded", svg);
305
+
306
+ //creates gradient and definitions
307
+ var defs = svg.defs('myDefs');
308
+
309
+
310
+ //create backgound
311
+ var extDep = svg.pattern(defs, "extDep", 0, 0, 10, 10, 0, 0, 10, 10, {patternUnits:'userSpaceOnUse'});
312
+ var img=svg.image(extDep, 0, 0, 10, 10, self.master.resourceUrl + "hasExternalDeps.png",{opacity:.3});
313
+
314
+ self.svg = svg;
315
+ $(svg).addClass("ganttSVGBox");
316
+
317
+ //creates grid group
318
+ var gridGroup = svg.group("gridGroup");
319
+
320
+ //creates rows grid
321
+ for (var i = 40; i <= height; i += self.rowHeight)
322
+ svg.line(gridGroup, 0, i, "100%", i, {class:"ganttLinesSVG"});
323
+
324
+ //creates links group
325
+ self.linksGroup = svg.group("linksGroup");
326
+
327
+ //creates tasks group
328
+ self.tasksGroup = svg.group("tasksGroup");
329
+
330
+ //compute scalefactor fx
331
+ self.fx = computedTableWidth / (endPeriod - startPeriod);
332
+
333
+ // drawTodayLine
334
+ if (new Date().getTime() > self.startMillis && new Date().getTime() < self.endMillis) {
335
+ var x = Math.round(((new Date().getTime()) - self.startMillis) * self.fx);
336
+ svg.line(gridGroup, x, 0, x, "100%", {class:"ganttTodaySVG"});
337
+ }
338
+
339
+ }
340
+ });
341
+
342
+ return box;
343
+ }
344
+
345
+ //if include today synch extremes
346
+ if (this.includeToday) {
347
+ var today = new Date().getTime();
348
+ originalStartmillis = originalStartmillis > today ? today : originalStartmillis;
349
+ originalEndMillis = originalEndMillis < today ? today : originalEndMillis;
350
+ }
351
+
352
+
353
+ //get best dimension fo gantt
354
+ var period = getPeriod(zoom, originalStartmillis, originalEndMillis); //this is enlarged to match complete periods basing on zoom level
355
+
356
+ //console.debug(new Date(period.start) + " " + new Date(period.end));
357
+ self.startMillis = period.start; //real dimension of gantt
358
+ self.endMillis = period.end;
359
+ self.originalStartMillis = originalStartmillis; //minimal dimension required by user or by task duration
360
+ self.originalEndMillis = originalEndMillis;
361
+
362
+ var table = createGantt(zoom, period.start, period.end);
363
+
364
+ return table;
365
+ };
366
+
367
+
368
+ //<%-------------------------------------- GANT TASK GRAPHIC ELEMENT --------------------------------------%>
369
+ Ganttalendar.prototype.drawTask = function (task) {
370
+ //console.debug("drawTask", task.name,new Date(task.start));
371
+ var self = this;
372
+ //var prof = new Profiler("ganttDrawTask");
373
+ editorRow = task.rowElement;
374
+ //var top = editorRow.position().top + self.master.editor.element.parent().scrollTop();
375
+ var top = editorRow.position().top + editorRow.offsetParent().scrollTop();
376
+ var x = Math.round((task.start - self.startMillis) * self.fx);
377
+ task.hasChild = task.isParent();
378
+
379
+ var taskBox = $(_createTaskSVG(task, {x:x, y:top+self.taskVertOffset, width:Math.round((task.end - task.start) * self.fx),height:self.taskHeight}));
380
+ task.ganttElement = taskBox;
381
+ if (self.showCriticalPath && task.isCritical)
382
+ taskBox.addClass("critical");
383
+
384
+ if (this.master.canWrite && task.canWrite) {
385
+
386
+ //bind all events on taskBox
387
+ taskBox
388
+ .click(function (e) { // manages selection
389
+ e.stopPropagation();// to avoid body remove focused
390
+ self.element.find(".focused").removeClass("focused");
391
+ $(".ganttSVGBox .focused").removeClass("focused");
392
+ var el = $(this);
393
+ if (!self.resDrop)
394
+ el.addClass("focused");
395
+ self.resDrop = false; //hack to avoid select
396
+
397
+ $("body").off("click.focused").one("click.focused", function () {
398
+ $(".ganttSVGBox .focused").removeClass("focused");
399
+ })
400
+
401
+ }).dblclick(function () {
402
+ self.master.showTaskEditor($(this).attr("taskid"));
403
+ }).mouseenter(function () {
404
+ //bring to top
405
+ var el = $(this);
406
+ if (!self.linkOnProgress) {
407
+ el.find(".linkHandleSVG").show();
408
+ } else {
409
+ el.addClass("linkOver");
410
+ //el.find(".linkHandleSVG"+(self.linkFromEnd?".taskLinkStartSVG ":".taskLinkEndSVG")).show()
411
+ }
412
+ }).mouseleave(function () {
413
+ var el = $(this);
414
+ el.removeClass("linkOver").find(".linkHandleSVG").hide();
415
+
416
+ }).mouseup(function (e) {
417
+ $(":focus").blur(); // in order to save grid field when moving task
418
+ }).mousedown(function () {
419
+ var task = self.master.getTask($(this).attr("taskId"));
420
+ task.rowElement.click();
421
+ }).dragExtedSVG($(self.svg.root()), {
422
+ canResize: this.master.canWrite && task.canWrite,
423
+ canDrag: !task.depends && this.master.canWrite && task.canWrite,
424
+ startDrag: function (e) {
425
+ $(".ganttSVGBox .focused").removeClass("focused");
426
+ },
427
+ drag: function (e) {
428
+ $("[from=" + task.id + "],[to=" + task.id + "]").trigger("update");
429
+ },
430
+ drop: function (e) {
431
+ self.resDrop = true; //hack to avoid select
432
+ var taskbox = $(this);
433
+ var task = self.master.getTask(taskbox.attr("taskid"));
434
+ var s = Math.round((parseFloat(taskbox.attr("x")) / self.fx) + self.startMillis);
435
+ self.master.beginTransaction();
436
+ self.master.moveTask(task, new Date(s));
437
+ self.master.endTransaction();
438
+ },
439
+ startResize:function (e) {
440
+ //console.debug("startResize");
441
+ $(".ganttSVGBox .focused").removeClass("focused");
442
+ var taskbox = $(this);
443
+ var text = $(self.svg.text(parseInt(taskbox.attr("x")) + parseInt(taskbox.attr("width") + 8), parseInt(taskbox.attr("y")), "", {"font-size":"10px", "fill":"red"}));
444
+ taskBox.data("textDur", text);
445
+ },
446
+ resize: function (e) {
447
+ //find and update links from, to
448
+ var taskbox = $(this);
449
+ var st = Math.round((parseFloat(taskbox.attr("x")) / self.fx) + self.startMillis);
450
+ var en = Math.round(((parseFloat(taskbox.attr("x")) + parseFloat(taskbox.attr("width"))) / self.fx) + self.startMillis);
451
+ var d = computeStartDate(st).distanceInWorkingDays(computeEndDate(en));
452
+ var text = taskBox.data("textDur");
453
+ text.attr("x", parseInt(taskbox.attr("x")) + parseInt(taskbox.attr("width")) + 8).html(d);
454
+
455
+ $("[from=" + task.id + "],[to=" + task.id + "]").trigger("update");
456
+ },
457
+ stopResize: function (e) {
458
+ self.resDrop = true; //hack to avoid select
459
+ //console.debug(ui)
460
+ var textBox = taskBox.data("textDur");
461
+ if (textBox)
462
+ textBox.remove();
463
+ var taskbox = $(this);
464
+ var task = self.master.getTask(taskbox.attr("taskid"));
465
+ var st = Math.round((parseFloat(taskbox.attr("x")) / self.fx) + self.startMillis);
466
+ var en = Math.round(((parseFloat(taskbox.attr("x")) + parseFloat(taskbox.attr("width"))) / self.fx) + self.startMillis);
467
+ self.master.beginTransaction();
468
+ self.master.changeTaskDates(task, new Date(st), new Date(en));
469
+ self.master.endTransaction();
470
+ }
471
+ });
472
+
473
+ //binding for creating link
474
+ taskBox.find(".linkHandleSVG").mousedown(function (e) {
475
+ e.preventDefault();
476
+ e.stopPropagation();
477
+ var taskBox = $(this).closest(".taskBoxSVG");
478
+ var svg = $(self.svg.root());
479
+ var offs = svg.offset();
480
+ self.linkOnProgress = true;
481
+ self.linkFromEnd = $(this).is(".taskLinkEndSVG");
482
+ svg.addClass("linkOnProgress");
483
+
484
+ // create the line
485
+ var startX = parseFloat(taskBox.attr("x")) + (self.linkFromEnd ? parseFloat(taskBox.attr("width")) : 0);
486
+ var startY = parseFloat(taskBox.attr("y")) + parseFloat(taskBox.attr("height")) / 2;
487
+ var line = self.svg.line(startX, startY, e.pageX - offs.left - 5, e.pageY - offs.top - 5, {class:"linkLineSVG"});
488
+ var circle = self.svg.circle(startX, startY, 5, {class:"linkLineSVG"});
489
+
490
+ //bind mousemove to draw a line
491
+ svg.bind("mousemove.linkSVG", function (e) {
492
+ var offs = svg.offset();
493
+ var nx = e.pageX - offs.left;
494
+ var ny = e.pageY - offs.top;
495
+ var c = Math.sqrt(Math.pow(nx - startX, 2) + Math.pow(ny - startY, 2));
496
+ nx = nx - (nx - startX) * 10 / c;
497
+ ny = ny - (ny - startY) * 10 / c;
498
+ self.svg.change(line, { x2:nx, y2:ny});
499
+ self.svg.change(circle, { cx:nx, cy:ny});
500
+ });
501
+
502
+ //bind mouseup un body to stop
503
+ $("body").one("mouseup.linkSVG", function (e) {
504
+ $(line).remove();
505
+ $(circle).remove();
506
+ self.linkOnProgress = false;
507
+ svg.removeClass("linkOnProgress");
508
+
509
+ $(self.svg.root()).unbind("mousemove.linkSVG");
510
+ var targetBox = $(e.target).closest(".taskBoxSVG");
511
+ //console.debug("create link from " + taskBox.attr("taskid") + " to " + targetBox.attr("taskid"));
512
+
513
+ if (targetBox && targetBox.attr("taskid") != taskBox.attr("taskid")) {
514
+ var taskTo;
515
+ var taskFrom;
516
+ if (self.linkFromEnd) {
517
+ taskTo = self.master.getTask(targetBox.attr("taskid"));
518
+ taskFrom = self.master.getTask(taskBox.attr("taskid"));
519
+ } else {
520
+ taskFrom = self.master.getTask(targetBox.attr("taskid"));
521
+ taskTo = self.master.getTask(taskBox.attr("taskid"));
522
+ }
523
+
524
+ if (taskTo && taskFrom) {
525
+ var gap = 0;
526
+ var depInp = taskTo.rowElement.find("[name=depends]");
527
+ depInp.val(depInp.val() + ((depInp.val() + "").length > 0 ? "," : "") + (taskFrom.getRow() + 1) + (gap != 0 ? ":" + gap : ""));
528
+ depInp.blur();
529
+ }
530
+ }
531
+ })
532
+ });
533
+ }
534
+ //ask for redraw link
535
+ self.redrawLinks();
536
+
537
+ //prof.stop();
538
+
539
+
540
+ function _createTaskSVG(task, dimensions) {
541
+ var svg = self.svg;
542
+ var taskSvg = svg.svg(self.tasksGroup, dimensions.x, dimensions.y, dimensions.width, dimensions.height, {class:"taskBox taskBoxSVG taskStatusSVG", status:task.status, taskid:task.id });
543
+
544
+ //svg.title(taskSvg, task.name);
545
+ //external box
546
+ var layout = svg.rect(taskSvg, 0, 0, "100%", "100%", {class:"taskLayout", rx:"2", ry:"2"});
547
+
548
+ svg.rect(taskSvg, 0, 0, "100%", "100%", {fill:"rgba(255,255,255,.3)"});
549
+
550
+ //external dep
551
+ if (task.hasExternalDep)
552
+ svg.rect(taskSvg, 0, 0, "100%", "100%", {fill:"url(#extDep)"});
553
+
554
+ //progress
555
+ if (task.progress > 0) {
556
+ var progress = svg.rect(taskSvg, 0, 0, (task.progress > 100 ? 100 : task.progress) + "%", "100%", {rx:"2", ry:"2"});
557
+ if (dimensions.width > 50) {
558
+ var textStyle = {fill:"#888", "font-size":"10px",class:"textPerc teamworkIcons",transform:"translate(5)"};
559
+ if (task.progress > 100)
560
+ textStyle["font-weight"]="bold";
561
+ if (task.progress > 90)
562
+ textStyle.transform = "translate(-40)";
563
+ svg.text(taskSvg, (task.progress > 90 ? 100 : task.progress) + "%", (self.rowHeight-5)/2, (task.progress>100?"!!! ":"")+ task.progress + "%", textStyle);
564
+ }
565
+ }
566
+
567
+ if (task.hasChild)
568
+ svg.rect(taskSvg, 0, 0, "100%", 3, {fill:"#000"});
569
+
570
+ if (task.startIsMilestone) {
571
+ svg.image(taskSvg, -9, dimensions.height/2-9, 18, 18, self.master.resourceUrl + "milestone.png")
572
+ }
573
+
574
+ if (task.endIsMilestone) {
575
+ svg.image(taskSvg, "100%",dimensions.height/2-9, 18, 18, self.master.resourceUrl + "milestone.png", {transform:"translate(-9)"})
576
+ }
577
+
578
+ //task label
579
+ svg.text(taskSvg, "100%", 18, task.name, {class:"taskLabelSVG", transform:"translate(20,-5)"});
580
+
581
+ //link tool
582
+ if (task.level>0){
583
+ svg.circle(taskSvg, "0", dimensions.height/2,dimensions.height/3, {class:"taskLinkStartSVG linkHandleSVG", transform:"translate("+(-dimensions.height/3-1)+")"});
584
+ svg.circle(taskSvg, "100%",dimensions.height/2,dimensions.height/3, {class:"taskLinkEndSVG linkHandleSVG", transform:"translate("+(dimensions.height/3+1)+")"});
585
+ }
586
+ return taskSvg
587
+ }
588
+
589
+ };
590
+
591
+
592
+ Ganttalendar.prototype.addTask = function (task) {
593
+ //set new boundaries for gantt
594
+ this.originalEndMillis = this.originalEndMillis > task.end ? this.originalEndMillis : task.end;
595
+ this.originalStartMillis = this.originalStartMillis < task.start ? this.originalStartMillis : task.start;
596
+ };
597
+
598
+
599
+ //<%-------------------------------------- GANT DRAW LINK SVG ELEMENT --------------------------------------%>
600
+ //'from' and 'to' are tasks already drawn
601
+ Ganttalendar.prototype.drawLink = function (from, to, type) {
602
+ var self = this;
603
+ //console.debug("drawLink")
604
+ var peduncolusSize = 10;
605
+
606
+ /**
607
+ * Given an item, extract its rendered position
608
+ * width and height into a structure.
609
+ */
610
+ function buildRect(item) {
611
+ var p = item.ganttElement.position();
612
+ var rect = {
613
+ left: parseFloat(item.ganttElement.attr("x")),
614
+ top: parseFloat(item.ganttElement.attr("y")),
615
+ width: parseFloat(item.ganttElement.attr("width")),
616
+ height:parseFloat(item.ganttElement.attr("height"))
617
+ };
618
+ return rect;
619
+ }
620
+
621
+ /**
622
+ * The default rendering method, which paints a start to end dependency.
623
+ */
624
+ function drawStartToEnd(from, to, ps) {
625
+ var svg = self.svg;
626
+
627
+ //this function update an existing link
628
+ function update() {
629
+ var group = $(this);
630
+ var from = group.data("from");
631
+ var to = group.data("to");
632
+
633
+ var rectFrom = buildRect(from);
634
+ var rectTo = buildRect(to);
635
+
636
+ var fx1 = rectFrom.left;
637
+ var fx2 = rectFrom.left + rectFrom.width;
638
+ var fy = rectFrom.height / 2 + rectFrom.top;
639
+
640
+ var tx1 = rectTo.left;
641
+ var tx2 = rectTo.left + rectTo.width;
642
+ var ty = rectTo.height / 2 + rectTo.top;
643
+
644
+
645
+ var tooClose = tx1 < fx2 + 2 * ps;
646
+ var r = 5; //radius
647
+ var arrowOffset = 5;
648
+ var up = fy > ty;
649
+ var fup = up ? -1 : 1;
650
+
651
+ var prev = fx2 + 2 * ps > tx1;
652
+ var fprev = prev ? -1 : 1;
653
+
654
+ var image = group.find("image");
655
+ var p = svg.createPath();
656
+
657
+ if (tooClose) {
658
+ var firstLine = fup * (rectFrom.height / 2 - 2 * r + 2);
659
+ p.move(fx2, fy)
660
+ .line(ps, 0, true)
661
+ .arc(r, r, 90, false, !up, r, fup * r, true)
662
+ .line(0, firstLine, true)
663
+ .arc(r, r, 90, false, !up, -r, fup * r, true)
664
+ .line(fprev * 2 * ps + (tx1 - fx2), 0, true)
665
+ .arc(r, r, 90, false, up, -r, fup * r, true)
666
+ .line(0, (Math.abs(ty - fy) - 4 * r - Math.abs(firstLine)) * fup - arrowOffset, true)
667
+ .arc(r, r, 90, false, up, r, fup * r, true)
668
+ .line(ps, 0, true);
669
+ image.attr({x:tx1 - 5, y:ty - 5 - arrowOffset});
670
+
671
+ } else {
672
+ p.move(fx2, fy)
673
+ .line((tx1 - fx2) / 2 - r, 0, true)
674
+ .arc(r, r, 90, false, !up, r, fup * r, true)
675
+ .line(0, ty - fy - fup * 2 * r + arrowOffset, true)
676
+ .arc(r, r, 90, false, up, r, fup * r, true)
677
+ .line((tx1 - fx2) / 2 - r, 0, true);
678
+ image.attr({x:tx1 - 5, y:ty - 5 + arrowOffset});
679
+ }
680
+
681
+ group.find("path").attr({d:p.path()});
682
+ }
683
+
684
+
685
+ // create the group
686
+ var group = svg.group(self.linksGroup, "" + from.id + "-" + to.id);
687
+ svg.title(group, from.name + " -> " + to.name);
688
+
689
+ var p = svg.createPath();
690
+
691
+ //add the arrow
692
+ svg.image(group, 0, 0, 5, 10, self.master.resourceUrl + "linkArrow.png");
693
+ //create empty path
694
+ svg.path(group, p, {class:"taskLinkPathSVG"});
695
+
696
+ //set "from" and "to" to the group, bind "update" and trigger it
697
+ var jqGroup = $(group).data({from:from, to:to }).attr({from:from.id, to:to.id}).on("update", update).trigger("update");
698
+
699
+ if (self.showCriticalPath && from.isCritical && to.isCritical)
700
+ jqGroup.addClass("critical");
701
+
702
+ jqGroup.addClass("linkGroup");
703
+ return jqGroup;
704
+ }
705
+
706
+
707
+ /**
708
+ * A rendering method which paints a start to start dependency.
709
+ */
710
+ function drawStartToStart(from, to) {
711
+ console.error("StartToStart not supported on SVG");
712
+ var rectFrom = buildRect(from);
713
+ var rectTo = buildRect(to);
714
+ }
715
+
716
+ var link;
717
+ // Dispatch to the correct renderer
718
+ if (type == 'start-to-start') {
719
+ link = drawStartToStart(from, to, peduncolusSize);
720
+ } else {
721
+ link = drawStartToEnd(from, to, peduncolusSize);
722
+ }
723
+
724
+ if (this.master.canWrite && (from.canWrite || to.canWrite)) {
725
+ link.click(function (e) {
726
+ var el = $(this);
727
+ e.stopPropagation();// to avoid body remove focused
728
+ self.element.find(".focused").removeClass("focused");
729
+ $(".ganttSVGBox .focused").removeClass("focused");
730
+ var el = $(this);
731
+ if (!self.resDrop)
732
+ el.addClass("focused");
733
+ self.resDrop = false; //hack to avoid select
734
+
735
+ $("body").off("click.focused").one("click.focused", function () {
736
+ $(".ganttSVGBox .focused").removeClass("focused");
737
+ })
738
+
739
+ });
740
+ }
741
+
742
+
743
+ };
744
+
745
+ Ganttalendar.prototype.redrawLinks = function () {
746
+ //console.debug("redrawLinks ");
747
+ var self = this;
748
+ this.element.stopTime("ganttlnksredr");
749
+ this.element.oneTime(60, "ganttlnksredr", function () {
750
+
751
+ //var prof=new Profiler("gd_drawLink_real");
752
+
753
+ //remove all links
754
+ $("#linksSVG").empty();
755
+
756
+ var collapsedDescendant = [];
757
+
758
+ //[expand]
759
+ var collapsedDescendant = self.master.getCollapsedDescendant();
760
+ for (var i = 0; i < self.master.links.length; i++) {
761
+ var link = self.master.links[i];
762
+
763
+ if (collapsedDescendant.indexOf(link.from) >= 0 || collapsedDescendant.indexOf(link.to) >= 0) continue;
764
+
765
+ self.drawLink(link.from, link.to);
766
+ }
767
+ //prof.stop();
768
+ });
769
+ };
770
+
771
+
772
+ Ganttalendar.prototype.reset = function () {
773
+ this.element.find(".linkGroup").remove();
774
+ this.element.find("[taskid]").remove();
775
+ };
776
+
777
+
778
+ Ganttalendar.prototype.redrawTasks = function () {
779
+ //[expand]
780
+ var collapsedDescendant = this.master.getCollapsedDescendant();
781
+ for (var i = 0; i < this.master.tasks.length; i++) {
782
+ var task = this.master.tasks[i];
783
+ if (collapsedDescendant.indexOf(task) >= 0) continue;
784
+ this.drawTask(task);
785
+ }
786
+ };
787
+
788
+
789
+ Ganttalendar.prototype.refreshGantt = function () {
790
+ //console.debug("refreshGantt")
791
+
792
+ if (this.showCriticalPath) {
793
+ this.master.computeCriticalPath();
794
+ }
795
+
796
+
797
+ var par = this.element.parent();
798
+
799
+ //try to maintain last scroll
800
+ var scrollY = par.scrollTop();
801
+ var scrollX = par.scrollLeft();
802
+
803
+ this.element.remove();
804
+ //guess the zoom level in base of period
805
+ if (!this.zoom) {
806
+ var days = Math.round((this.originalEndMillis - this.originalStartMillis) / (3600000 * 24));
807
+ this.zoom = this.zoomLevels[days < 2 ? 0 : (days < 15 ? 1 : (days < 60 ? 2 : (days < 150 ? 3 : 4 ) ) )];
808
+ }
809
+ var domEl = this.create(this.zoom, this.originalStartMillis, this.originalEndMillis);
810
+ this.element = domEl;
811
+ par.append(domEl);
812
+ this.redrawTasks();
813
+
814
+ //set old scroll
815
+ //console.debug("old scroll:",scrollX,scrollY)
816
+ par.scrollTop(scrollY);
817
+ par.scrollLeft(scrollX);
818
+
819
+ //set current task
820
+ this.synchHighlight();
821
+
822
+ };
823
+
824
+
825
+ Ganttalendar.prototype.fitGantt = function () {
826
+ delete this.zoom;
827
+ this.refreshGantt();
828
+ };
829
+
830
+ Ganttalendar.prototype.synchHighlight = function () {
831
+ if (this.master.currentTask && this.master.currentTask.ganttElement){
832
+ this.highlightBar.css("top", (parseInt(this.master.currentTask.ganttElement.attr("y"))-this.taskVertOffset) + "px");
833
+ }
834
+ };
835
+
836
+
837
+ Ganttalendar.prototype.centerOnToday = function () {
838
+ var x = Math.round(((new Date().getTime()) - this.startMillis) * this.fx) - 30;
839
+ //console.debug("centerOnToday "+x);
840
+ this.element.parent().scrollLeft(x);
841
+ };
842
+
843
+
844
+ /**
845
+ * Allows drag and drop and extesion of task boxes. Only works on x axis
846
+ * @param opt
847
+ * @return {*}
848
+ */
849
+ $.fn.dragExtedSVG = function (svg, opt) {
850
+
851
+ //doing this can work with one svg at once only
852
+ var target;
853
+ var svgX;
854
+ var rectMouseDx;
855
+
856
+ var options = {
857
+ canDrag: true,
858
+ canResize: true,
859
+ resizeZoneWidth:15,
860
+ minSize: 10,
861
+ startDrag: function (e) {},
862
+ drag: function (e) {},
863
+ drop: function (e) {},
864
+ startResize: function (e) {},
865
+ resize: function (e) {},
866
+ stopResize: function (e) {}
867
+ };
868
+
869
+ $.extend(options, opt);
870
+
871
+ this.each(function () {
872
+ var el = $(this);
873
+ svgX = svg.parent().offset().left; //parent is used instead of svg for a Firefox oddity
874
+ if (options.canDrag)
875
+ el.addClass("deSVGdrag");
876
+
877
+ if (options.canResize || options.canDrag) {
878
+ el.bind("mousedown.deSVG",
879
+ function (e) {
880
+ if ($(e.target).is("image")) {
881
+ e.preventDefault();
882
+ }
883
+
884
+ target = $(this);
885
+ var x1 = parseFloat(el.offset().left);
886
+
887
+ //var x1 = parseFloat(el.attr("x"));
888
+ var x2 = x1 + parseFloat(el.attr("width"));
889
+ var posx = e.pageX;
890
+
891
+ $("body").unselectable();
892
+
893
+ //start resize
894
+ var x = x2 - posx;
895
+ if (options.canResize && (x >= 0 && x <= options.resizeZoneWidth)) {
896
+ //store offset mouse x1
897
+ rectMouseDx = x2 - e.pageX;
898
+ target.attr("oldw", target.attr("width"));
899
+
900
+ var one = true;
901
+
902
+ //bind event for start resizing
903
+ $(svg).bind("mousemove.deSVG", function (e) {
904
+
905
+ if (one) {
906
+ //trigger startResize
907
+ options.startResize.call(target.get(0), e);
908
+ one = false;
909
+ }
910
+
911
+ //manage resizing
912
+ var posx = e.pageX;
913
+ var nW = posx - x1 + rectMouseDx;
914
+
915
+ target.attr("width", nW < options.minSize ? options.minSize : nW);
916
+ //callback
917
+ options.resize.call(target.get(0), e);
918
+ });
919
+
920
+ //bind mouse up on body to stop resizing
921
+ $("body").one("mouseup.deSVG", stopResize);
922
+
923
+ // start drag
924
+ } else if (options.canDrag) {
925
+ //store offset mouse x1
926
+ rectMouseDx = parseFloat(target.attr("x")) - e.pageX;
927
+ target.attr("oldx", target.attr("x"));
928
+
929
+ var one = true;
930
+ //bind event for start dragging
931
+ $(svg).bind("mousemove.deSVG",function (e) {
932
+ if (one) {
933
+ //trigger startDrag
934
+ options.startDrag.call(target.get(0), e);
935
+ one = false;
936
+ }
937
+
938
+ //manage resizing
939
+ target.attr("x", rectMouseDx + e.pageX);
940
+ //callback
941
+ options.drag.call(target.get(0), e);
942
+
943
+ }).bind("mouseleave.deSVG", drop);
944
+
945
+ //bind mouse up on body to stop resizing
946
+ $("body").one("mouseup.deSVG", drop);
947
+
948
+ }
949
+ }
950
+
951
+ ).bind("mousemove.deSVG",
952
+ function (e) {
953
+ var el = $(this);
954
+ var x1 = el.offset().left;
955
+ var x2 = x1 + parseFloat(el.attr("width"));
956
+ var posx = e.pageX;
957
+
958
+
959
+ //console.debug("mousemove", options.canResize && x2 - posx)
960
+ //set cursor handle
961
+ var x = x2 - posx;
962
+ if (options.canResize && (x >= 0 && x <= options.resizeZoneWidth)) {
963
+ el.addClass("deSVGhand");
964
+ } else {
965
+ el.removeClass("deSVGhand");
966
+ }
967
+ }
968
+
969
+ ).addClass("deSVG");
970
+ }
971
+ });
972
+ return this;
973
+
974
+
975
+ function stopResize(e) {
976
+ $(svg).unbind("mousemove.deSVG").unbind("mouseup.deSVG").unbind("mouseleave.deSVG");
977
+ //if (target && target.attr("oldw")!=target.attr("width"))
978
+ if (target)
979
+ options.stopResize.call(target.get(0), e); //callback
980
+ target = undefined;
981
+ $("body").clearUnselectable();
982
+ }
983
+
984
+ function drop(e) {
985
+ $(svg).unbind("mousemove.deSVG").unbind("mouseup.deSVG").unbind("mouseleave.deSVG");
986
+ if (target && target.attr("oldx") != target.attr("x"))
987
+ options.drop.call(target.get(0), e); //callback
988
+ target = undefined;
989
+ $("body").clearUnselectable();
990
+ }
991
+
992
+ };