diff options
Diffstat (limited to 'assets/js/src')
-rw-r--r-- | assets/js/src/App.js | 8 | ||||
-rw-r--r-- | assets/js/src/AvgTimeColumnChart.js | 7 | ||||
-rw-r--r-- | assets/js/src/Board.js | 13 | ||||
-rw-r--r-- | assets/js/src/Gantt.js | 402 | ||||
-rw-r--r-- | assets/js/src/LeadCycleTimeChart.js | 7 | ||||
-rw-r--r-- | assets/js/src/Router.js | 5 | ||||
-rw-r--r-- | assets/js/src/TaskTimeColumnChart.js | 7 | ||||
-rw-r--r-- | assets/js/src/Tooltip.js | 1 |
8 files changed, 430 insertions, 20 deletions
diff --git a/assets/js/src/App.js b/assets/js/src/App.js index 0fc96a6a..301e30d4 100644 --- a/assets/js/src/App.js +++ b/assets/js/src/App.js @@ -8,7 +8,6 @@ function App() { this.popover = new Popover(this); this.keyboardShortcuts(); this.boardSelector(); - this.listen(); this.poll(); // Alert box fadeout @@ -28,8 +27,6 @@ function App() { } App.prototype.listen = function() { - $(document).off(); - this.popover.listen(); this.markdown.listen(); this.sidebar.listen(); @@ -42,6 +39,11 @@ App.prototype.listen = function() { this.focus(); }; +App.prototype.refresh = function() { + $(document).off(); + this.listen(); +}; + App.prototype.focus = function() { // Autofocus fields (html5 autofocus works only with page onload) diff --git a/assets/js/src/AvgTimeColumnChart.js b/assets/js/src/AvgTimeColumnChart.js index f8b7d2ee..3fe02ea3 100644 --- a/assets/js/src/AvgTimeColumnChart.js +++ b/assets/js/src/AvgTimeColumnChart.js @@ -1,7 +1,8 @@ -function AvgTimeColumnChart() { +function AvgTimeColumnChart(app) { + this.app = app; } -AvgTimeColumnChart.prototype.execute = function(app) { +AvgTimeColumnChart.prototype.execute = function() { var metrics = $("#chart").data("metrics"); var plots = [$("#chart").data("label")]; var categories = []; @@ -28,7 +29,7 @@ AvgTimeColumnChart.prototype.execute = function(app) { }, y: { tick: { - format: app.formatDuration + format: this.app.formatDuration } } }, diff --git a/assets/js/src/Board.js b/assets/js/src/Board.js index 7015f1c6..3299b9d4 100644 --- a/assets/js/src/Board.js +++ b/assets/js/src/Board.js @@ -1,10 +1,9 @@ -function Board() { - this.app = null; +function Board(app) { + this.app = app; this.checkInterval = null; } -Board.prototype.execute = function(app) { - this.app = app; +Board.prototype.execute = function() { this.app.swimlane.refresh(); this.app.swimlane.listen(); this.poll(); @@ -33,7 +32,7 @@ Board.prototype.check = function() { $.ajax({ cache: false, - url: $("#board").attr("data-check-url"), + url: $("#board").data("check-url"), statusCode: { 200: function(data) { self.refresh(data); }, 304: function () { self.app.hideLoadingIcon(); } @@ -47,7 +46,7 @@ Board.prototype.save = function(taskId, columnId, position, swimlaneId) { $.ajax({ cache: false, - url: $("#board").attr("data-save-url"), + url: $("#board").data("save-url"), contentType: "application/json", type: "POST", processData: false, @@ -65,7 +64,7 @@ Board.prototype.save = function(taskId, columnId, position, swimlaneId) { Board.prototype.refresh = function(data) { $("#board-container").replaceWith(data); - this.app.listen(); + this.app.refresh(); this.app.swimlane.refresh(); this.app.swimlane.listen(); this.resizeColumnHeight(); diff --git a/assets/js/src/Gantt.js b/assets/js/src/Gantt.js new file mode 100644 index 00000000..131799c0 --- /dev/null +++ b/assets/js/src/Gantt.js @@ -0,0 +1,402 @@ +// Based on jQuery.ganttView v.0.8.8 Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com - MIT License +function Gantt(app) { + this.app = app; + this.data = []; + + this.options = { + container: "#gantt-chart", + showWeekends: true, + cellWidth: 21, + cellHeight: 31, + slideWidth: 1000, + vHeaderWidth: 200 + }; +} + +// Save task after a resize or move +Gantt.prototype.saveTask = function(task) { + this.app.showLoadingIcon(); + + $.ajax({ + cache: false, + url: $(this.options.container).data("save-url"), + contentType: "application/json", + type: "POST", + processData: false, + data: JSON.stringify(task), + complete: this.app.hideLoadingIcon.bind(this) + }); +}; + +// Build the Gantt chart +Gantt.prototype.execute = function() { + this.data = this.prepareData($(this.options.container).data('tasks')); + + var minDays = Math.floor((this.options.slideWidth / this.options.cellWidth) + 5); + var range = this.getDateRange(minDays); + var startDate = range[0]; + var endDate = range[1]; + var container = $(this.options.container); + var chart = jQuery("<div>", { "class": "ganttview" }); + + chart.append(this.renderVerticalHeader()); + chart.append(this.renderSlider(startDate, endDate)); + container.append(chart); + + jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", container).addClass("last"); + jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", container).addClass("last"); + jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", container).addClass("last"); + + this.listenForBlockResize(startDate); + this.listenForBlockDrag(startDate); +}; + +// Render task list on the left +Gantt.prototype.renderVerticalHeader = function() { + var headerDiv = jQuery("<div>", { "class": "ganttview-vtheader" }); + var itemDiv = jQuery("<div>", { "class": "ganttview-vtheader-item" }); + var seriesDiv = jQuery("<div>", { "class": "ganttview-vtheader-series" }); + + for (var i = 0; i < this.data.length; i++) { + seriesDiv.append(jQuery("<div>", { + "class": "ganttview-vtheader-series-name tooltip", + "title": "<strong>" + this.data[i].column_title + "</strong> (" + this.data[i].progress + ")<br/>" + this.data[i].title + }).append(jQuery("<a>", {"href": this.data[i].link, "target": "_blank"}).append(this.data[i].title))); + } + + itemDiv.append(seriesDiv); + headerDiv.append(itemDiv); + + return headerDiv; +}; + +// Render right part of the chart (top header + grid + bars) +Gantt.prototype.renderSlider = function(startDate, endDate) { + var slideDiv = jQuery("<div>", {"class": "ganttview-slide-container"}); + var dates = this.getDates(startDate, endDate); + + slideDiv.append(this.renderHorizontalHeader(dates)); + slideDiv.append(this.renderGrid(dates)); + slideDiv.append(this.addBlockContainers()); + this.addBlocks(slideDiv, startDate); + + return slideDiv; +}; + +// Render top header (days) +Gantt.prototype.renderHorizontalHeader = function(dates) { + var headerDiv = jQuery("<div>", { "class": "ganttview-hzheader" }); + var monthsDiv = jQuery("<div>", { "class": "ganttview-hzheader-months" }); + var daysDiv = jQuery("<div>", { "class": "ganttview-hzheader-days" }); + var totalW = 0; + + for (var y in dates) { + for (var m in dates[y]) { + var w = dates[y][m].length * this.options.cellWidth; + totalW = totalW + w; + + monthsDiv.append(jQuery("<div>", { + "class": "ganttview-hzheader-month", + "css": { "width": (w - 1) + "px" } + }).append($.datepicker.regional[$("body").data('js-lang')].monthNames[m] + " " + y)); + + for (var d in dates[y][m]) { + daysDiv.append(jQuery("<div>", { "class": "ganttview-hzheader-day" }).append(dates[y][m][d].getDate())); + } + } + } + + monthsDiv.css("width", totalW + "px"); + daysDiv.css("width", totalW + "px"); + headerDiv.append(monthsDiv).append(daysDiv); + + return headerDiv; +}; + +// Render grid +Gantt.prototype.renderGrid = function(dates) { + var gridDiv = jQuery("<div>", { "class": "ganttview-grid" }); + var rowDiv = jQuery("<div>", { "class": "ganttview-grid-row" }); + + for (var y in dates) { + for (var m in dates[y]) { + for (var d in dates[y][m]) { + var cellDiv = jQuery("<div>", { "class": "ganttview-grid-row-cell" }); + if (this.options.showWeekends && this.isWeekend(dates[y][m][d])) { + cellDiv.addClass("ganttview-weekend"); + } + rowDiv.append(cellDiv); + } + } + } + var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * this.options.cellWidth; + rowDiv.css("width", w + "px"); + gridDiv.css("width", w + "px"); + + for (var i = 0; i < this.data.length; i++) { + gridDiv.append(rowDiv.clone()); + } + + return gridDiv; +}; + +// Render bar containers +Gantt.prototype.addBlockContainers = function() { + var blocksDiv = jQuery("<div>", { "class": "ganttview-blocks" }); + + for (var i = 0; i < this.data.length; i++) { + blocksDiv.append(jQuery("<div>", { "class": "ganttview-block-container" })); + } + + return blocksDiv; +}; + +// Render bars +Gantt.prototype.addBlocks = function(slider, start) { + var rows = jQuery("div.ganttview-blocks div.ganttview-block-container", slider); + var rowIdx = 0; + + for (var i = 0; i < this.data.length; i++) { + var series = this.data[i]; + var size = this.daysBetween(series.start, series.end) + 1; + var offset = this.daysBetween(start, series.start); + var text = jQuery("<div>", {"class": "ganttview-block-text"}); + + var block = jQuery("<div>", { + "class": "ganttview-block tooltip", + "title": this.getBarTooltip(this.data[i]), + "css": { + "width": ((size * this.options.cellWidth) - 9) + "px", + "margin-left": (offset * this.options.cellWidth) + "px" + } + }).append(text); + + if (size >= 2) { + text.append(this.data[i].progress); + } + + block.data("task", this.data[i]); + this.setTaskColor(block, this.data[i]); + jQuery(rows[rowIdx]).append(block); + rowIdx = rowIdx + 1; + } +}; + +// Get tooltip for task bars +Gantt.prototype.getBarTooltip = function(task) { + + if (task.not_defined) { + return $(this.options.container).data("label-not-defined"); + } + + return "<strong>" + task.progress + "</strong><br/>" + + $(this.options.container).data("label-assignee") + " " + (task.assignee ? task.assignee : '') + "<br/>" + + $(this.options.container).data("label-start-date") + " " + $.datepicker.formatDate('yy-mm-dd', task.start) + "<br/>" + + $(this.options.container).data("label-end-date") + " " + $.datepicker.formatDate('yy-mm-dd', task.end); +}; + +// Set task color +Gantt.prototype.setTaskColor = function(block, task) { + if (task.not_defined) { + block.addClass("ganttview-block-not-defined"); + } + else { + block.css("background-color", task.color.background); + block.css("border-color", task.color.border); + } +}; + +// Setup jquery-ui resizable +Gantt.prototype.listenForBlockResize = function(startDate) { + var self = this; + + jQuery("div.ganttview-block", this.options.container).resizable({ + grid: this.options.cellWidth, + handles: "e,w", + stop: function() { + var block = jQuery(this); + self.updateDataAndPosition(block, startDate); + self.saveTask(block.data("task")); + } + }); +}; + +// Setup jquery-ui drag and drop +Gantt.prototype.listenForBlockDrag = function(startDate) { + var self = this; + + jQuery("div.ganttview-block", this.options.container).draggable({ + axis: "x", + grid: [this.options.cellWidth, this.options.cellWidth], + stop: function() { + var block = jQuery(this); + self.updateDataAndPosition(block, startDate); + self.saveTask(block.data("task")); + } + }); +}; + +// Update the task data and the position on the chart +Gantt.prototype.updateDataAndPosition = function(block, startDate) { + var container = jQuery("div.ganttview-slide-container", this.options.container); + var scroll = container.scrollLeft(); + var offset = block.offset().left - container.offset().left - 1 + scroll; + var task = block.data("task"); + + // Restore color for defined block + task.not_defined = false; + this.setTaskColor(block, task); + + // Set new start date + var daysFromStart = Math.round(offset / this.options.cellWidth); + var newStart = this.addDays(this.cloneDate(startDate), daysFromStart); + task.start = newStart; + + // Set new end date + var width = block.outerWidth(); + var numberOfDays = Math.round(width / this.options.cellWidth) - 1; + task.end = this.addDays(this.cloneDate(newStart), numberOfDays); + + if (numberOfDays > 0) { + jQuery("div.ganttview-block-text", block).text(task.progress); + } + + // Update tooltip + block.attr("title", this.getBarTooltip(task)); + + block.data("task", task); + + // Remove top and left properties to avoid incorrect block positioning, + // set position to relative to keep blocks relative to scrollbar when scrolling + block + .css("top", "") + .css("left", "") + .css("position", "relative") + .css("margin-left", offset + "px"); +}; + +// Creates a 3 dimensional array [year][month][day] of every day +// between the given start and end dates +Gantt.prototype.getDates = function(start, end) { + var dates = []; + dates[start.getFullYear()] = []; + dates[start.getFullYear()][start.getMonth()] = [start]; + var last = start; + + while (this.compareDate(last, end) == -1) { + var next = this.addDays(this.cloneDate(last), 1); + + if (! dates[next.getFullYear()]) { + dates[next.getFullYear()] = []; + } + + if (! dates[next.getFullYear()][next.getMonth()]) { + dates[next.getFullYear()][next.getMonth()] = []; + } + + dates[next.getFullYear()][next.getMonth()].push(next); + last = next; + } + + return dates; +}; + +// Convert data to Date object +Gantt.prototype.prepareData = function(data) { + for (var i = 0; i < data.length; i++) { + var start = new Date(data[i].start[0], data[i].start[1] - 1, data[i].start[2], 0, 0, 0, 0); + data[i].start = start; + + var end = new Date(data[i].end[0], data[i].end[1] - 1, data[i].end[2], 0, 0, 0, 0); + data[i].end = end; + } + + return data; +}; + +// Get the start and end date from the data provided +Gantt.prototype.getDateRange = function(minDays) { + var minStart = new Date(); + var maxEnd = new Date(); + + for (var i = 0; i < this.data.length; i++) { + var start = new Date(); + start.setTime(Date.parse(this.data[i].start)); + + var end = new Date(); + end.setTime(Date.parse(this.data[i].end)); + + if (i == 0) { + minStart = start; + maxEnd = end; + } + + if (this.compareDate(minStart, start) == 1) { + minStart = start; + } + + if (this.compareDate(maxEnd, end) == -1) { + maxEnd = end; + } + } + + // Insure that the width of the chart is at least the slide width to avoid empty + // whitespace to the right of the grid + if (this.daysBetween(minStart, maxEnd) < minDays) { + maxEnd = this.addDays(this.cloneDate(minStart), minDays); + } + + // Always start one day before the minStart + minStart.setDate(minStart.getDate() - 1); + + return [minStart, maxEnd]; +}; + +// Returns the number of day between 2 dates +Gantt.prototype.daysBetween = function(start, end) { + if (! start || ! end) { + return 0; + } + + var count = 0, date = this.cloneDate(start); + + while (this.compareDate(date, end) == -1) { + count = count + 1; + this.addDays(date, 1); + } + + return count; +}; + +// Return true if it's the weekend +Gantt.prototype.isWeekend = function(date) { + return date.getDay() % 6 == 0; +}; + +// Clone Date object +Gantt.prototype.cloneDate = function(date) { + return new Date(date.getTime()); +}; + +// Add days to a Date object +Gantt.prototype.addDays = function(date, value) { + date.setDate(date.getDate() + value * 1); + return date; +}; + +/** + * Compares the first date to the second date and returns an number indication of their relative values. + * + * -1 = date1 is lessthan date2 + * 0 = values are equal + * 1 = date1 is greaterthan date2. + */ +Gantt.prototype.compareDate = function(date1, date2) { + if (isNaN(date1) || isNaN(date2)) { + throw new Error(date1 + " - " + date2); + } else if (date1 instanceof Date && date2 instanceof Date) { + return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0; + } else { + throw new TypeError(date1 + " - " + date2); + } +}; diff --git a/assets/js/src/LeadCycleTimeChart.js b/assets/js/src/LeadCycleTimeChart.js index 501ed892..9ba4a222 100644 --- a/assets/js/src/LeadCycleTimeChart.js +++ b/assets/js/src/LeadCycleTimeChart.js @@ -1,7 +1,8 @@ -function LeadCycleTimeChart() { +function LeadCycleTimeChart(app) { + this.app = app; } -LeadCycleTimeChart.prototype.execute = function(app) { +LeadCycleTimeChart.prototype.execute = function() { var metrics = $("#chart").data("metrics"); var cycle = [$("#chart").data("label-cycle")]; var lead = [$("#chart").data("label-lead")]; @@ -37,7 +38,7 @@ LeadCycleTimeChart.prototype.execute = function(app) { }, y: { tick: { - format: app.formatDuration + format: this.app.formatDuration } } } diff --git a/assets/js/src/Router.js b/assets/js/src/Router.js index d1e5de2a..6993e5c3 100644 --- a/assets/js/src/Router.js +++ b/assets/js/src/Router.js @@ -10,7 +10,8 @@ Router.prototype.dispatch = function(app) { for (var id in this.routes) { if (document.getElementById(id)) { var controller = Object.create(this.routes[id].prototype); - controller.execute(app); + this.routes[id].apply(controller, [app]); + controller.execute(); break; } } @@ -30,5 +31,7 @@ jQuery(document).ready(function() { router.addRoute('analytic-avg-time-column', AvgTimeColumnChart); router.addRoute('analytic-task-time-column', TaskTimeColumnChart); router.addRoute('analytic-lead-cycle-time', LeadCycleTimeChart); + router.addRoute('gantt-chart', Gantt); router.dispatch(app); + app.listen(); }); diff --git a/assets/js/src/TaskTimeColumnChart.js b/assets/js/src/TaskTimeColumnChart.js index a95424a0..1ecc486b 100644 --- a/assets/js/src/TaskTimeColumnChart.js +++ b/assets/js/src/TaskTimeColumnChart.js @@ -1,7 +1,8 @@ -function TaskTimeColumnChart() { +function TaskTimeColumnChart(app) { + this.app = app; } -TaskTimeColumnChart.prototype.execute = function(app) { +TaskTimeColumnChart.prototype.execute = function() { var metrics = $("#chart").data("metrics"); var plots = [$("#chart").data("label")]; var categories = []; @@ -28,7 +29,7 @@ TaskTimeColumnChart.prototype.execute = function(app) { }, y: { tick: { - format: app.formatDuration + format: this.app.formatDuration } } }, diff --git a/assets/js/src/Tooltip.js b/assets/js/src/Tooltip.js index 48102da6..0ec8b268 100644 --- a/assets/js/src/Tooltip.js +++ b/assets/js/src/Tooltip.js @@ -8,6 +8,7 @@ Tooltip.prototype.listen = function() { $(".tooltip").tooltip({ track: false, show: false, + hide: false, position: { my: 'left-20 top', at: 'center bottom+9', |